diff --git a/.vitepress/components.d.ts b/.vitepress/components.d.ts index 2977c6af..b03ba472 100644 --- a/.vitepress/components.d.ts +++ b/.vitepress/components.d.ts @@ -2,17 +2,20 @@ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 -// biome-ignore lint: disable export {} /* prettier-ignore */ declare module 'vue' { export interface GlobalComponents { + Advanced: typeof import('./components/Advanced.vue')['default'] ArrowDown: typeof import('./components/ArrowDown.vue')['default'] BlogIndex: typeof import('./components/BlogIndex.vue')['default'] Box: typeof import('./components/Box.vue')['default'] Contributors: typeof import('./components/Contributors.vue')['default'] CourseLink: typeof import('./components/CourseLink.vue')['default'] + CRoot: typeof import('./components/CRoot.vue')['default'] + Deprecated: typeof import('./components/Deprecated.vue')['default'] + Experimental: typeof import('./components/Experimental.vue')['default'] FeaturesList: typeof import('./components/FeaturesList.vue')['default'] HomePage: typeof import('./components/HomePage.vue')['default'] ListItem: typeof import('./components/ListItem.vue')['default'] diff --git a/.vitepress/components/Advanced.vue b/.vitepress/components/Advanced.vue new file mode 100644 index 00000000..7bc97ede --- /dev/null +++ b/.vitepress/components/Advanced.vue @@ -0,0 +1,5 @@ + diff --git a/.vitepress/components/CRoot.vue b/.vitepress/components/CRoot.vue new file mode 100644 index 00000000..31a5f0bf --- /dev/null +++ b/.vitepress/components/CRoot.vue @@ -0,0 +1,5 @@ + diff --git a/.vitepress/components/Deprecated.vue b/.vitepress/components/Deprecated.vue new file mode 100644 index 00000000..9ae99627 --- /dev/null +++ b/.vitepress/components/Deprecated.vue @@ -0,0 +1,5 @@ + diff --git a/.vitepress/components/Experimental.vue b/.vitepress/components/Experimental.vue new file mode 100644 index 00000000..18681c68 --- /dev/null +++ b/.vitepress/components/Experimental.vue @@ -0,0 +1,5 @@ + diff --git a/.vitepress/components/FeaturesList.vue b/.vitepress/components/FeaturesList.vue index dd43d850..3c633519 100644 --- a/.vitepress/components/FeaturesList.vue +++ b/.vitepress/components/FeaturesList.vue @@ -1,64 +1,32 @@ diff --git a/.vitepress/components/HomePage.vue b/.vitepress/components/HomePage.vue index 6e4df15e..4feda3f2 100644 --- a/.vitepress/components/HomePage.vue +++ b/.vitepress/components/HomePage.vue @@ -9,7 +9,7 @@ import { sponsors } from '../sponsors'
@@ -19,18 +19,12 @@ import { sponsors } from '../sponsors' target="_blank" rel="noreferrer" > - 赞助 Vitest + Sponsor Vitest

- Deploys by Netlify + Deploys by Netlify

@@ -53,7 +47,10 @@ import { sponsors } from '../sponsors' text-align: center; font-weight: 600; white-space: nowrap; - transition: color 0.25s, border-color 0.25s, background-color 0.25s; + transition: + color 0.25s, + border-color 0.25s, + background-color 0.25s; /* .VPButton.medium */ border-radius: 20px; padding: 0 20px; diff --git a/.vitepress/components/Version.vue b/.vitepress/components/Version.vue index a0742ce9..da9c803e 100644 --- a/.vitepress/components/Version.vue +++ b/.vitepress/components/Version.vue @@ -1,9 +1,16 @@ diff --git a/.vitepress/config.ts b/.vitepress/config.ts index dc3b7f0a..b9d1725c 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1,5 +1,3 @@ -import type { DefaultTheme } from 'vitepress' -import process from 'node:process' import { transformerNotationWordHighlight } from '@shikijs/transformers' import { transformerTwoslash } from '@shikijs/vitepress-twoslash' import { withPwa } from '@vite-pwa/vitepress' @@ -8,6 +6,7 @@ import { groupIconMdPlugin, groupIconVitePlugin, } from 'vitepress-plugin-group-icons' +import llmstxt from 'vitepress-plugin-llms' import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import { version } from '../package.json' import { teamMembers } from './contributors' @@ -28,696 +27,1064 @@ import { pwa } from './scripts/pwa' import { transformHead } from './scripts/transformHead' export default ({ mode }: { mode: string }) => { - return withPwa( - defineConfig({ - lang: 'en-US', - title: vitestName, - description: vitestDescription, - srcExclude: ['**/guide/examples/*', '**/guide/cli-generated.md'], - locales: { - root: { - label: '简体中文', - lang: 'zh', - }, - zh: { - label: 'English', - lang: 'en-US', - link: 'https://vitest.dev/', - }, + return withPwa(defineConfig({ + lang: 'en-US', + title: vitestName, + description: vitestDescription, + srcExclude: [ + '**/guide/examples/*', + '**/guide/cli-generated.md', + ], + locales: { + root: { + label: 'English', + lang: 'en-US', }, - 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', - }, - ], - [ - '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 }], - ['meta', { property: 'og:description', content: vitestDescription }], - ['meta', { property: 'og:url', content: ogUrl }], - ['meta', { property: 'og:image', content: ogImage }], - ['meta', { name: 'twitter:card', content: 'summary_large_image' }], - [ - 'link', - { - rel: 'preload', - as: 'style', - onload: 'this.onload=null;this.rel=\'stylesheet\'', - href: font, - }, - ], - [ - 'noscript', - {}, - ``, - ], - ['link', { rel: 'me', href: 'https://m.webtoo.ls/@vitest' }], - ['link', { rel: 'mask-icon', href: '/logo.svg', color: '#ffffff' }], - [ - 'link', - { - rel: 'apple-touch-icon', - href: '/apple-touch-icon.png', - sizes: '180x180', + zh: { + label: '简体中文', + lang: 'zh', + link: 'https://cn.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' }], + ['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, tinyspy, node' }], + ['meta', { property: 'og:title', content: vitestName }], + ['meta', { property: 'og:description', content: vitestDescription }], + ['meta', { property: 'og:url', content: ogUrl }], + ['meta', { property: 'og:image', content: ogImage }], + ['meta', { name: 'twitter:card', content: 'summary_large_image' }], + ['link', { rel: 'preload', as: 'style', onload: 'this.onload=null;this.rel=\'stylesheet\'', href: font }], + ['noscript', {}, ``], + ['link', { rel: 'me', href: 'https://m.webtoo.ls/@vitest' }], + ['link', { rel: 'mask-icon', href: '/logo.svg', color: '#ffffff' }], + ['link', { rel: 'apple-touch-icon', href: '/apple-touch-icon.png', sizes: '180x180' }], + ], + lastUpdated: true, + vite: { + plugins: [ + groupIconVitePlugin({ + customIcon: { + 'CLI': 'vscode-icons:file-type-shell', + 'vitest.shims': 'vscode-icons:file-type-vitest', + 'vitest.config': 'vscode-icons:file-type-vitest', + 'vitest.workspace': 'vscode-icons:file-type-vitest', + '.spec.ts': 'vscode-icons:file-type-testts', + '.test.ts': 'vscode-icons:file-type-testts', + '.spec.js': 'vscode-icons:file-type-testjs', + '.test.js': 'vscode-icons:file-type-testjs', + 'marko': 'vscode-icons:file-type-marko', + 'qwik': 'logos:qwik-icon', + 'next': '', }, - ], + }) as any, + llmstxt(), ], - lastUpdated: true, - vite: { - plugins: [ - groupIconVitePlugin({ - customIcon: { - 'CLI': 'vscode-icons:file-type-shell', - 'vitest.shims': 'vscode-icons:file-type-vitest', - 'vitest.config': 'vscode-icons:file-type-vitest', - 'vitest.workspace': 'vscode-icons:file-type-vitest', - '.spec.ts': 'vscode-icons:file-type-testts', - '.test.ts': 'vscode-icons:file-type-testts', - '.spec.js': 'vscode-icons:file-type-testjs', - '.test.js': 'vscode-icons:file-type-testjs', - 'marko': 'vscode-icons:file-type-marko', - 'qwik': 'logos:qwik-icon', - 'next': '', - }, - }) as any, - ], + }, + markdown: { + config(md) { + md.use(tabsMarkdownPlugin) + md.use(groupIconMdPlugin) }, - markdown: { - config(md) { - md.use(tabsMarkdownPlugin) - md.use(groupIconMdPlugin) - }, - theme: { - light: 'github-light', - dark: 'github-dark', - }, - codeTransformers: - mode === 'development' - ? [transformerNotationWordHighlight()] - : [ - transformerNotationWordHighlight(), - transformerTwoslash({ - processHoverInfo: (info) => { - if (info.includes(process.cwd())) { - return info.replace(new RegExp(process.cwd(), 'g'), '') - } - return info - }, - }), - ], - languages: ['js', 'jsx', 'ts', 'tsx'], + theme: { + light: 'github-light', + dark: 'github-dark', }, - themeConfig: { - logo: '/logo.svg', - - outline: { - label: '页面导航', - }, - - editLink: { - pattern: 'https://github.com/vitest-dev/docs-cn/edit/dev/:path', - text: '在 GitHub 上编辑此页面', - }, + codeTransformers: mode === 'development' + ? [transformerNotationWordHighlight()] + : [ + transformerNotationWordHighlight(), + transformerTwoslash({ + processHoverInfo: (info) => { + if (info.includes(process.cwd())) { + return info.replace(new RegExp(process.cwd(), 'g'), '') + } + return info + }, + }), + ], + languages: ['js', 'jsx', 'ts', 'tsx'], + }, + themeConfig: { + logo: '/logo.svg', - lastUpdated: { - text: '最后更新于', - formatOptions: { - dateStyle: 'full', - timeStyle: 'medium', - }, - }, + editLink: { + pattern: 'https://github.com/vitest-dev/vitest/edit/main/docs/:path', + text: 'Suggest changes to this page', + }, - search: { - provider: 'local', - }, + search: { + provider: 'local', + /* provider: 'algolia', + options: { + appId: 'ZTF29HGJ69', + apiKey: '9c3ced6fed60d2670bb36ab7e8bed8bc', + indexName: 'vitest', + // searchParameters: { + // facetFilters: ['tags:en'], + // }, + }, */ + }, - docFooter: { - prev: '上一页', - next: '下一页', - }, + carbonAds: { + code: 'CW7DVKJE', + placement: 'vitestdev', + }, - carbonAds: { - code: 'CW7DVKJE', - placement: 'vitestdev', - }, + socialLinks: [ + { icon: 'bluesky', link: bluesky }, + { icon: 'mastodon', link: mastodon }, + { icon: 'discord', link: discord }, + { icon: 'github', link: github }, + ], - socialLinks: [ - { icon: 'bluesky', link: bluesky }, - { icon: 'mastodon', link: mastodon }, - { icon: 'discord', link: discord }, - { icon: 'github', link: github }, - ], + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright © 2021-PRESENT VoidZero Inc. and Vitest contributors', + }, - footer: { - message: 'Released under the MIT License.', - copyright: - 'Copyright © 2021-PRESENT VoidZero Inc. and Vitest contributors', + nav: [ + { text: 'Guides', link: '/guide/', activeMatch: '^/guide/' }, + { text: 'API', link: '/api/', activeMatch: '^/api/' }, + { text: 'Config', link: '/config/', activeMatch: '^/config/' }, + { + text: 'Blog', + link: '/blog', }, - - nav: [ - { - text: '指南 & API', - link: '/guide/', - activeMatch: '^/(guide|api)/(?!browser)', - }, - { text: '配置', link: '/config/', activeMatch: '^/config/' }, - { - text: '浏览器模式', - link: '/guide/browser', - activeMatch: '^/guide/browser/', - }, - { - text: '相关链接', - items: [ - { - text: '高级 API', - link: '/advanced/api/', - activeMatch: '^/advanced/', - }, - { - items: [ - { - text: '博客', - link: '/blog', - }, - { - text: '团队', - link: '/team', - }, - ], - }, - ], - }, - { - text: `v${version}`, - items: [ - { - items: [ - { - text: `v${version}`, - link: `https://github.com/vitest-dev/vitest/releases/tag/v${version}`, - }, - { - text: '更新日志', - link: releases, - }, - { - text: '贡献指南', - link: contributing, - }, - ], - }, - { - items: [ - { - text: '未发布', - link: 'https://main.vitest.dev/', - }, - { - text: 'v0.x', - link: 'https://v0.vitest.dev/', - }, - { - text: 'v1.x', - link: 'https://v1.vitest.dev/', - }, - { - text: 'v2.x', - link: 'https://v2.vitest.dev/', - }, - { - text: 'v3.x', - link: 'https://v3.cn.vitest.dev/', - }, - ], - }, - ], - }, - ], - - sidebar: { - '/guide/browser': [ - { - text: '介绍', - collapsed: false, - items: [ - { - text: '什么是浏览器模式', - link: '/guide/browser/why', - docFooterText: '什么是浏览器模式 | 浏览器模式', - }, - { - text: '快速起步', - link: '/guide/browser/', - docFooterText: '快速起步 | 浏览器模式', - }, - ], - }, - { - text: '配置', - collapsed: false, - items: [ - { - text: '浏览器配置', - link: '/guide/browser/config', - docFooterText: '浏览器配置 | 浏览器模式', - }, - { - text: '配置 Playwright', - link: '/guide/browser/playwright', - docFooterText: '配置 Playwright | 浏览器模式', - }, - { - text: '配置 WebdriverIO', - link: '/guide/browser/webdriverio', - docFooterText: '配置 WebdriverIO | 浏览器模式', - }, - { - text: 'Configuring Preview', - link: '/guide/browser/preview', - docFooterText: 'Configuring Preview | 浏览器模式', - }, - ], - }, - { - text: 'API', - collapsed: false, - items: [ - { - text: 'Context API', - link: '/guide/browser/context', - docFooterText: 'Context API | 浏览器模式', - }, - { - text: 'Interactivity API', - link: '/guide/browser/interactivity-api', - docFooterText: 'Interactivity API | 浏览器模式', - }, - { - text: 'Locators', - link: '/guide/browser/locators', - docFooterText: 'Locators | 浏览器模式', - }, - { - text: 'Assertion API', - link: '/guide/browser/assertion-api', - docFooterText: 'Assertion API | 浏览器模式', - }, - { - text: 'Commands API', - link: '/guide/browser/commands', - docFooterText: 'Commands API | 浏览器模式', - }, - ], - }, - { - text: '指南', - collapsed: false, - items: [ - { - text: '多环境配置', - link: '/guide/browser/multiple-setups', - docFooterText: '多环境配置 | 浏览器模式', - }, - { - text: '组件测试', - link: '/guide/browser/component-testing', - docFooterText: '组件测试 | 浏览器模式', - }, - { - text: '可视化回归测试', - link: '/guide/browser/visual-regression-testing', - docFooterText: '可视化回归测试 | 浏览器模式', - }, - { - text: '跟踪查看器', - link: '/guide/browser/trace-view', - docFooterText: '跟踪查看器 | 浏览器模式', - }, - ], - }, - { - items: [ - ...footer(), - { - text: 'Node API', - link: '/advanced/api/', - }, - ], - }, - ], - '/advanced': [ + { + text: `v${version}`, + items: [ { - text: 'API', - collapsed: false, items: [ { - text: 'Node API', - items: [ - { - text: '快速起步', - link: '/advanced/api/', - }, - { - text: 'Vitest', - link: '/advanced/api/vitest', - }, - { - text: 'TestProject', - link: '/advanced/api/test-project', - }, - { - text: 'TestSpecification', - link: '/advanced/api/test-specification', - }, - ], - }, - { - text: 'Test Task API', - items: [ - { - text: 'TestCase', - link: '/advanced/api/test-case', - }, - { - text: 'TestSuite', - link: '/advanced/api/test-suite', - }, - { - text: 'TestModule', - link: '/advanced/api/test-module', - }, - { - text: 'TestCollection', - link: '/advanced/api/test-collection', - }, - ], - }, - { - text: '插件 API', - link: '/advanced/api/plugin', + text: `v${version}`, + link: `https://github.com/vitest-dev/vitest/releases/tag/v${version}`, }, { - text: '运行器 API', - link: '/advanced/runner', + text: 'Releases Notes', + link: releases, }, { - text: '报告器 API', - link: '/advanced/api/reporters', + text: 'Contributing', + link: contributing, }, { - text: '任务元数据', - link: '/advanced/metadata', + text: 'Team', + link: '/team', }, ], }, { - text: '指南', - collapsed: false, items: [ { - text: '运行测试', - link: '/advanced/guide/tests', + text: 'unreleased', + link: 'https://main.vitest.dev/', }, { - text: '扩展报告器', - link: '/advanced/reporters', + text: 'v0.x', + link: 'https://v0.vitest.dev/', }, { - text: '自定义运行池', - link: '/advanced/pool', - }, - ], - }, - { - items: footer(), - }, - ], - '/team': [], - '/blog': [], - '/': [ - { - text: '简介', - collapsed: false, - items: introduction(), - }, - { - text: 'API', - collapsed: false, - items: api(), - }, - { - text: '指南', - collapsed: false, - items: guide(), - }, - { - items: [ - { - text: '浏览器模式', - link: '/guide/browser', + text: 'v1.x', + link: 'https://v1.vitest.dev/', }, { - text: 'Node API', - link: '/advanced/api', + text: 'v2.x', + link: 'https://v2.vitest.dev/', }, { - text: '测试框架对比', - link: '/guide/comparisons', + text: 'v3.x', + link: 'https://v3.vitest.dev/', }, ], }, ], }, - }, - pwa, - transformHead, - }), - ) -} - -function footer(): DefaultTheme.SidebarItem[] { - return [ - { - text: '配置索引', - link: '/config/', - }, - { - text: 'Test API', - link: '/api/', - }, - ] -} - -function introduction(): DefaultTheme.SidebarItem[] { - return [ - { - text: '为什么是vitest?', - link: '/guide/why', - }, - { - text: '快速起步', - link: '/guide/', - }, - { - text: '主要功能', - link: '/guide/features', - }, - { - text: '配置索引', - link: '/config/', - }, - ] -} - -function guide(): DefaultTheme.SidebarItem[] { - return [ - { - text: '命令行界面', - link: '/guide/cli', - }, - { - text: '测试筛选', - link: '/guide/filtering', - }, - { - text: '测试项目', - link: '/guide/projects', - }, - { - text: '报告器', - link: '/guide/reporters', - }, - { - text: '覆盖率', - link: '/guide/coverage', - }, - { - text: '快照', - link: '/guide/snapshot', - }, - { - text: '模拟对象', - link: '/guide/mocking', - collapsed: true, - items: [ - { - text: '模拟日期', - link: '/guide/mocking/dates', - }, - { - text: '模拟函数', - link: '/guide/mocking/functions', - }, - { - text: '模拟全局对象', - link: '/guide/mocking/globals', - }, - { - text: '模拟模块', - link: '/guide/mocking/modules', - }, - { - text: '模拟文件系统', - link: '/guide/mocking/file-system', - }, - { - text: '模拟请求', - link: '/guide/mocking/requests', - }, - { - text: '模拟计时器', - link: '/guide/mocking/timers', - }, - { - text: '模拟类', - link: '/guide/mocking/classes', - }, - ], - }, - { - text: '并行性', - link: '/guide/parallelism', - }, - { - text: '类型测试', - link: '/guide/testing-types', - }, - { - text: 'UI 模式', - link: '/guide/ui', - }, - { - text: '源码内联测试', - link: '/guide/in-source', - }, - { - text: '测试上下文', - link: '/guide/test-context', - }, - { - text: '测试环境', - link: '/guide/environment', - }, - { - text: '扩展断言', - link: '/guide/extending-matchers', - }, - { - text: 'IDE 插件', - link: '/guide/ide', - }, - { - text: '调试', - link: '/guide/debugging', - }, - { - text: '常见错误', - link: '/guide/common-errors', - }, - { - text: '迁移指南', - link: '/guide/migration', - collapsed: false, - items: [ - { - text: '迁移到 Vitest 4.0', - link: '/guide/migration#vitest-4', - }, - { - text: '从 Jest 迁移', - link: '/guide/migration#jest', - }, - ], - }, - { - text: '性能', - collapsed: false, - items: [ - { - text: '性能测试分析', - link: '/guide/profiling-test-performance', - }, - { - text: '性能优化', - link: '/guide/improving-performance', - }, ], - }, - ] -} -function api(): DefaultTheme.SidebarItem[] { - return [ - { - text: 'Test API', - link: '/api/', - }, - { - text: 'Mocks', - link: '/api/mock', - }, - { - text: 'Vi Utility', - link: '/api/vi', - }, - { - text: 'Expect', - link: '/api/expect', - }, - { - text: 'ExpectTypeOf', - link: '/api/expect-typeof', - }, - { - text: 'Assert', - link: '/api/assert', - }, - { - text: 'AssertType', - link: '/api/assert-type', + sidebar: { + '/config': [ + { + text: 'Config Reference', + collapsed: false, + items: [ + { + text: 'Config File', + link: '/config/', + }, + { + text: 'include', + link: '/config/include', + }, + { + text: 'exclude', + link: '/config/exclude', + }, + { + text: 'includeSource', + link: '/config/include-source', + }, + { + text: 'name', + link: '/config/name', + }, + { + text: 'server', + link: '/config/server', + }, + { + text: 'deps', + link: '/config/deps', + }, + { + text: 'runner', + link: '/config/runner', + }, + { + text: 'benchmark', + link: '/config/benchmark', + }, + { + text: 'alias', + link: '/config/alias', + }, + { + text: 'globals', + link: '/config/globals', + }, + { + text: 'environment', + link: '/config/environment', + }, + { + text: 'environmentOptions', + link: '/config/environmentoptions', + }, + { + text: 'watch', + link: '/config/watch', + }, + { + text: 'watchTriggerPatterns', + link: '/config/watchtriggerpatterns', + }, + { + text: 'root', + link: '/config/root', + }, + { + text: 'dir', + link: '/config/dir', + }, + { + text: 'reporters', + link: '/config/reporters', + }, + { + text: 'outputFile', + link: '/config/outputfile', + }, + { + text: 'pool', + link: '/config/pool', + }, + { + text: 'execArgv', + link: '/config/execargv', + }, + { + text: 'vmMemoryLimit', + link: '/config/vmmemorylimit', + }, + { + text: 'fileParallelism', + link: '/config/fileparallelism', + }, + { + text: 'maxWorkers', + link: '/config/maxworkers', + }, + { + text: 'testTimeout', + link: '/config/testtimeout', + }, + { + text: 'hookTimeout', + link: '/config/hooktimeout', + }, + { + text: 'teardownTimeout', + link: '/config/teardowntimeout', + }, + { + text: 'silent', + link: '/config/silent', + }, + { + text: 'setupFiles', + link: '/config/setupfiles', + }, + { + text: 'provide', + link: '/config/provide', + }, + { + text: 'globalSetup', + link: '/config/globalsetup', + }, + { + text: 'forceRerunTriggers', + link: '/config/forcereruntriggers', + }, + { + text: 'coverage', + link: '/config/coverage', + }, + { + text: 'testNamePattern', + link: '/config/testnamepattern', + }, + { + text: 'ui', + link: '/config/ui', + }, + { + text: 'open', + link: '/config/open', + }, + { + text: 'api', + link: '/config/api', + }, + { + text: 'clearMocks', + link: '/config/clearmocks', + }, + { + text: 'mockReset', + link: '/config/mockreset', + }, + { + text: 'restoreMocks', + link: '/config/restoremocks', + }, + { + text: 'unstubEnvs', + link: '/config/unstubenvs', + }, + { + text: 'unstubGlobals', + link: '/config/unstubglobals', + }, + { + text: 'snapshotFormat', + link: '/config/snapshotformat', + }, + { + text: 'snapshotSerializers', + link: '/config/snapshotserializers', + }, + { + text: 'resolveSnapshotPath', + link: '/config/resolvesnapshotpath', + }, + { + text: 'allowOnly', + link: '/config/allowonly', + }, + { + text: 'passWithNoTests', + link: '/config/passwithnotests', + }, + { + text: 'logHeapUsage', + link: '/config/logheapusage', + }, + { + text: 'css', + link: '/config/css', + }, + { + text: 'maxConcurrency', + link: '/config/maxconcurrency', + }, + { + text: 'cache', + link: '/config/cache', + }, + { + text: 'sequence', + link: '/config/sequence', + }, + { + text: 'typecheck', + link: '/config/typecheck', + }, + { + text: 'slowTestThreshold', + link: '/config/slowtestthreshold', + }, + { + text: 'chaiConfig', + link: '/config/chaiconfig', + }, + { + text: 'bail', + link: '/config/bail', + }, + { + text: 'retry', + link: '/config/retry', + }, + { + text: 'onConsoleLog', + link: '/config/onconsolelog', + }, + { + text: 'onStackTrace', + link: '/config/onstacktrace', + }, + { + text: 'onUnhandledError', + link: '/config/onunhandlederror', + }, + { + text: 'dangerouslyIgnoreUnhandled...', + link: '/config/dangerouslyignoreunhandlederrors', + }, + { + text: 'diff', + link: '/config/diff', + }, + { + text: 'fakeTimers', + link: '/config/faketimers', + }, + { + text: 'projects', + link: '/config/projects', + }, + { + text: 'isolate', + link: '/config/isolate', + }, + { + text: 'includeTaskLocation', + link: '/config/includetasklocation', + }, + { + text: 'snapshotEnvironment', + link: '/config/snapshotenvironment', + }, + { + text: 'env', + link: '/config/env', + }, + { + text: 'expect', + link: '/config/expect', + }, + { + text: 'printConsoleTrace', + link: '/config/printconsoletrace', + }, + { + text: 'attachmentsDir', + link: '/config/attachmentsdir', + }, + { + text: 'hideSkippedTests', + link: '/config/hideskippedtests', + }, + { + text: 'mode', + link: '/config/mode', + }, + { + text: 'expandSnapshotDiff', + link: '/config/expandsnapshotdiff', + }, + { + text: 'disableConsoleIntercept', + link: '/config/disableconsoleintercept', + }, + { + text: 'experimental', + link: '/config/experimental', + }, + ], + }, + { + text: 'Browser Mode', + collapsed: false, + items: [ + { + text: 'Providers', + collapsed: false, + items: [ + { + text: 'playwright', + link: '/config/browser/playwright', + }, + { + text: 'webdriverio', + link: '/config/browser/webdriverio', + }, + { + text: 'preview', + link: '/config/browser/preview', + }, + ], + }, + // { + // text: 'Render Function', + // collapsed: true, + // items: [ + // { + // text: 'react', + // link: '/config/browser/react', + // }, + // { + // text: 'vue', + // link: '/config/browser/vue', + // }, + // { + // text: 'svelte', + // link: '/config/browser/svelte', + // }, + // ], + // }, + { + text: 'browser.enabled', + link: '/config/browser/enabled', + }, + { + text: 'browser.instances', + link: '/config/browser/instances', + }, + { + text: 'browser.headless', + link: '/config/browser/headless', + }, + { + text: 'browser.isolate', + link: '/config/browser/isolate', + }, + { + text: 'browser.testerHtmlPath', + link: '/config/browser/testerhtmlpath', + }, + { + text: 'browser.api', + link: '/config/browser/api', + }, + { + text: 'browser.provider', + link: '/config/browser/provider', + }, + { + text: 'browser.ui', + link: '/config/browser/ui', + }, + { + text: 'browser.viewport', + link: '/config/browser/viewport', + }, + { + text: 'browser.locators', + link: '/config/browser/locators', + }, + { + text: 'browser.screenshotDirectory', + link: '/config/browser/screenshotdirectory', + }, + { + text: 'browser.screenshotFailures', + link: '/config/browser/screenshotfailures', + }, + { + text: 'browser.orchestratorScripts', + link: '/config/browser/orchestratorscripts', + }, + { + text: 'browser.commands', + link: '/config/browser/commands', + }, + { + text: 'browser.connectTimeout', + link: '/config/browser/connecttimeout', + }, + { + text: 'browser.trace', + link: '/config/browser/trace', + }, + { + text: 'browser.trackUnhandledErrors', + link: '/config/browser/trackunhandlederrors', + }, + { + text: 'browser.expect', + link: '/config/browser/expect', + }, + ], + }, + // { + // text: '@vitest/plugin-eslint', + // collapsed: true, + // items: [ + // { + // text: 'Lints', + // link: '/config/eslint', + // }, + // // TODO: generate + // { + // text: 'consistent-test-filename', + // link: '/config/eslint/consistent-test-filename', + // }, + // { + // text: 'consistent-test-it', + // link: '/config/eslint/consistent-test-it', + // }, + // ], + // }, + // { + // text: 'vscode', + // link: '/config/vscode', + // }, + ], + '/guide': [ + { + text: 'Introduction', + collapsed: false, + items: [ + { + text: 'Why Vitest', + link: '/guide/why', + }, + { + text: 'Getting Started', + link: '/guide/', + }, + { + text: 'Features', + link: '/guide/features', + }, + ], + }, + { + text: 'Browser Mode', + collapsed: false, + items: [ + { + text: 'Why Browser Mode', + link: '/guide/browser/why', + docFooterText: 'Why Browser Mode | Browser Mode', + }, + { + text: 'Getting Started', + link: '/guide/browser/', + docFooterText: 'Getting Started | Browser Mode', + }, + { + text: 'Multiple Setups', + link: '/guide/browser/multiple-setups', + docFooterText: 'Multiple Setups | Browser Mode', + }, + { + text: 'Component Testing', + link: '/guide/browser/component-testing', + docFooterText: 'Component Testing | Browser Mode', + }, + { + text: 'Visual Regression Testing', + link: '/guide/browser/visual-regression-testing', + docFooterText: 'Visual Regression Testing | Browser Mode', + }, + { + text: 'Trace View', + link: '/guide/browser/trace-view', + docFooterText: 'Trace View | Browser Mode', + }, + ], + }, + { + text: 'Guides', + collapsed: false, + items: [ + { + text: 'CLI', + link: '/guide/cli', + }, + { + text: 'Test Filtering', + link: '/guide/filtering', + }, + { + text: 'Test Context', + link: '/guide/test-context', + }, + { + text: 'Test Environment', + link: '/guide/environment', + }, + { + text: 'Snapshot', + link: '/guide/snapshot', + }, + { + text: 'Mocking', + link: '/guide/mocking', + collapsed: true, + items: [ + { + text: 'Mocking Dates', + link: '/guide/mocking/dates', + }, + { + text: 'Mocking Functions', + link: '/guide/mocking/functions', + }, + { + text: 'Mocking Globals', + link: '/guide/mocking/globals', + }, + { + text: 'Mocking Modules', + link: '/guide/mocking/modules', + }, + { + text: 'Mocking the File System', + link: '/guide/mocking/file-system', + }, + { + text: 'Mocking Requests', + link: '/guide/mocking/requests', + }, + { + text: 'Mocking Timers', + link: '/guide/mocking/timers', + }, + { + text: 'Mocking Classes', + link: '/guide/mocking/classes', + }, + ], + }, + { + text: 'Parallelism', + link: '/guide/parallelism', + }, + { + text: 'Test Projects', + link: '/guide/projects', + }, + { + text: 'Reporters', + link: '/guide/reporters', + }, + { + text: 'Coverage', + link: '/guide/coverage', + }, + { + text: 'Testing Types', + link: '/guide/testing-types', + }, + { + text: 'Vitest UI', + link: '/guide/ui', + }, + { + text: 'In-Source Testing', + link: '/guide/in-source', + }, + { + text: 'Test Annotations', + link: '/guide/test-annotations', + }, + { + text: 'Extending Matchers', + link: '/guide/extending-matchers', + }, + { + text: 'IDE Integration', + link: '/guide/ide', + }, + { + text: 'Debugging', + link: '/guide/debugging', + }, + { + text: 'Common Errors', + link: '/guide/common-errors', + }, + { + text: 'Migration Guide', + link: '/guide/migration', + collapsed: false, + items: [ + { + text: 'Migrating to Vitest 4.0', + link: '/guide/migration#vitest-4', + }, + { + text: 'Migrating from Jest', + link: '/guide/migration#jest', + }, + ], + }, + { + text: 'Performance', + collapsed: false, + items: [ + { + text: 'Profiling Test Performance', + link: '/guide/profiling-test-performance', + }, + { + text: 'Improving Performance', + link: '/guide/improving-performance', + }, + ], + }, + { + text: 'OpenTelemetry', + link: '/guide/open-telemetry', + }, + ], + }, + { + text: 'Advanced', + collapsed: true, + items: [ + { + text: 'Getting Started', + link: '/guide/advanced/', + }, + { + text: 'Running Tests via API', + link: '/guide/advanced/tests', + }, + { + text: 'Extending Reporters', + link: '/guide/advanced/reporters', + }, + { + text: 'Custom Pool', + link: '/guide/advanced/pool', + }, + ], + }, + { + items: [ + { + text: 'Recipes', + link: '/guide/recipes', + }, + { + text: 'Comparisons', + link: '/guide/comparisons', + }, + ], + }, + ], + '/api': [ + { + text: 'Test API Reference', + link: '/api/', + }, + { + text: 'Mocks', + link: '/api/mock', + }, + { + text: 'Vi Utility', + link: '/api/vi', + }, + { + text: 'Expect', + link: '/api/expect', + }, + { + text: 'ExpectTypeOf', + link: '/api/expect-typeof', + }, + { + text: 'Assert', + link: '/api/assert', + }, + { + text: 'AssertType', + link: '/api/assert-type', + }, + { + text: 'Browser Mode', + items: [ + { + text: 'Context', + link: '/api/browser/context', + }, + { + text: 'Interactivity', + link: '/api/browser/interactivity', + }, + { + text: 'Locators', + link: '/api/browser/locators', + }, + { + text: 'Assertions', + link: '/api/browser/assertions', + }, + { + text: 'Commands', + link: '/api/browser/commands', + }, + ], + }, + { + text: 'Advanced', + collapsed: true, + items: [ + { + text: 'Vitest', + link: '/api/advanced/vitest', + }, + { + text: 'TestProject', + link: '/api/advanced/test-project', + }, + { + text: 'TestSpecification', + link: '/api/advanced/test-specification', + }, + { + text: 'TestCase', + link: '/api/advanced/test-case', + }, + { + text: 'TestSuite', + link: '/api/advanced/test-suite', + }, + { + text: 'TestModule', + link: '/api/advanced/test-module', + }, + { + text: 'TestCollection', + link: '/api/advanced/test-collection', + }, + { + text: 'VitestPlugin', + link: '/api/advanced/plugin', + }, + { + text: 'VitestRunner', + link: '/api/advanced/runner', + }, + { + text: 'Reporter', + link: '/api/advanced/reporters', + }, + { + text: 'TaskMeta', + link: '/api/advanced/metadata', + }, + { + text: 'TestArtifact', + link: '/api/advanced/artifacts', + }, + ], + }, + // { + // text: 'Text Runner', + // collapsed: false, + // items: [ + // // TODO: generate + // { + // text: 'test', + // link: '/api/test', + // }, + // { + // text: 'describe', + // link: '/api/describe', + // }, + // { + // text: 'beforeEach', + // link: '/api/before-each', + // }, + // { + // text: 'afterEach', + // link: '/api/after-each', + // }, + // ], + // }, + // { + // text: 'Assertion API', + // collapsed: false, + // items: [ + // { + // text: 'expect', + // link: '/api/expect', + // }, + // { + // text: 'assert', + // link: '/api/assert', + // }, + // { + // text: 'expectTypeOf', + // link: '/api/expect-typeof', + // }, + // { + // text: 'assertType', + // link: '/api/assert-type', + // }, + // ], + // }, + // { + // text: 'Vi Utility API', + // collapsed: false, + // items: [ + // { + // text: 'Mock Modules', + // link: '/api/vi/mock-modiles', + // }, + // { + // text: 'Mock Functions', + // link: '/api/vi/mock-functions', + // }, + // { + // text: 'Mock Timers', + // link: '/api/vi/mock-timers', + // }, + // { + // text: 'Miscellaneous', + // link: '/api/vi/miscellaneous', + // }, + // ], + // }, + // { + // text: 'Browser Mode', + // collapsed: false, + // items: [ + // // TODO: generate + // { + // text: 'page', + // link: '/api/browser/page', + // }, + // { + // text: 'locators', + // link: '/api/browser/locators', + // }, + // ], + // }, + ], + }, }, - ] + pwa, + transformHead, + })) } diff --git a/.vitepress/contributors.ts b/.vitepress/contributors.ts index 766a5cd5..aa08dd69 100644 --- a/.vitepress/contributors.ts +++ b/.vitepress/contributors.ts @@ -164,47 +164,7 @@ const plainTeamEmeritiMembers: CoreTeam[] = [ }, ] -const plainTranslationTeamMembers: CoreTeam[] = [ - { - avatar: 'https://github.com/elonehoo.png', - name: 'Elone Hoo', - github: 'elonehoo', - desc: 'Vitest 中文文档发起者', - }, - { - avatar: 'https://github.com/watonyweng.png', - name: 'Wáng Wěi Tāo', - github: 'watonyweng', - }, - { - avatar: 'https://github.com/thinkasany.png', - name: 'thinkasany', - github: 'thinkasany', - desc: 'Vite & Vitest & antd 中文文档维护者', - }, - { - avatar: 'https://github.com/XieZongChen.png', - name: 'XieZongChen', - github: 'XieZongChen', - desc: 'Vitest 中文文档维护者', - }, - { - avatar: 'https://github.com/lxKylin.png', - name: 'Kylin', - github: 'lxKylin', - desc: 'Vite & Vitest 中文文档维护者', - }, - { - avatar: 'https://github.com/NoiseFan.png', - name: 'NoiseFan', - github: 'NoiseFan', - title: '开源爱好者', - desc: 'Vitest 中文文档维护者', - }, -] - const teamMembers = plainTeamMembers.map(tm => createLinks(tm)) const teamEmeritiMembers = plainTeamEmeritiMembers.map(tm => createLinks(tm)) -const translationTeamMembers = plainTranslationTeamMembers.map(tm => createLinks(tm)) -export { teamEmeritiMembers, teamMembers, translationTeamMembers } +export { teamEmeritiMembers, teamMembers } diff --git a/.vitepress/scripts/cli-generator.ts b/.vitepress/scripts/cli-generator.ts deleted file mode 100644 index bea3ade8..00000000 --- a/.vitepress/scripts/cli-generator.ts +++ /dev/null @@ -1,83 +0,0 @@ -import type { CLIOption, CLIOptions } from '../../../packages/vitest/src/node/cli/cli-config' -import { writeFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' -import { cliOptionsConfig } from '../../../packages/vitest/src/node/cli/cli-config' - -const docsDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..') -const cliTablePath = resolve(docsDir, './guide/cli-generated.md') - -const nonNullable = (value: T): value is NonNullable => value !== null && value !== undefined - -const skipCli = new Set([ - 'mergeReports', - 'changed', - 'shard', -]) - -const skipConfig = new Set([ - 'config', - 'api.port', - 'api.host', - 'api.strictPort', - 'coverage.watermarks.statements', - 'coverage.watermarks.lines', - 'coverage.watermarks.branches', - 'coverage.watermarks.functions', - 'coverage.thresholds.statements', - 'coverage.thresholds.branches', - 'coverage.thresholds.functions', - 'coverage.thresholds.lines', - 'standalone', - 'clearScreen', - 'configLoader', - 'color', - 'run', - 'hideSkippedTests', - 'dom', -]) - -function resolveOptions(options: CLIOptions, parentName?: string) { - return Object.entries(options).flatMap( - ([subcommandName, subcommandConfig]) => resolveCommand( - parentName ? `${parentName}.${subcommandName}` : subcommandName, - subcommandConfig, - ), - ).filter(nonNullable) -} - -function resolveCommand(name: string, config: CLIOption | null): any { - if (!config || skipCli.has(name)) { - return null - } - - let title = '`' - if (config.shorthand) { - title += `-${config.shorthand}, ` - } - title += `--${config.alias || name}` - if ('argument' in config) { - title += ` ${config.argument}` - } - title += '`' - if ('subcommands' in config && config.subcommands) { - return resolveOptions(config.subcommands, name) - } - - return { - title: name, - cli: title, - description: config.description, - } -} - -const options = resolveOptions(cliOptionsConfig) - -const template = options.map((option) => { - const title = option.title - const cli = option.cli - const config = skipConfig.has(title) ? '' : `[${title}](${title.includes('browser.') ? '/guide/browser/config' : '/config/'}#${title.toLowerCase().replace(/\./g, '-')})` - return `### ${title}\n\n- **CLI:** ${cli}\n${config ? `- **Config:** ${config}\n` : ''}\n${option.description}\n` -}).join('\n') - -writeFileSync(cliTablePath, template, 'utf-8') diff --git a/.vitepress/scripts/fetch-avatars.ts b/.vitepress/scripts/fetch-avatars.ts index eae810df..268b0b4a 100644 --- a/.vitepress/scripts/fetch-avatars.ts +++ b/.vitepress/scripts/fetch-avatars.ts @@ -1,11 +1,9 @@ -import { Buffer } from 'node:buffer' import { existsSync, promises as fsp } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join, resolve } from 'pathe' import { teamEmeritiMembers, teamMembers } from '../contributors' const docsDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..') - const dirAvatars = resolve(docsDir, 'public/user-avatars/') const dirSponsors = resolve(docsDir, 'public/sponsors/') @@ -19,7 +17,7 @@ async function download(url: string, fileName: string) { const image = await (await fetch(url)).arrayBuffer() await fsp.writeFile(fileName, Buffer.from(image)) } - catch { } + catch {} } async function fetchAvatars() { diff --git a/.vitepress/sponsors.ts b/.vitepress/sponsors.ts index 7bf17112..79fe4ab2 100644 --- a/.vitepress/sponsors.ts +++ b/.vitepress/sponsors.ts @@ -51,6 +51,11 @@ const vitestSponsors = { url: 'https://www.liminity.se/', img: '/liminity.svg', }, + { + name: 'Bytebase', + url: 'https://www.bytebase.com/', + img: '/bytebase.svg', + }, ], } satisfies Record diff --git a/.vitepress/style/main.css b/.vitepress/style/main.css index 251138d1..d46ffcd6 100644 --- a/.vitepress/style/main.css +++ b/.vitepress/style/main.css @@ -37,31 +37,37 @@ button:focus:not(:focus-visible) { html:not(.dark) .custom-block.tip code { color: var(--vitest-custom-block-tip-code-text) !important; } + html:not(.dark) .custom-block.info code { color: var(--vitest-custom-block-info-code-text) !important; } + .custom-block.tip a:hover, -.vp-doc .custom-block.tip a:hover > code { +.vp-doc .custom-block.tip a:hover>code { color: var(--vp-c-brand-1) !important; opacity: 1; } + .custom-block.info a:hover, -.vp-doc .custom-block.info a:hover > code { +.vp-doc .custom-block.info a:hover>code { color: var(--vp-c-brand-1) !important; opacity: 1; } + html:not(.dark) .custom-block.info a:hover, -html:not(.dark) .vp-doc .custom-block.info a:hover > code { +html:not(.dark) .vp-doc .custom-block.info a:hover>code { color: var(--vitest-custom-block-info-code-text) !important; opacity: 1; } + .custom-block.warning a:hover, -.vp-doc .custom-block.warning a:hover > code { +.vp-doc .custom-block.warning a:hover>code { color: var(--vp-c-warning-1) !important; opacity: 1; } + .custom-block.danger a:hover, -.vp-doc .custom-block.danger a:hover > code { +.vp-doc .custom-block.danger a:hover>code { color: var(--vp-c-danger-1) !important; opacity: 1; } @@ -70,6 +76,7 @@ html:not(.dark) .vp-doc .custom-block.info a:hover > code { :not(.dark) .title-icon { opacity: 1 !important; } + .dark .title-icon { opacity: 0.67 !important; } @@ -81,6 +88,7 @@ html:not(.dark) .vp-doc .custom-block.info a:hover > code { .vp-doc a { text-decoration-style: dotted; } + .custom-block a:focus, .custom-block a:active, .custom-block a:hover, @@ -92,7 +100,8 @@ html:not(.dark) .vp-doc .custom-block.info a:hover > code { text-decoration: underline; } -.vp-doc th, .vp-doc td { +.vp-doc th, +.vp-doc td { padding: 6px 10px; border: 1px solid #8882; } @@ -113,14 +122,17 @@ img.resizable-img { .VPTeamMembersItem.medium .profile .data .affiliation { min-height: unset; } + .VPTeamMembersItem.medium .profile .data .desc { min-height: unset; } + /* fix height ~ 2 lines of text: 3 cards per row */ @media (min-width: 648px) { .VPTeamMembersItem.medium .profile .data .affiliation { min-height: 4rem; } + .VPTeamMembersItem.medium .profile .data .desc { min-height: 4rem; } @@ -130,6 +142,7 @@ img.resizable-img { .VPTeamMembersItem.small .profile .data .affiliation { min-height: 3rem; } + .VPTeamMembersItem.small .profile .data .desc { min-height: 3rem; } @@ -139,33 +152,40 @@ img.resizable-img { .VPTeamMembersItem.small .profile .data .affiliation { min-height: 4rem; } + .VPTeamMembersItem.small .profile .data .desc { min-height: 4rem; } } + /* fix height ~ 3 lines of text: 3 cards per row */ @media (min-width: 815px) and (max-width: 875px) { .VPTeamMembersItem.small .profile .data .affiliation { min-height: 4rem; } + .VPTeamMembersItem.small .profile .data .desc { min-height: 4rem; } } + /* fix height ~ 3 lines of text: 2 cards per row */ @media (max-width: 612px) { .VPTeamMembersItem.small .profile .data .affiliation { min-height: 4rem; } + .VPTeamMembersItem.small .profile .data .desc { min-height: 4rem; } } + /* fix height: one card per row */ @media (max-width: 568px) { .VPTeamMembersItem.small .profile .data .affiliation { min-height: unset; } + .VPTeamMembersItem.small .profile .data .desc { min-height: unset; } @@ -176,3 +196,30 @@ img.resizable-img { transition: background-color 0.5s; display: inline-block; } + +/* credit goes to https://dylanatsmith.com/wrote/styling-the-kbd-element */ +html:not(.dark) kbd { + --kbd-color-background: #f7f7f7; + --kbd-color-border: #cbcccd; + --kbd-color-text: #222325; +} + +kbd { + --kbd-color-background: #898b90; + --kbd-color-border: #3d3e42; + --kbd-color-text: #222325; + + background-color: var(--kbd-color-background); + color: var(--kbd-color-text); + border-radius: 0.25rem; + border: 1px solid var(--kbd-color-border); + box-shadow: 0 2px 0 1px var(--kbd-color-border); + font-family: var(--font-family-sans-serif); + font-size: 0.75em; + line-height: 1; + min-width: 0.75rem; + text-align: center; + padding: 2px 5px; + position: relative; + top: -1px; +} \ No newline at end of file diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index 916db6de..92b2d06b 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -4,6 +4,9 @@ import { inBrowser } from 'vitepress' import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' import DefaultTheme from 'vitepress/theme' import { h } from 'vue' +import CRoot from '../components/CRoot.vue' +import Deprecated from '../components/Deprecated.vue' +import Experimental from '../components/Experimental.vue' import HomePage from '../components/HomePage.vue' import Version from '../components/Version.vue' import '../style/main.css' @@ -23,8 +26,30 @@ export default { 'home-features-after': () => h(HomePage), }) }, - enhanceApp({ app }) { + enhanceApp({ app, router }) { + router.onBeforeRouteChange = (to) => { + if (typeof location === 'undefined') { + return true + } + const url = new URL(to, location.href) + if (!url.hash) { + return true + } + if (url.pathname === '/config' || url.pathname === '/config/' || url.pathname === '/config.html') { + const [page, ...hash] = (url.hash.startsWith('#browser.') ? url.hash.slice(9) : url.hash.slice(1)).toLowerCase().split('-') + setTimeout(() => { router.go(`/config/${page}${hash.length ? `#${[page, ...hash].join('-')}` : ''}`) }) + return false + } + if (url.pathname === '/guide/browser/config' || url.pathname === '/guide/browser/config/' || url.pathname === '/guide/browser/config.html') { + const [page, ...hash] = url.hash.slice('#browser.'.length).toLowerCase().split('-') + setTimeout(() => { router.go(`/config/browser/${page}${hash.length ? `#${[page, ...hash].join('-')}` : ''}`) }) + return false + } + } app.component('Version', Version) + app.component('CRoot', CRoot) + app.component('Experimental', Experimental) + app.component('Deprecated', Deprecated) app.use(TwoslashFloatingVue) enhanceAppWithTabs(app) }, diff --git a/advanced/runner.md b/advanced/runner.md index 0311e34d..89bd9c00 100644 --- a/advanced/runner.md +++ b/advanced/runner.md @@ -31,7 +31,7 @@ export interface VitestRunner { * 这是在实际运行测试函数之前被调用的。 * 此时已经有了带有 "state" 和 "startTime" 属性的 "result" 对象。 */ - onBeforeTryTask?: (test: Test, options: { retry: number, repeats: number }) => unknown + onBeforeTryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown /** * 这是在结果和状态都被设置之后被调用的。 */ @@ -40,12 +40,12 @@ export interface VitestRunner { * 这是在运行测试函数后立即被调用的。此时还没有新的状态。 * 如果测试函数抛出异常,将不会调用此方法。 */ - onAfterTryTask?: (test: Test, options: { retry: number, repeats: number }) => unknown + onAfterTryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown /** * 在重试结果确定后调用。与 `onAfterTryTask` 不同,此时测试已进入新的状态, * 并且所有的 `after` 钩子此时也已被执行。 */ - onAfterRetryTask?: (test: Test, options: { retry: number, repeats: number }) => unknown + onAfterRetryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown /** * 这是在运行单个测试套件之前被调用的,此时还没有测试结果。 diff --git a/api/advanced/artifacts.md b/api/advanced/artifacts.md new file mode 100644 index 00000000..67b121f9 --- /dev/null +++ b/api/advanced/artifacts.md @@ -0,0 +1,176 @@ +--- +outline: deep +title: Test Artifacts +--- + +# Test Artifacts 4.0.11 + +::: warning +This is an advanced API. As a user, you most likely want to use [test annotations](/guide/test-annotations) to add notes or context to your tests instead. This is primarily used internally and by library authors. +::: + +Test artifacts allow attaching or recording structured data, files, or metadata during test execution. This is a low-level feature primarily designed for: + +- Internal use ([`annotate`](/guide/test-annotations) is built on top of the artifact system) +- Framework authors creating custom testing tools on top of Vitest + +Each artifact includes: + +- A type discriminator which is a unique identifier for the artifact type +- Custom data, can be any relevant information +- Optional attachments, either files or inline content associated with the artifact +- A source code location indicating where the artifact was created + +Vitest automatically manages attachment serialization (files are copied to [`attachmentsDir`](/config/#attachmentsdir)) and injects source location metadata, so you can focus on the data you want to record. All artifacts **must** extend from [`TestArtifactBase`](#testartifactbase) and all attachments from [`TestAttachment`](#testattachment) to be correctly handled internally. + +## API + +### `recordArtifact` {#recordartifact} + +::: warning +`recordArtifact` is an experimental API. Breaking changes might not follow SemVer, please pin Vitest's version when using it. + +The API surface may change based on feedback. We encourage you to try it out and share your experience with the team. +::: + +```ts +function recordArtifact(task: Test, artifact: Artifact): Promise +``` + +The `recordArtifact` function records an artifact during test execution and returns it. It expects a [task](/api/advanced/runner#tasks) as the first parameter and an object assignable to [`TestArtifact`](#testartifact) as the second. + +This function has to be used within a test, and the test has to still be running. Recording after test completion will throw an error. + +When an artifact is recorded on a test, it emits an `onTestArtifactRecord` runner event and a [`onTestCaseArtifactRecord` reporter event](/api/advanced/reporters#ontestcaseartifactrecord). To retrieve recorded artifacts from a test case, use the [`artifacts()`](/api/advanced/test-case#artifacts) method. + +Note: annotations, [even though they're built on top of this feature](#relationship-with-annotations), won't appear in the `task.artifacts` array for backwards compatibility reasons until the next major version. + +### `TestArtifact` + +The `TestArtifact` type is a union containing all artifacts Vitest can produce, including custom ones. All artifacts extend from [`TestArtifactBase`](#testartifactbase) + +### `TestArtifactBase` {#testartifactbase} + +```ts +export interface TestArtifactBase { + /** File or data attachments associated with this artifact */ + attachments?: TestAttachment[] + /** Source location where this artifact was created */ + location?: TestArtifactLocation +} +``` + +The `TestArtifactBase` interface is the base for all test artifacts. + +Extend this interface when creating custom test artifacts. Vitest automatically manages the `attachments` array and injects the `location` property to indicate where the artifact was created in your test code. + +### `TestAttachment` + +```ts +export interface TestAttachment { + /** MIME type of the attachment (e.g., 'image/png', 'text/plain') */ + contentType?: string + /** File system path to the attachment */ + path?: string + /** Inline attachment content as a string or raw binary data */ + body?: string | Uint8Array +} +``` + +The `TestAttachment` interface represents a file or data attachment associated with a test artifact. + +Attachments can be either file-based (via `path`) or inline content (via `body`). The `contentType` helps consumers understand how to interpret the attachment data. + +### `TestArtifactLocation` + +```ts +export interface TestArtifactLocation { + /** Line number in the source file (1-indexed) */ + line: number + /** Column number in the line (1-indexed) */ + column: number + /** Path to the source file */ + file: string +} +``` + +The `TestArtifactLocation` interface represents the source code location information for a test artifact. It indicates where in the source code the artifact originated from. + +### `TestArtifactRegistry` + +The `TestArtifactRegistry` interface is a registry for custom test artifact types. + +Augmenting this interface using [TypeScript's module augmentation feature](https://typescriptlang.org/docs/handbook/declaration-merging#module-augmentation) allows registering custom artifact types that tests can produce. + +Each custom artifact should extend [`TestArtifactBase`](#testartifactbase) and include a unique `type` discriminator property. + +Here are a few guidelines or best practices to follow: + +- Try using a `Symbol` as the **registry key** to guarantee uniqueness +- The `type` property should follow the pattern `'package-name:artifact-name'`, **`'internal:'` is a reserved prefix** +- Use `attachments` to include files or data; extend [`TestAttachment`](#testattachment) for custom metadata +- `location` property is automatically injected + +## Custom Artifacts + +To use and manage artifacts in a type-safe manner, you need to create its type and register it: + +```ts +import type { TestArtifactBase, TestAttachment } from 'vitest' + +interface A11yReportAttachment extends TestAttachment { + contentType: 'text/html' + path: string +} + +interface AccessibilityArtifact extends TestArtifactBase { + type: 'a11y:report' + passed: boolean + wcagLevel: 'A' | 'AA' | 'AAA' + attachments: [A11yReportAttachment] +} + +const a11yReportKey = Symbol('report') + +declare module 'vitest' { + interface TestArtifactRegistry { + [a11yReportKey]: AccessibilityArtifact + } +} +``` + +As long as the types are assignable to their bases and don't have errors, everything should work fine and you should be able to record artifacts using [`recordArtifact`](#recordartifact): + +```ts +async function toBeAccessible( + this: MatcherState, + actual: Element, + wcagLevel: 'A' | 'AA' | 'AAA' = 'AA' +): AsyncExpectationResult { + const report = await runAccessibilityAudit(actual, wcagLevel) + + await recordArtifact(this.task, { + type: 'a11y:report', + passed: report.violations.length === 0, + wcagLevel, + attachments: [{ + contentType: 'text/html', + path: report.path, + }], + }) + + return { + pass: violations.length === 0, + message: () => `Found ${report.violations.length} accessibility violation(s)` + } +} +``` + +## Relationship with Annotations + +Test annotations are built on top of the artifact system. When using annotations in tests, they create `internal:annotation` artifacts under the hood. However, annotations are: + +- Simpler to use +- Designed for end-users, not developers + +Use annotations if you just want to add notes to your tests. Use artifacts if you need custom data. diff --git a/api/advanced/metadata.md b/api/advanced/metadata.md new file mode 100644 index 00000000..43dd41a0 --- /dev/null +++ b/api/advanced/metadata.md @@ -0,0 +1,68 @@ +# Task Metadata advanced + +If you are developing a custom reporter or using Vitest Node.js API, you might find it useful to pass data from tests that are being executed in various contexts to your reporter or custom Vitest handler. + +To accomplish this, relying on the [test context](/guide/test-context) is not feasible since it cannot be serialized. However, with Vitest, you can utilize the `meta` property available on every task (suite or test) to share data between your tests and the Node.js process. It's important to note that this communication is one-way only, as the `meta` property can only be modified from within the test context. Any changes made within the Node.js context will not be visible in your tests. + +You can populate `meta` property on test context or inside `beforeAll`/`afterAll` hooks for suite tasks. + +```ts +afterAll((suite) => { + suite.meta.done = true +}) + +test('custom', ({ task }) => { + task.meta.custom = 'some-custom-handler' +}) +``` + +Once a test is completed, Vitest will send a task including the result and `meta` to the Node.js process using RPC, and then report it in `onTestCaseResult` and other hooks that have access to tasks. To process this test case, you can utilize the `onTestCaseResult` method available in your reporter implementation: + +```ts [custom-reporter.js] +import type { Reporter, TestCase, TestModule } from 'vitest/node' + +export default { + onTestCaseResult(testCase: TestCase) { + // custom === 'some-custom-handler' ✅ + const { custom } = testCase.meta() + }, + onTestRunEnd(testModule: TestModule) { + testModule.meta().done === true + testModule.children.at(0).meta().custom === 'some-custom-handler' + } +} satisfies Reporter +``` + +::: danger BEWARE +Vitest uses different methods to communicate with the Node.js process. + +- If Vitest runs tests inside worker threads, it will send data via [message port](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort) +- If Vitest uses child process, the data will be send as a serialized Buffer via [`process.send`](https://nodejs.org/api/process.html#processsendmessage-sendhandle-options-callback) API +- If Vitest runs tests in the browser, the data will be stringified using [flatted](https://www.npmjs.com/package/flatted) package + +This property is also present on every test in the `json` reporter, so make sure that data can be serialized into JSON. + +Also, make sure you serialize [Error properties](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#error_types) before you set them. +::: + +You can also get this information from Vitest state when tests finished running: + +```ts +const vitest = await createVitest('test') +const { testModules } = await vitest.start() + +const testModule = testModules[0] +testModule.meta().done === true +testModule.children.at(0).meta().custom === 'some-custom-handler' +``` + +It's also possible to extend type definitions when using TypeScript: + +```ts +declare module 'vitest' { + interface TaskMeta { + done?: boolean + custom?: string + } +} +``` diff --git a/api/advanced/plugin.md b/api/advanced/plugin.md new file mode 100644 index 00000000..6308bf6e --- /dev/null +++ b/api/advanced/plugin.md @@ -0,0 +1,173 @@ +--- +title: Plugin API +outline: deep +--- + +# Plugin API 3.1.0 {#plugin-api} + +::: warning +This is an advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. + +This guide assumes you know how to work with [Vite plugins](https://vite.dev/guide/api-plugin.html). +::: + +Vitest supports a `configureVitest` [plugin](https://vite.dev/guide/api-plugin.html) hook hook since version 3.1. + +::: code-group +```ts [only vitest] +import type { Vite, VitestPluginContext } from 'vitest/node' + +export function plugin(): Vite.Plugin { + return { + name: 'vitest:my-plugin', + configureVitest(context: VitestPluginContext) { + // ... + } + } +} +``` +```ts [vite and vitest] +/// + +import type { Plugin } from 'vite' + +export function plugin(): Plugin { + return { + name: 'vitest:my-plugin', + transform() { + // ... + }, + configureVitest(context) { + // ... + } + } +} +``` +::: + +::: tip TypeScript +Vitest re-exports all Vite type-only imports via a `Vite` namespace, which you can use to keep your versions in sync. However, if you are writing a plugin for both Vite and Vitest, you can continue using the `Plugin` type from the `vite` entrypoint. Just make sure you have `vitest/config` referenced somewhere so that `configureVitest` is augmented correctly: + +```ts +/// +``` +::: + +Unlike [`reporter.onInit`](/api/advanced/reporters#oninit), this hooks runs early in Vitest lifecycle allowing you to make changes to configuration like `coverage` and `reporters`. A more notable change is that you can manipulate the global config from a [test project](/guide/projects) if your plugin is defined in the project and not in the global config. + +## Context + +### project + +The current [test project](./test-project) that the plugin belongs to. + +::: warning Browser Mode +Note that if you are relying on a browser feature, the `project.browser` field is not set yet. Use [`reporter.onBrowserInit`](./reporters#onbrowserinit) event instead. +::: + +### vitest + +The global [Vitest](./vitest) instance. You can change the global configuration by directly mutating the `vitest.config` property: + +```ts +vitest.config.coverage.enabled = false +vitest.config.reporters.push([['my-reporter', {}]]) +``` + +::: warning Config is Resolved +Note that Vitest already resolved the config, so some types might be different from the usual user configuration. This also means that some properties will not be resolved again, like `setupFile`. If you are adding new files, make sure to resolve it first. + +At this point reporters are not created yet, so modifying `vitest.reporters` will have no effect because it will be overwritten. If you need to inject your own reporter, modify the config instead. +::: + +### injectTestProjects + +```ts +function injectTestProjects( + config: TestProjectConfiguration | TestProjectConfiguration[] +): Promise +``` + +This methods accepts a config glob pattern, a filepath to the config or an inline configuration. It returns an array of resolved [test projects](./test-project). + +```ts +// inject a single project with a custom alias +const newProjects = await injectTestProjects({ + // you can inherit the current project config by referencing `extends` + // note that you cannot have a project with the name that already exists, + // so it's a good practice to define a custom name + extends: project.vite.config.configFile, + test: { + name: 'my-custom-alias', + alias: { + customAlias: resolve('./custom-path.js'), + }, + }, +}) +``` + +::: warning Projects are Filtered +Vitest filters projects during the config resolution, so if the user defined a filter, injected project might not be resolved unless it [matches the filter](./vitest#matchesprojectfilter). You can update the filter via the `vitest.config.project` option to always include your test project: + +```ts +vitest.config.project.push('my-project-name') +``` + +Note that this will only affect projects injected with [`injectTestProjects`](#injecttestprojects) method. +::: + +::: tip Referencing the Current Config +If you want to keep the user configuration, you can specify the `extends` property. All other properties will be merged with the user defined config. + +The project's `configFile` can be accessed in Vite's config: `project.vite.config.configFile`. + +Note that this will also inherit the `name` - Vitest doesn't allow multiple projects with the same name, so this will throw an error. Make sure you specified a different name. You can access the current name via the `project.name` property and all used names are available in the `vitest.projects` array. +::: + +### experimental_defineCacheKeyGenerator 4.0.11 {#definecachekeygenerator} + +```ts +interface CacheKeyIdGeneratorContext { + environment: DevEnvironment + id: string + sourceCode: string +} + +function experimental_defineCacheKeyGenerator( + callback: (context: CacheKeyIdGeneratorContext) => string | undefined | null | false +): void +``` + +Define a generator that will be applied before hashing the cache key. + +Use this to make sure Vitest generates correct hash. It is a good idea to define this function if your plugin can be registered with different options. + +This is called only if [`experimental.fsModuleCache`](/config/experimental#experimental-fsmodulecache) is defined. + +```ts +interface PluginOptions { + replacePropertyKey: string + replacePropertyValue: string +} + +export function plugin(options: PluginOptions) { + return { + name: 'plugin-that-replaces-property', + transform(code) { + return code.replace( + options.replacePropertyKey, + options.replacePropertyValue + ) + }, + configureVitest({ experimental_defineCacheKeyGenerator }) { + experimental_defineCacheKeyGenerator(() => { + // since these options affect the transform result, + // return them together as a unique string + return options.replacePropertyKey + options.replacePropertyValue + }) + } + } +} +``` + +If `false` is returned, the module will not be cached on the file system. diff --git a/api/advanced/reporters.md b/api/advanced/reporters.md new file mode 100644 index 00000000..9060c9b8 --- /dev/null +++ b/api/advanced/reporters.md @@ -0,0 +1,350 @@ +# Reporters + +::: warning +This is an advanced API. If you just want to configure built-in reporters, read the ["Reporters"](/guide/reporters) guide. +::: + +Vitest has its own test run lifecycle. These are represented by reporter's methods: + +- [`onInit`](#oninit) +- [`onTestRunStart`](#ontestrunstart) + - [`onTestModuleQueued`](#ontestmodulequeued) + - [`onTestModuleCollected`](#ontestmodulecollected) + - [`onTestModuleStart`](#ontestmodulestart) + - [`onTestSuiteReady`](#ontestsuiteready) + - [`onHookStart(beforeAll)`](#onhookstart) + - [`onHookEnd(beforeAll)`](#onhookend) + - [`onTestCaseReady`](#ontestcaseready) + - [`onTestAnnotate`](#ontestannotate) 3.2.0 + - [`onTestCaseArtifactRecord`](#ontestcaseartifactrecord) 4.0.11 + - [`onHookStart(beforeEach)`](#onhookstart) + - [`onHookEnd(beforeEach)`](#onhookend) + - [`onHookStart(afterEach)`](#onhookstart) + - [`onHookEnd(afterEach)`](#onhookend) + - [`onTestCaseResult`](#ontestcaseresult) + - [`onHookStart(afterAll)`](#onhookstart) + - [`onHookEnd(afterAll)`](#onhookend) + - [`onTestSuiteResult`](#ontestsuiteresult) + - [`onTestModuleEnd`](#ontestmoduleend) + - [`onCoverage`](#oncoverage) +- [`onTestRunEnd`](#ontestrunend) + +Tests and suites within a single module will be reported in order unless they were skipped. All skipped tests are reported at the end of suite/module. + +Note that since test modules can run in parallel, Vitest will report them in parallel. + +This guide lists all supported reporter methods. However, don't forget that instead of creating your own reporter, you can [extend existing one](/guide/advanced/reporters) instead: + +```ts [custom-reporter.js] +import { BaseReporter } from 'vitest/reporters' + +export default class CustomReporter extends BaseReporter { + onTestRunEnd(testModules, errors) { + console.log(testModule.length, 'tests finished running') + super.onTestRunEnd(testModules, errors) + } +} +``` + +## onInit + +```ts +function onInit(vitest: Vitest): Awaitable +``` + +This method is called when [Vitest](/api/advanced/vitest) was initiated or started, but before the tests were filtered. + +::: info +Internally this method is called inside [`vitest.start`](/api/advanced/vitest#start), [`vitest.init`](/api/advanced/vitest#init) or [`vitest.mergeReports`](/api/advanced/vitest#mergereports). If you are using programmatic API, make sure to call either one depending on your needs before calling [`vitest.runTestSpecifications`](/api/advanced/vitest#runtestspecifications), for example. Built-in CLI will always run methods in correct order. +::: + +Note that you can also get access to `vitest` instance from test cases, suites and test modules via a [`project`](/api/advanced/test-project) property, but it might also be useful to store a reference to `vitest` in this method. + +::: details Example +```ts +import type { Reporter, TestSpecification, Vitest } from 'vitest/node' + +class MyReporter implements Reporter { + private vitest!: Vitest + + onInit(vitest: Vitest) { + this.vitest = vitest + } + + onTestRunStart(specifications: TestSpecification[]) { + console.log( + specifications.length, + 'test files will run in', + this.vitest.config.root, + ) + } +} + +export default new MyReporter() +``` +::: + +## onBrowserInit {#onbrowserinit} + +```ts +function onBrowserInit(project: TestProject): Awaitable +``` + +This method is called when the browser instance is initiated. It receives an instance of the project for which the browser is initiated. `project.browser` will always be defined when this method is called. + +## onTestRunStart + +```ts +function onTestRunStart( + specifications: TestSpecification[] +): Awaitable +``` + +This method is called when a new test run has started. It receives an array of [test specifications](/api/advanced/test-specification) scheduled to run. This array is readonly and available only for information purposes. + +If Vitest didn't find any test files to run, this event will be invoked with an empty array, and then [`onTestRunEnd`](#ontestrunend) will be called immediately after. + +::: details Example +```ts +import type { Reporter, TestSpecification } from 'vitest/node' + +class MyReporter implements Reporter { + onTestRunStart(specifications: TestSpecification[]) { + console.log(specifications.length, 'test files will run') + } +} + +export default new MyReporter() +``` +::: + +::: tip DEPRECATION NOTICE +This method was added in Vitest 3, replacing `onPathsCollected` and `onSpecsCollected`, both of which are now deprecated. +::: + +## onTestRunEnd + +```ts +function onTestRunEnd( + testModules: ReadonlyArray, + unhandledErrors: ReadonlyArray, + reason: TestRunEndReason +): Awaitable +``` + +This method is called after all tests have finished running and the coverage merged all reports, if it's enabled. Note that you can get the coverage information in [`onCoverage`](#oncoverage) hook. + +It receives a readonly list of test modules. You can iterate over it via a [`testModule.children`](/api/advanced/test-collection) property to report the state and errors, if any. + +The second argument is a readonly list of unhandled errors that Vitest wasn't able to attribute to any test. These can happen outside of the test run because of an error in a plugin, or inside the test run as a side-effect of a non-awaited function (for example, a timeout that threw an error after the test has finished running). + +The third argument indicated why the test run was finished: + +- `passed`: test run was finished normally and there are no errors +- `failed`: test run has at least one error (due to a syntax error during collection or an actual error during test execution) +- `interrupted`: test was interrupted by [`vitest.cancelCurrentRun`](/api/advanced/vitest#cancelcurrentrun) call or `Ctrl+C` was pressed in the terminal (note that it's still possible to have failed tests in this case) + +If Vitest didn't find any test files to run, this event will be invoked with empty arrays of modules and errors, and the state will depend on the value of [`config.passWithNoTests`](/config/#passwithnotests). + +::: details Example +```ts +import type { + Reporter, + SerializedError, + TestModule, + TestRunEndReason, + TestSpecification +} from 'vitest/node' + +class MyReporter implements Reporter { + onTestRunEnd( + testModules: ReadonlyArray, + unhandledErrors: ReadonlyArray, + reason: TestRunEndReason, + ) { + if (reason === 'passed') { + testModules.forEach(module => console.log(module.moduleId, 'succeeded')) + } + else if (reason === 'failed') { + // note that this will skip possible errors in suites + // you can get them from testSuite.errors() + for (const testCase of testModules.children.allTests()) { + if (testCase.result().state === 'failed') { + console.log(testCase.fullName, 'in', testCase.module.moduleId, 'failed') + console.log(testCase.result().errors) + } + } + } + else { + console.log('test run was interrupted, skipping report') + } + } +} + +export default new MyReporter() +``` +::: + +::: tip DEPRECATION NOTICE +This method was added in Vitest 3, replacing `onFinished`, which is now deprecated. +::: + +## onCoverage + +```ts +function onCoverage(coverage: unknown): Awaitable +``` + +This hook is called after coverage results have been processed. Coverage provider's reporters are called after this hook. The typings of `coverage` depends on the `coverage.provider`. For Vitest's default built-in providers you can import the types from `istanbul-lib-coverage` package: + +```ts +import type { CoverageMap } from 'istanbul-lib-coverage' + +declare function onCoverage(coverage: CoverageMap): Awaitable +``` + +If Vitest didn't perform any coverage, this hook is not called. + +## onTestModuleQueued + +```ts +function onTestModuleQueued(testModule: TestModule): Awaitable +``` + +This method is called right before Vitest imports the setup file and the test module itself. This means that `testModule` will have no [`children`](/api/advanced/test-suite#children) yet, but you can start reporting it as the next test to run. + +## onTestModuleCollected + +```ts +function onTestModuleCollected(testModule: TestModule): Awaitable +``` + +This method is called when all tests inside the file were collected, meaning [`testModule.children`](/api/advanced/test-suite#children) collection is populated, but tests don't have any results yet. + +## onTestModuleStart + +```ts +function onTestModuleStart(testModule: TestModule): Awaitable +``` + +This method is called right after [`onTestModuleCollected`](#ontestmodulecollected) unless Vitest runs in collection mode ([`vitest.collect()`](/api/advanced/vitest#collect) or `vitest collect` in the CLI), in this case it will not be called at all because there are no tests to run. + +## onTestModuleEnd + +```ts +function onTestModuleEnd(testModule: TestModule): Awaitable +``` + +This method is called when every test in the module finished running. This means, every test inside [`testModule.children`](/api/advanced/test-suite#children) will have a `test.result()` that is not equal to `pending`. + +## onHookStart + +```ts +function onHookStart(context: ReportedHookContext): Awaitable +``` + +This method is called when any of these hooks have started running: + +- `beforeAll` +- `afterAll` +- `beforeEach` +- `afterEach` + +If `beforeAll` or `afterAll` are started, the `entity` will be either [`TestSuite`](/api/advanced/test-suite) or [`TestModule`](/api/advanced/test-module). + +If `beforeEach` or `afterEach` are started, the `entity` will always be [`TestCase`](/api/advanced/test-case). + +::: warning +`onHookStart` method will not be called if the hook did not run during the test run. +::: + +## onHookEnd + +```ts +function onHookEnd(context: ReportedHookContext): Awaitable +``` + +This method is called when any of these hooks have finished running: + +- `beforeAll` +- `afterAll` +- `beforeEach` +- `afterEach` + +If `beforeAll` or `afterAll` have finished, the `entity` will be either [`TestSuite`](/api/advanced/test-suite) or [`TestModule`](/api/advanced/test-module). + +If `beforeEach` or `afterEach` have finished, the `entity` will always be [`TestCase`](/api/advanced/test-case). + +::: warning +`onHookEnd` method will not be called if the hook did not run during the test run. +::: + +## onTestSuiteReady + +```ts +function onTestSuiteReady(testSuite: TestSuite): Awaitable +``` + +This method is called before the suite starts to run its tests. This method is also called if the suite was skipped. + +If the file doesn't have any suites, this method will not be called. Consider using `onTestModuleStart` to cover this use case. + +## onTestSuiteResult + +```ts +function onTestSuiteResult(testSuite: TestSuite): Awaitable +``` + +This method is called after the suite has finished running tests. This method is also called if the suite was skipped. + +If the file doesn't have any suites, this method will not be called. Consider using `onTestModuleEnd` to cover this use case. + +## onTestCaseReady + +```ts +function onTestCaseReady(testCase: TestCase): Awaitable +``` + +This method is called before the test starts to run or it was skipped. Note that `beforeEach` and `afterEach` hooks are considered part of the test because they can influence the result. + +::: warning +Notice that it's possible to have [`testCase.result()`](/api/advanced/test-case#result) with `passed` or `failed` state already when `onTestCaseReady` is called. This can happen if test was running too fast and both `onTestCaseReady` and `onTestCaseResult` were scheduled to run in the same microtask. +::: + +## onTestCaseResult + +```ts +function onTestCaseResult(testCase: TestCase): Awaitable +``` + +This method is called when the test has finished running or was just skipped. Note that this will be called after the `afterEach` hook is finished, if there are any. + +At this point, [`testCase.result()`](/api/advanced/test-case#result) will have non-pending state. + +## onTestAnnotate 3.2.0 {#ontestannotate} + +```ts +function onTestAnnotate( + testCase: TestCase, + annotation: TestAnnotation, +): Awaitable +``` + +The `onTestAnnotate` hook is associated with the [`context.annotate`](/guide/test-context#annotate) method. When `annotate` is invoked, Vitest serialises it and sends the same attachment to the main thread where reporter can interact with it. + +If the path is specified, Vitest stores it in a separate directory (configured by [`attachmentsDir`](/config/#attachmentsdir)) and modifies the `path` property to reference it. + +## onTestCaseArtifactRecord 4.0.11 {#ontestcaseartifactrecord} + +```ts +function onTestCaseArtifactRecord( + testCase: TestCase, + artifact: TestArtifact, +): Awaitable +``` + +The `onTestCaseArtifactRecord` hook is associated with the [`recordArtifact`](/api/advanced/artifacts#recordartifact) utility. When `recordArtifact` is invoked, Vitest serialises it and sends the same attachment to the main thread where reporter can interact with it. + +If the path is specified, Vitest stores it in a separate directory (configured by [`attachmentsDir`](/config/#attachmentsdir)) and modifies the `path` property to reference it. + +Note: annotations, [even though they're built on top of this feature](/api/advanced/artifacts#relationship-with-annotations), won't hit this hook and won't appear in the `task.artifacts` array for backwards compatibility reasons until the next major version. diff --git a/api/advanced/runner.md b/api/advanced/runner.md new file mode 100644 index 00000000..4b7d3fd4 --- /dev/null +++ b/api/advanced/runner.md @@ -0,0 +1,338 @@ +# Runner API advanced + +::: warning +This is advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. +::: + +You can specify a path to your test runner with the `runner` option in your configuration file. This file should have a default export with a class constructor implementing these methods: + +```ts +export interface VitestRunner { + /** + * First thing that's getting called before actually collecting and running tests. + */ + onBeforeCollect?: (paths: string[]) => unknown + /** + * Called after collecting tests and before "onBeforeRun". + */ + onCollected?: (files: File[]) => unknown + + /** + * Called when test runner should cancel next test runs. + * Runner should listen for this method and mark tests and suites as skipped in + * "onBeforeRunSuite" and "onBeforeRunTask" when called. + */ + onCancel?: (reason: CancelReason) => unknown + + /** + * Called before running a single test. Doesn't have "result" yet. + */ + onBeforeRunTask?: (test: Test) => unknown + /** + * Called before actually running the test function. Already has "result" with "state" and "startTime". + */ + onBeforeTryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown + /** + * Called after result and state are set. + */ + onAfterRunTask?: (test: Test) => unknown + /** + * Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws. + */ + onAfterTryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown + /** + * Called after the retry resolution happend. Unlike `onAfterTryTask`, the test now has a new state. + * All `after` hooks were also called by this point. + */ + onAfterRetryTask?: (test: Test, options: { retry: number; repeats: number }) => unknown + + /** + * Called before running a single suite. Doesn't have "result" yet. + */ + onBeforeRunSuite?: (suite: Suite) => unknown + /** + * Called after running a single suite. Has state and result. + */ + onAfterRunSuite?: (suite: Suite) => unknown + + /** + * If defined, will be called instead of usual Vitest suite partition and handling. + * "before" and "after" hooks will not be ignored. + */ + runSuite?: (suite: Suite) => Promise + /** + * If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function. + * "before" and "after" hooks will not be ignored. + */ + runTask?: (test: TaskPopulated) => Promise + + /** + * Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests. + */ + onTaskUpdate?: (task: [string, TaskResult | undefined, TaskMeta | undefined][]) => Promise + + /** + * Called before running all tests in collected paths. + */ + onBeforeRunFiles?: (files: File[]) => unknown + /** + * Called right after running all tests in collected paths. + */ + onAfterRunFiles?: (files: File[]) => unknown + /** + * Called when new context for a test is defined. Useful, if you want to add custom properties to the context. + * If you only want to define custom context with a runner, consider using "beforeAll" in "setupFiles" instead. + */ + extendTaskContext?: (context: TestContext) => TestContext + /** + * Called when certain files are imported. Can be called in two situations: to collect tests and to import setup files. + */ + importFile: (filepath: string, source: VitestRunnerImportSource) => unknown + /** + * Function that is called when the runner attempts to get the value when `test.extend` is used with `{ injected: true }` + */ + injectValue?: (key: string) => unknown + /** + * Publicly available configuration. + */ + config: VitestRunnerConfig + /** + * The name of the current pool. Can affect how stack trace is inferred on the server side. + */ + pool?: string +} +``` + +When initiating this class, Vitest passes down Vitest config, - you should expose it as a `config` property: + +```ts [runner.ts] +import type { RunnerTestFile } from 'vitest' +import type { VitestRunner, VitestRunnerConfig } from 'vitest/suite' +import { VitestTestRunner } from 'vitest/runners' + +class CustomRunner extends VitestTestRunner implements VitestRunner { + public config: VitestRunnerConfig + + constructor(config: VitestRunnerConfig) { + this.config = config + } + + onAfterRunFiles(files: RunnerTestFile[]) { + console.log('finished running', files) + } +} + +export default CustomRunner +``` + +::: warning +Vitest also injects an instance of `ModuleRunner` from `vite/module-runner` as `moduleRunner` property. You can use it to process files in `importFile` method (this is default behavior of `TestRunner` and `BenchmarkRunner`). + +`ModuleRunner` exposes `import` method, which is used to import test files in a Vite-friendly environment. Meaning, it will resolve imports and transform file content at runtime so that Node can understand it: + +```ts +export default class Runner { + async importFile(filepath: string) { + await this.moduleRunner.import(filepath) + } +} +``` +::: + +::: warning +If you don't have a custom runner or didn't define `runTest` method, Vitest will try to retrieve a task automatically. If you didn't add a function with `setFn`, it will fail. +::: + +::: tip +Snapshot support and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `NodeBenchmarkRunner`, if you want to extend benchmark functionality. +::: + +## Tasks + +::: warning +The "Runner Tasks API" is experimental and should primarily be used only in the test runtime. Vitest also exposes the ["Reported Tasks API"](/api/advanced/test-module), which should be preferred when working in the main thread (inside the reporter, for example). + +The team is currently discussing if "Runner Tasks" should be replaced by "Reported Tasks" in the future. +::: + +Suites and tests are called `tasks` internally. Vitest runner initiates a `File` task before collecting any tests - this is a superset of `Suite` with a few additional properties. It is available on every task (including `File`) as a `file` property. + +```ts +interface File extends Suite { + /** + * The name of the pool that the file belongs to. + * @default 'forks' + */ + pool?: string + /** + * The path to the file in UNIX format. + */ + filepath: string + /** + * The name of the test project the file belongs to. + */ + projectName: string | undefined + /** + * The time it took to collect all tests in the file. + * This time also includes importing all the file dependencies. + */ + collectDuration?: number + /** + * The time it took to import the setup file. + */ + setupDuration?: number +} +``` + +Every suite has a `tasks` property that is populated during collection phase. It is useful to traverse the task tree from the top down. + +```ts +interface Suite extends TaskBase { + type: 'suite' + /** + * File task. It's the root task of the file. + */ + file: File + /** + * An array of tasks that are part of the suite. + */ + tasks: Task[] +} +``` + +Every task has a `suite` property that references a suite it is located in. If `test` or `describe` are initiated at the top level, they will not have a `suite` property (it will **not** be equal to `file`!). `File` also never has a `suite` property. It is useful to travers the tasks from the bottom up. + +```ts +interface Test extends TaskBase { + type: 'test' + /** + * Test context that will be passed to the test function. + */ + context: TestContext & ExtraContext + /** + * File task. It's the root task of the file. + */ + file: File + /** + * Whether the task was skipped by calling `context.skip()`. + */ + pending?: boolean + /** + * Whether the task should succeed if it fails. If the task fails, it will be marked as passed. + */ + fails?: boolean + /** + * Store promises (from async expects) to wait for them before finishing the test + */ + promises?: Promise[] +} +``` + +Every task can have a `result` field. Suites can only have this field if an error thrown within a suite callback or `beforeAll`/`afterAll` callbacks prevents them from collecting tests. Tests always have this field after their callbacks are called - the `state` and `errors` fields are present depending on the outcome. If an error was thrown in `beforeEach` or `afterEach` callbacks, the thrown error will be present in `task.result.errors`. + +```ts +export interface TaskResult { + /** + * State of the task. Inherits the `task.mode` during collection. + * When the task has finished, it will be changed to `pass` or `fail`. + * - **pass**: task ran successfully + * - **fail**: task failed + */ + state: TaskState + /** + * Errors that occurred during the task execution. It is possible to have several errors + * if `expect.soft()` failed multiple times. + */ + errors?: TestError[] + /** + * How long in milliseconds the task took to run. + */ + duration?: number + /** + * Time in milliseconds when the task started running. + */ + startTime?: number + /** + * Heap size in bytes after the task finished. + * Only available if `logHeapUsage` option is set and `process.memoryUsage` is defined. + */ + heap?: number + /** + * State of related to this task hooks. Useful during reporting. + */ + hooks?: Partial> + /** + * The amount of times the task was retried. The task is retried only if it + * failed and `retry` option is set. + */ + retryCount?: number + /** + * The amount of times the task was repeated. The task is repeated only if + * `repeats` option is set. This number also contains `retryCount`. + */ + repeatCount?: number +} +``` + +## Your Task Function + +Vitest exposes `createTaskCollector` utility to create your own `test` method. It behaves the same way as a test, but calls a custom method during collection. + +A task is an object that is part of a suite. It is automatically added to the current suite with a `suite.task` method: + +```js [custom.js] +import { createTaskCollector, getCurrentSuite } from 'vitest/suite' + +export { afterAll, beforeAll, describe } from 'vitest' + +// this function will be called during collection phase: +// don't call function handler here, add it to suite tasks +// with "getCurrentSuite().task()" method +// note: createTaskCollector provides support for "todo"/"each"/... +export const myCustomTask = createTaskCollector( + function (name, fn, timeout) { + getCurrentSuite().task(name, { + ...this, // so "todo"/"skip"/... is tracked correctly + meta: { + customPropertyToDifferentiateTask: true + }, + handler: fn, + timeout, + }) + } +) +``` + +```js [tasks.test.js] +import { + afterAll, + beforeAll, + describe, + myCustomTask +} from './custom.js' +import { gardener } from './gardener.js' + +describe('take care of the garden', () => { + beforeAll(() => { + gardener.putWorkingClothes() + }) + + myCustomTask('weed the grass', () => { + gardener.weedTheGrass() + }) + myCustomTask.todo('mow the lawn', () => { + gardener.mowerTheLawn() + }) + myCustomTask('water flowers', () => { + gardener.waterFlowers() + }) + + afterAll(() => { + gardener.goHome() + }) +}) +``` + +```bash +vitest ./garden/tasks.test.js +``` diff --git a/api/advanced/test-case.md b/api/advanced/test-case.md new file mode 100644 index 00000000..67984323 --- /dev/null +++ b/api/advanced/test-case.md @@ -0,0 +1,282 @@ +# TestCase + +The `TestCase` class represents a single test. This class is only available in the main thread. Refer to the ["Runner API"](/api/advanced/runner#tasks) if you are working with runtime tasks. + +The `TestCase` instance always has a `type` property with the value of `test`. You can use it to distinguish between different task types: + +```ts +if (task.type === 'test') { + task // TestCase +} +``` + +## project + +This references the [`TestProject`](/api/advanced/test-project) that the test belongs to. + +## module + +This is a direct reference to the [`TestModule`](/api/advanced/test-module) where the test is defined. + +## name + +This is a test name that was passed to the `test` function. + +```ts +import { test } from 'vitest' + +// [!code word:'the validation works correctly'] +test('the validation works correctly', () => { + // ... +}) +``` + +## fullName + +The name of the test including all parent suites separated with `>` symbol. This test has a full name "the validation logic > the validation works correctly": + +```ts +import { describe, test } from 'vitest' + +// [!code word:'the validation works correctly'] +// [!code word:'the validation logic'] +describe('the validation logic', () => { + test('the validation works correctly', () => { + // ... + }) +}) +``` + +## id + +This is test's unique identifier. This ID is deterministic and will be the same for the same test across multiple runs. The ID is based on the [project](/api/advanced/test-project) name, module ID and test order. + +The ID looks like this: + +``` +1223128da3_0_0 +^^^^^^^^^^ the file hash + ^ suite index + ^ test index +``` + +::: tip +You can generate file hash with `generateFileHash` function from `vitest/node` which is available since Vitest 3: + +```ts +import { generateFileHash } from 'vitest/node' + +const hash = generateFileHash( + '/file/path.js', // relative path + undefined, // the project name or `undefined` is not set +) +``` +::: + +::: danger +Don't try to parse the ID. It can have a minus at the start: `-1223128da3_0_0_0`. +::: + +## location + +The location in the module where the test was defined. Locations are collected only if [`includeTaskLocation`](/config/#includetasklocation) is enabled in the config. Note that this option is automatically enabled if `--reporter=html`, `--ui` or `--browser` flags are used. + +The location of this test will be equal to `{ line: 3, column: 1 }`: + +```ts:line-numbers {3} +import { test } from 'vitest' + +test('the validation works correctly', () => { + // ... +}) +``` + +## parent + +Parent [suite](/api/advanced/test-suite). If the test was called directly inside the [module](/api/advanced/test-module), the parent will be the module itself. + +## options + +```ts +interface TaskOptions { + readonly each: boolean | undefined + readonly fails: boolean | undefined + readonly concurrent: boolean | undefined + readonly shuffle: boolean | undefined + readonly retry: number | undefined + readonly repeats: number | undefined + readonly mode: 'run' | 'only' | 'skip' | 'todo' +} +``` + +The options that test was collected with. + +## ok + +```ts +function ok(): boolean +``` + +Checks if the test did not fail the suite. If the test is not finished yet or was skipped, it will return `true`. + +## meta + +```ts +function meta(): TaskMeta +``` + +Custom [metadata](/api/advanced/metadata) that was attached to the test during its execution. The meta can be attached by assigning a property to the `ctx.task.meta` object during a test run: + +```ts {3,6} +import { test } from 'vitest' + +test('the validation works correctly', ({ task }) => { + // ... + + task.meta.decorated = false +}) +``` + +If the test did not finish running yet, the meta will be an empty object. + +## result + +```ts +function result(): TestResult +``` + +Test results. If test is not finished yet or was just collected, it will be equal to `TestResultPending`: + +```ts +export interface TestResultPending { + /** + * The test was collected, but didn't finish running yet. + */ + readonly state: 'pending' + /** + * Pending tests have no errors. + */ + readonly errors: undefined +} +``` + +If the test was skipped, the return value will be `TestResultSkipped`: + +```ts +interface TestResultSkipped { + /** + * The test was skipped with `skip` or `todo` flag. + * You can see which one was used in the `options.mode` option. + */ + readonly state: 'skipped' + /** + * Skipped tests have no errors. + */ + readonly errors: undefined + /** + * A custom note passed down to `ctx.skip(note)`. + */ + readonly note: string | undefined +} +``` + +::: tip +If the test was skipped because another test has `only` flag, the `options.mode` will be equal to `skip`. +::: + +If the test failed, the return value will be `TestResultFailed`: + +```ts +interface TestResultFailed { + /** + * The test failed to execute. + */ + readonly state: 'failed' + /** + * Errors that were thrown during the test execution. + */ + readonly errors: ReadonlyArray +} +``` + +If the test passed, the return value will be `TestResultPassed`: + +```ts +interface TestResultPassed { + /** + * The test passed successfully. + */ + readonly state: 'passed' + /** + * Errors that were thrown during the test execution. + */ + readonly errors: ReadonlyArray | undefined +} +``` + +::: warning +Note that the test with `passed` state can still have errors attached - this can happen if `retry` was triggered at least once. +::: + +## diagnostic + +```ts +function diagnostic(): TestDiagnostic | undefined +``` + +Useful information about the test like duration, memory usage, etc: + +```ts +interface TestDiagnostic { + /** + * If the duration of the test is above `slowTestThreshold`. + */ + readonly slow: boolean + /** + * The amount of memory used by the test in bytes. + * This value is only available if the test was executed with `logHeapUsage` flag. + */ + readonly heap: number | undefined + /** + * The time it takes to execute the test in ms. + */ + readonly duration: number + /** + * The time in ms when the test started. + */ + readonly startTime: number + /** + * The amount of times the test was retried. + */ + readonly retryCount: number + /** + * The amount of times the test was repeated as configured by `repeats` option. + * This value can be lower if the test failed during the repeat and no `retry` is configured. + */ + readonly repeatCount: number + /** + * If test passed on a second retry. + */ + readonly flaky: boolean +} +``` + +::: info +`diagnostic()` will return `undefined` if the test was not scheduled to run yet. +::: + +## annotations + +```ts +function annotations(): ReadonlyArray +``` + +[Test annotations](/guide/test-annotations) added via the [`task.annotate`](/guide/test-context#annotate) API during the test execution. + +## artifacts 4.0.11 {#artifacts} + +```ts +function artifacts(): ReadonlyArray +``` + +[Test artifacts](/api/advanced/artifacts) recorded via the `recordArtifact` API during the test execution. diff --git a/api/advanced/test-collection.md b/api/advanced/test-collection.md new file mode 100644 index 00000000..50e55a61 --- /dev/null +++ b/api/advanced/test-collection.md @@ -0,0 +1,89 @@ +# TestCollection + +`TestCollection` represents a collection of top-level [suites](/api/advanced/test-suite) and [tests](/api/advanced/test-case) in a suite or a module. It also provides useful methods to iterate over itself. + +::: info +Most methods return an iterator instead of an array for better performance in case you don't need every item in the collection. If you prefer working with array, you can spread the iterator: `[...children.allSuites()]`. + +Also note that the collection itself is an iterator: + +```ts +for (const child of module.children) { + console.log(child.type, child.name) +} +``` +::: + +## size + +The number of tests and suites in the collection. + +::: warning +This number includes only tests and suites at the top-level, it doesn't include nested suites and tests. +::: + +## at + +```ts +function at(index: number): TestCase | TestSuite | undefined +``` + +Returns the test or suite at a specific index. This method accepts negative indexes. + +## array + +```ts +function array(): (TestCase | TestSuite)[] +``` + +The same collection but as an array. This is useful if you want to use `Array` methods like `map` and `filter` that are not supported by the `TaskCollection` implementation. + +## allSuites + +```ts +function allSuites(): Generator +``` + +Filters all suites that are part of this collection and its children. + +```ts +for (const suite of module.children.allSuites()) { + if (suite.errors().length) { + console.log('failed to collect', suite.errors()) + } +} +``` + +## allTests + +```ts +function allTests(state?: TestState): Generator +``` + +Filters all tests that are part of this collection and its children. + +```ts +for (const test of module.children.allTests()) { + if (test.result().state === 'pending') { + console.log('test', test.fullName, 'did not finish') + } +} +``` + +You can pass down a `state` value to filter tests by the state. + +## tests + +```ts +function tests(state?: TestState): Generator +``` + +Filters only the tests that are part of this collection. You can pass down a `state` value to filter tests by the state. + +## suites + +```ts +function suites(): Generator +``` + +Filters only the suites that are part of this collection. diff --git a/api/advanced/test-module.md b/api/advanced/test-module.md new file mode 100644 index 00000000..b85f476d --- /dev/null +++ b/api/advanced/test-module.md @@ -0,0 +1,126 @@ +# TestModule + +The `TestModule` class represents a single module in a single project. This class is only available in the main thread. Refer to the ["Runner API"](/api/advanced/runner#tasks) if you are working with runtime tasks. + +The `TestModule` instance always has a `type` property with the value of `module`. You can use it to distinguish between different task types: + +```ts +if (task.type === 'module') { + task // TestModule +} +``` + +::: warning Extending Suite Methods +The `TestModule` class inherits all methods and properties from the [`TestSuite`](/api/advanced/test-suite). This guide will only list methods and properties unique to the `TestModule`. +::: + +## moduleId + +This is usually an absolute unix file path (even on Windows). It can be a virtual id if the file is not on the disk. This value corresponds to Vite's `ModuleGraph` id. + +```ts +'C:/Users/Documents/project/example.test.ts' // ✅ +'/Users/mac/project/example.test.ts' // ✅ +'C:\\Users\\Documents\\project\\example.test.ts' // ❌ +``` + +## relativeModuleId + +Module id relative to the project. This is the same as `task.name` in the deprecated API. + +```ts +'project/example.test.ts' // ✅ +'example.test.ts' // ✅ +'project\\example.test.ts' // ❌ +``` + +## state + +```ts +function state(): TestModuleState +``` + +Works the same way as [`testSuite.state()`](/api/advanced/test-suite#state), but can also return `queued` if module wasn't executed yet. + +## meta 3.1.0 {#meta} + +```ts +function meta(): TaskMeta +``` + +Custom [metadata](/api/advanced/metadata) that was attached to the module during its execution or collection. The meta can be attached by assigning a property to the `task.meta` object during a test run: + +```ts {5,10} +import { test } from 'vitest' + +describe('the validation works correctly', (task) => { + // assign "decorated" during collection + task.file.meta.decorated = false + + test('some test', ({ task }) => { + // assign "decorated" during test run, it will be available + // only in onTestCaseReady hook + task.file.meta.decorated = false + }) +}) +``` + +:::tip +If metadata was attached during collection (outside of the `test` function), then it will be available in [`onTestModuleCollected`](./reporters#ontestmodulecollected) hook in the custom reporter. +::: + +## diagnostic + +```ts +function diagnostic(): ModuleDiagnostic +``` + +Useful information about the module like duration, memory usage, etc. If the module was not executed yet, all diagnostic values will return `0`. + +```ts +interface ModuleDiagnostic { + /** + * The time it takes to import and initiate an environment. + */ + readonly environmentSetupDuration: number + /** + * The time it takes Vitest to setup test harness (runner, mocks, etc.). + */ + readonly prepareDuration: number + /** + * The time it takes to import the test module. + * This includes importing everything in the module and executing suite callbacks. + */ + readonly collectDuration: number + /** + * The time it takes to import the setup module. + */ + readonly setupDuration: number + /** + * Accumulated duration of all tests and hooks in the module. + */ + readonly duration: number + /** + * The amount of memory used by the module in bytes. + * This value is only available if the test was executed with `logHeapUsage` flag. + */ + readonly heap: number | undefined + /** + * The time spent importing every non-externalized dependency that Vitest has processed. + */ + readonly importDurations: Record +} + +/** The time spent importing & executing a non-externalized file. */ +interface ImportDuration { + /** The time spent importing & executing the file itself, not counting all non-externalized imports that the file does. */ + selfTime: number + + /** The time spent importing & executing the file and all its imports. */ + totalTime: number +} +``` + +## viteEnvironment 4.0.15 {#viteenvironment} + +This is a Vite's [`DevEnvironment`](https://vite.dev/guide/api-environment) that transforms all files inside of the test module. diff --git a/api/advanced/test-project.md b/api/advanced/test-project.md new file mode 100644 index 00000000..72eff647 --- /dev/null +++ b/api/advanced/test-project.md @@ -0,0 +1,325 @@ +--- +title: TestProject +--- + +# TestProject 3.0.0 {#testproject} + +::: warning +This guide describes the advanced Node.js API. If you just want to define projects, follow the ["Test Projects"](/guide/projects) guide. +::: + +## name + +The name is a unique string assigned by the user or interpreted by Vitest. If user did not provide a name, Vitest tries to load a `package.json` in the root of the project and takes the `name` property from there. If there is no `package.json`, Vitest uses the name of the folder by default. Inline projects use numbers as the name (converted to string). + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.projects.map(p => p.name) === [ + '@pkg/server', + 'utils', + '2', + 'custom' +] +``` +```ts [vitest.config.js] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + './packages/server', // has package.json with "@pkg/server" + './utils', // doesn't have a package.json file + { + // doesn't customize the name + test: { + pool: 'threads', + }, + }, + { + // customized the name + test: { + name: 'custom', + }, + }, + ], + }, +}) +``` +::: + +::: info +If the [root project](/api/advanced/vitest#getroottestproject) is not part of user projects, its `name` will not be resolved. +::: + +## vitest + +`vitest` references the global [`Vitest`](/api/advanced/vitest) process. + +## serializedConfig + +This is the config that test processes receive. Vitest [serializes config](https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/config/serializeConfig.ts) manually by removing all functions and properties that are not possible to serialize. Since this value is available in both tests and node, its type is exported from the main entry point. + +```ts +import type { SerializedConfig } from 'vitest' + +const config: SerializedConfig = vitest.projects[0].serializedConfig +``` + +::: warning +The `serializedConfig` property is a getter. Every time it's accessed Vitest serializes the config again in case it was changed. This also means that it always returns a different reference: + +```ts +project.serializedConfig === project.serializedConfig // ❌ +``` +::: + +## globalConfig + +The test config that [`Vitest`](/api/advanced/vitest) was initialized with. If this is the [root project](/api/advanced/vitest#getroottestproject), `globalConfig` and `config` will reference the same object. This config is useful for values that cannot be set on the project level, like `coverage` or `reporters`. + +```ts +import type { ResolvedConfig } from 'vitest/node' + +vitest.config === vitest.projects[0].globalConfig +``` + +## config + +This is the project's resolved test config. + +## hash 3.2.0 {#hash} + +The unique hash of this project. This value is consistent between the reruns. + +It is based on the root of the project and its name. Note that the root path is not consistent between different OS, so the hash will also be different. + +## vite + +This is project's [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). All projects have their own Vite servers. + +## browser + +This value will be set only if tests are running in the browser. If `browser` is enabled, but tests didn't run yet, this will be `undefined`. If you need to check if the project supports browser tests, use `project.isBrowserEnabled()` method. + +::: warning +The browser API is even more experimental and doesn't follow SemVer. The browser API will be standardized separately from the rest of the APIs. +::: + +## provide + +```ts +function provide( + key: T, + value: ProvidedContext[T], +): void +``` + +A way to provide custom values to tests in addition to [`config.provide`](/config/#provide) field. All values are validated with [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) before they are stored, but the values on `providedContext` themselves are not cloned. + +::: code-group +```ts [node.js] +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') +await vitest.start() +``` +```ts [test.spec.js] +import { inject } from 'vitest' +const value = inject('key') +``` +::: + +The values can be provided dynamically. Provided value in tests will be updated on their next run. + +::: tip +This method is also available to [global setup files](/config/#globalsetup) for cases where you cannot use the public API: + +```js +export default function setup({ provide }) { + provide('wsPort', 3000) +} +``` +::: + +## getProvidedContext + +```ts +function getProvidedContext(): ProvidedContext +``` + +This returns the context object. Every project also inherits the global context set by `vitest.provide`. + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test') +vitest.provide('global', true) +const project = vitest.projects.find(p => p.name === 'custom') +project.provide('key', 'value') + +// { global: true, key: 'value' } +const context = project.getProvidedContext() +``` + +::: tip +Project context values will always override root project's context. +::: + +## createSpecification + +```ts +function createSpecification( + moduleId: string, + locations?: number[], +): TestSpecification +``` + +Create a [test specification](/api/advanced/test-specification) that can be used in [`vitest.runTestSpecifications`](/api/advanced/vitest#runtestspecifications). Specification scopes the test file to a specific `project` and test `locations` (optional). Test [locations](/api/advanced/test-case#location) are code lines where the test is defined in the source code. If locations are provided, Vitest will only run tests defined on those lines. Note that if [`testNamePattern`](/config/#testnamepattern) is defined, then it will also be applied. + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] +const specification = project.createSpecification( + resolve('./example.test.ts'), + [20, 40], // optional test lines +) +await vitest.runTestSpecifications([specification]) +``` + +::: warning +`createSpecification` expects resolved [module ID](/api/advanced/test-specification#moduleid). It doesn't auto-resolve the file or check that it exists on the file system. + +Also note that `project.createSpecification` always returns a new instance. +::: + +## isRootProject + +```ts +function isRootProject(): boolean +``` + +Checks if the current project is the root project. You can also get the root project by calling [`vitest.getRootProject()`](#getrootproject). + +## globTestFiles + +```ts +function globTestFiles(filters?: string[]): { + /** + * Test files that match the filters. + */ + testFiles: string[] + /** + * Typecheck test files that match the filters. This will be empty unless `typecheck.enabled` is `true`. + */ + typecheckTestFiles: string[] +} +``` + +Globs all test files. This function returns an object with regular tests and typecheck tests. + +This method accepts `filters`. Filters can only a part of the file path, unlike in other methods on the [`Vitest`](/api/advanced/vitest) instance: + +```js +project.globTestFiles(['foo']) // ✅ +project.globTestFiles(['basic/foo.js:10']) // ❌ +``` + +::: tip +Vitest uses [fast-glob](https://www.npmjs.com/package/fast-glob) to find test files. `test.dir`, `test.root`, `root` or `process.cwd()` define the `cwd` option. + +This method looks at several config options: + +- `test.include`, `test.exclude` to find regular test files +- `test.includeSource`, `test.exclude` to find in-source tests +- `test.typecheck.include`, `test.typecheck.exclude` to find typecheck tests +::: + +## matchesTestGlob + +```ts +function matchesTestGlob( + moduleId: string, + source?: () => string +): boolean +``` + +This method checks if the file is a regular test file. It uses the same config properties that `globTestFiles` uses for validation. + +This method also accepts a second parameter, which is the source code. This is used to validate if the file is an in-source test. If you are calling this method several times for several projects it is recommended to read the file once and pass it down directly. If the file is not a test file, but matches the `includeSource` glob, Vitest will synchronously read the file unless the `source` is provided. + +```ts +import { createVitest } from 'vitest/node' +import { resolve } from 'node:path/posix' + +const vitest = await createVitest('test') +const project = vitest.projects[0] + +project.matchesTestGlob(resolve('./basic.test.ts')) // true +project.matchesTestGlob(resolve('./basic.ts')) // false +project.matchesTestGlob(resolve('./basic.ts'), () => ` +if (import.meta.vitest) { + // ... +} +`) // true if `includeSource` is set +``` + +## import + + + +Import a file using Vite module runner. The file will be transformed by Vite with provided project's config and executed in a separate context. Note that `moduleId` will be relative to the `config.root`. + +::: danger +`project.import` reuses Vite's module graph, so importing the same module using a regular import will return a different module: + +```ts +import * as staticExample from './example.js' +const dynamicExample = await project.import('./example.js') + +dynamicExample !== staticExample // ✅ +``` +::: + +::: info +Internally, Vitest uses this method to import global setups, custom coverage providers and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. +::: + +## onTestsRerun + +```ts +function onTestsRerun(cb: OnTestsRerunHandler): void +``` + +This is a shorthand for [`project.vitest.onTestsRerun`](/api/advanced/vitest#ontestsrerun). It accepts a callback that will be awaited when the tests have been scheduled to rerun (usually, due to a file change). + +```ts +project.onTestsRerun((specs) => { + console.log(specs) +}) +``` + +## isBrowserEnabled + +```ts +function isBrowserEnabled(): boolean +``` + +Returns `true` if this project runs tests in the browser. + +## close + +```ts +function close(): Promise +``` + +Closes the project and all associated resources. This can only be called once; the closing promise is cached until the server restarts. If the resources are needed again, create a new project. + +In detail, this method closes the Vite server, stops the typechecker service, closes the browser if it's running, deletes the temporary directory that holds the source code, and resets the provided context. diff --git a/api/advanced/test-specification.md b/api/advanced/test-specification.md new file mode 100644 index 00000000..020859f8 --- /dev/null +++ b/api/advanced/test-specification.md @@ -0,0 +1,79 @@ +# TestSpecification + +The `TestSpecification` class describes what module to run as a test and its parameters. + +You can only create a specification by calling [`createSpecification`](/api/advanced/test-project#createspecification) method on a test project: + +```ts +const specification = project.createSpecification( + resolve('./example.test.ts'), + [20, 40], // optional test lines +) +``` + +`createSpecification` expects resolved module ID. It doesn't auto-resolve the file or check that it exists on the file system. + +## taskId + +[Test module's](/api/advanced/test-suite#id) identifier. + +## project + +This references the [`TestProject`](/api/advanced/test-project) that the test module belongs to. + +## moduleId + +The ID of the module in Vite's module graph. Usually, it's an absolute file path using posix separator: + +```ts +'C:/Users/Documents/project/example.test.ts' // ✅ +'/Users/mac/project/example.test.ts' // ✅ +'C:\\Users\\Documents\\project\\example.test.ts' // ❌ +``` + +## testModule + +Instance of [`TestModule`](/api/advanced/test-module) associated with the specification. If test wasn't queued yet, this will be `undefined`. + +## pool experimental {#pool} + +The [`pool`](/config/#pool) in which the test module will run. + +::: danger +It's possible to have multiple pools in a single test project with [`poolMatchGlob`](/config/#poolmatchglob) and [`typecheck.enabled`](/config/#typecheck-enabled). This means it's possible to have several specifications with the same `moduleId` but different `pool`. In Vitest 4, the project will only support a single pool, and this property will be removed. +::: + +## testLines + +This is an array of lines in the source code where the test files are defined. This field is defined only if the `createSpecification` method received an array. + +Note that if there is no test on at least one of the lines, the whole suite will fail. An example of a correct `testLines` configuration: + +::: code-group +```ts [script.js] +const specification = project.createSpecification( + resolve('./example.test.ts'), + [3, 8, 9], +) +``` +```ts:line-numbers{3,8,9} [example.test.js] +import { test, describe } from 'vitest' + +test('verification works') + +describe('a group of tests', () => { // [!code error] + // ... + + test('nested test') + test.skip('skipped test') +}) +``` +::: + +## toJSON + +```ts +function toJSON(): SerializedTestSpecification +``` + +`toJSON` generates a JSON-friendly object that can be consumed by the [Browser Mode](/guide/browser/) or [Vitest UI](/guide/ui). diff --git a/api/advanced/test-suite.md b/api/advanced/test-suite.md new file mode 100644 index 00000000..db2f98e7 --- /dev/null +++ b/api/advanced/test-suite.md @@ -0,0 +1,221 @@ +# TestSuite + +The `TestSuite` class represents a single suite. This class is only available in the main thread. Refer to the ["Runner API"](/api/advanced/runner#tasks) if you are working with runtime tasks. + +The `TestSuite` instance always has a `type` property with the value of `suite`. You can use it to distinguish between different task types: + +```ts +if (task.type === 'suite') { + task // TestSuite +} +``` + +## project + +This references the [`TestProject`](/api/advanced/test-project) that the test belongs to. + +## module + +This is a direct reference to the [`TestModule`](/api/advanced/test-module) where the test is defined. + +## name + +This is a suite name that was passed to the `describe` function. + +```ts +import { describe } from 'vitest' + +// [!code word:'the validation logic'] +describe('the validation logic', () => { + // ... +}) +``` + +## fullName + +The name of the suite including all parent suites separated with `>` symbol. This suite has a full name "the validation logic > validating cities": + +```ts +import { describe, test } from 'vitest' + +// [!code word:'the validation logic'] +// [!code word:'validating cities'] +describe('the validation logic', () => { + describe('validating cities', () => { + // ... + }) +}) +``` + +## id + +This is suite's unique identifier. This ID is deterministic and will be the same for the same suite across multiple runs. The ID is based on the [project](/api/advanced/test-project) name, module ID and suite order. + +The ID looks like this: + +``` +1223128da3_0_0_0 +^^^^^^^^^^ the file hash + ^ suite index + ^ nested suite index + ^ test index +``` + +::: tip +You can generate file hash with `generateFileHash` function from `vitest/node` which is available since Vitest 3: + +```ts +import { generateFileHash } from 'vitest/node' + +const hash = generateFileHash( + '/file/path.js', // relative path + undefined, // the project name or `undefined` is not set +) +``` +::: + +::: danger +Don't try to parse the ID. It can have a minus at the start: `-1223128da3_0_0_0`. +::: + +## location + +The location in the module where the suite was defined. Locations are collected only if [`includeTaskLocation`](/config/#includetasklocation) is enabled in the config. Note that this option is automatically enabled if `--reporter=html`, `--ui` or `--browser` flags are used. + +The location of this suite will be equal to `{ line: 3, column: 1 }`: + +```ts:line-numbers {3} +import { describe } from 'vitest' + +describe('the validation works correctly', () => { + // ... +}) +``` + +## parent + +Parent suite. If the suite was called directly inside the [module](/api/advanced/test-module), the parent will be the module itself. + +## options + +```ts +interface TaskOptions { + readonly each: boolean | undefined + readonly fails: boolean | undefined + readonly concurrent: boolean | undefined + readonly shuffle: boolean | undefined + readonly retry: number | undefined + readonly repeats: number | undefined + readonly mode: 'run' | 'only' | 'skip' | 'todo' +} +``` + +The options that suite was collected with. + +## children + +This is a [collection](/api/advanced/test-collection) of all suites and tests inside the current suite. + +```ts +for (const task of suite.children) { + if (task.type === 'test') { + console.log('test', task.fullName) + } + else { + // task is TaskSuite + console.log('suite', task.name) + } +} +``` + +::: warning +Note that `suite.children` will only iterate the first level of nesting, it won't go deeper. If you need to iterate over all tests or suites, use [`children.allTests()`](/api/advanced/test-collection#alltests) or [`children.allSuites()`](/api/advanced/test-collection#allsuites). If you need to iterate over everything, use recursive function: + +```ts +function visit(collection: TestCollection) { + for (const task of collection) { + if (task.type === 'suite') { + // report a suite + visit(task.children) + } + else { + // report a test + } + } +} +``` +::: + +## ok + +```ts +function ok(): boolean +``` + +Checks if the suite has any failed tests. This will also return `false` if suite failed during collection. In that case, check the [`errors()`](#errors) for thrown errors. + +## state + +```ts +function state(): TestSuiteState +``` + +Checks the running state of the suite. Possible return values: + +- **pending**: the tests in this suite did not finish running yet. +- **failed**: this suite has failed tests or they couldn't be collected. If [`errors()`](#errors) is not empty, it means the suite failed to collect tests. +- **passed**: every test inside this suite has passed. +- **skipped**: this suite was skipped during collection. + +::: warning +Note that [test module](/api/advanced/test-module) also has a `state` method that returns the same values, but it can also return an additional `queued` state if the module wasn't executed yet. +::: + +## errors + +```ts +function errors(): TestError[] +``` + +Errors that happened outside of the test run during collection, like syntax errors. + +```ts {4} +import { describe } from 'vitest' + +describe('collection failed', () => { + throw new Error('a custom error') +}) +``` + +::: warning +Note that errors are serialized into simple objects: `instanceof Error` will always return `false`. +::: + +## meta 3.1.0 {#meta} + +```ts +function meta(): TaskMeta +``` + +Custom [metadata](/api/advanced/metadata) that was attached to the suite during its execution or collection. The meta can be attached by assigning a property to the `suite.meta` object during a test run: + +```ts {7,12} +import { test } from 'vitest' +import { getCurrentSuite } from 'vitest/suite' + +describe('the validation works correctly', () => { + // assign "decorated" during collection + const { suite } = getCurrentSuite() + suite!.meta.decorated = true + + test('some test', ({ task }) => { + // assign "decorated" during test run, it will be available + // only in onTestCaseReady hook + task.suite.meta.decorated = false + }) +}) +``` + +:::tip +If metadata was attached during collection (outside of the `test` function), then it will be available in [`onTestModuleCollected`](./reporters#ontestmodulecollected) hook in the custom reporter. +::: diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md new file mode 100644 index 00000000..bcfcac9d --- /dev/null +++ b/api/advanced/vitest.md @@ -0,0 +1,674 @@ +--- +outline: deep +title: Vitest API +--- + +# Vitest + +Vitest instance requires the current test mode. It can be either: + +- `test` when running runtime tests +- `benchmark` when running benchmarks experimental + +::: details New in Vitest 4 +Vitest 4 added several new APIs (they are marked with a "4.0.0+" badge) and removed deprecated APIs: + +- `invalidates` +- `changedTests` (use [`onFilterWatchedSpecification`](#onfilterwatchedspecification) instead) +- `server` (use [`vite`](#vite) instead) +- `getProjectsByTestFile` (use [`getModuleSpecifications`](#getmodulespecifications) instead) +- `getFileWorkspaceSpecs` (use [`getModuleSpecifications`](#getmodulespecifications) instead) +- `getModuleProjects` (filter by [`this.projects`](#projects) yourself) +- `updateLastChanged` (renamed to [`invalidateFile`](#invalidatefile)) +- `globTestSpecs` (use [`globTestSpecifications`](#globtestspecifications) instead) +- `globTestFiles` (use [`globTestSpecifications`](#globtestspecifications) instead) +- `listFile` (use [`getRelevantTestSpecifications`](#getrelevanttestspecifications) instead) +::: + +## mode + +### test + +Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files. + +### benchmark experimental + +Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files. + +## config + +The root (or global) config. If projects are defined, they will reference this as `globalConfig`. + +::: warning +This is Vitest config, it doesn't extend _Vite_ config. It only has resolved values from the `test` property. +::: + +## vite + +This is a global [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). + +## state experimental + +::: warning +Public `state` is an experimental API (except `vitest.state.getReportedEntity`). Breaking changes might not follow SemVer, please pin Vitest's version when using it. +::: + +Global state stores information about the current tests. It uses the same API from `@vitest/runner` by default, but we recommend using the [Reported Tasks API](/api/advanced/reporters#reported-tasks) instead by calling `state.getReportedEntity()` on the `@vitest/runner` API: + +```ts +const task = vitest.state.idMap.get(taskId) // old API +const testCase = vitest.state.getReportedEntity(task) // new API +``` + +In the future, the old API won't be exposed anymore. + +## snapshot + +The global snapshot manager. Vitest keeps track of all snapshots using the `snapshot.add` method. + +You can get the latest summary of snapshots via the `vitest.snapshot.summary` property. + +## cache + +Cache manager that stores information about latest test results and test file stats. In Vitest itself this is only used by the default sequencer to sort tests. + +## watcher 4.0.0 {#watcher} + +The instance of a Vitest watcher with useful methods to track file changes and rerun tests. You can use `onFileChange`, `onFileDelete` or `onFileCreate` with your own watcher, if the built-in watcher is disabled. + +## projects + +An array of [test projects](/api/advanced/test-project) that belong to user's projects. If the user did not specify a them, this array will only contain a [root project](#getrootproject). + +Vitest will ensure that there is always at least one project in this array. If the user specifies a non-existent `--project` name, Vitest will throw an error before this array is defined. + +## getRootProject + +```ts +function getRootProject(): TestProject +``` + +This returns the root test project. The root project generally doesn't run any tests and is not included in `vitest.projects` unless the user explicitly includes the root config in their configuration, or projects are not defined at all. + +The primary goal of the root project is to setup the global config. In fact, `rootProject.config` references `rootProject.globalConfig` and `vitest.config` directly: + +```ts +rootProject.config === rootProject.globalConfig === rootProject.vitest.config +``` + +## provide + +```ts +function provide( + key: T, + value: ProvidedContext[T], +): void +``` + +Vitest exposes `provide` method which is a shorthand for `vitest.getRootProject().provide`. With this method you can pass down values from the main thread to tests. All values are checked with `structuredClone` before they are stored, but the values themselves are not cloned. + +To receive the values in the test, you need to import `inject` method from `vitest` entrypoint: + +```ts +import { inject } from 'vitest' +const port = inject('wsPort') // 3000 +``` + +For better type safety, we encourage you to augment the type of `ProvidedContext`: + +```ts +import { createVitest } from 'vitest/node' + +const vitest = await createVitest('test', { + watch: false, +}) +vitest.provide('wsPort', 3000) + +declare module 'vitest' { + export interface ProvidedContext { + wsPort: number + } +} +``` + +::: warning +Technically, `provide` is a method of [`TestProject`](/api/advanced/test-project), so it is limited to the specific project. However, all projects inherit the values from the root project which makes `vitest.provide` universal way of passing down values to tests. +::: + +## getProvidedContext + +```ts +function getProvidedContext(): ProvidedContext +``` + +This returns the root context object. This is a shorthand for `vitest.getRootProject().getProvidedContext`. + +## getProjectByName + +```ts +function getProjectByName(name: string): TestProject +``` + +This method returns the project by its name. Similar to calling `vitest.projects.find`. + +::: warning +In case the project doesn't exist, this method will return the root project - make sure to check the names again if the project you are looking for is the one returned. + +If user didn't customize a name, the Vitest will assign an empty string as a name. +::: + +## globTestSpecifications + +```ts +function globTestSpecifications( + filters?: string[], +): Promise +``` + +This method constructs new [test specifications](/api/advanced/test-specification) by collecting every test in all projects with [`project.globTestFiles`](/api/advanced/test-project#globtestfiles). It accepts string filters to match the test files - these are the same filters that [CLI supports](/guide/filtering#cli). + +This method automatically caches all test specifications. When you call [`getModuleSpecifications`](#getmodulespecifications) next time, it will return the same specifications unless [`clearSpecificationsCache`](#clearspecificationscache) was called before that. + +::: warning +As of Vitest 3, it's possible to have multiple test specifications with the same module ID (file path) if `poolMatchGlob` has several pools or if `typecheck` is enabled. This possibility will be removed in Vitest 4. +::: + +```ts +const specifications = await vitest.globTestSpecifications(['my-filter']) +// [TestSpecification{ moduleId: '/tests/my-filter.test.ts' }] +console.log(specifications) +``` + +## getRelevantTestSpecifications + +```ts +function getRelevantTestSpecifications( + filters?: string[] +): Promise +``` + +This method resolves every test specification by calling [`project.globTestFiles`](/api/advanced/test-project#globtestfiles). It accepts string filters to match the test files - these are the same filters that [CLI supports](/guide/filtering#cli). If `--changed` flag was specified, the list will be filtered to include only files that changed. `getRelevantTestSpecifications` doesn't run any test files. + +::: warning +This method can be slow because it needs to filter `--changed` flags. Do not use it if you just need a list of test files. + +- If you need to get the list of specifications for known test files, use [`getModuleSpecifications`](#getmodulespecifications) instead. +- If you need to get the list of all possible test files, use [`globTestSpecifications`](#globtestspecifications). +::: + +## mergeReports + +```ts +function mergeReports(directory?: string): Promise +``` + +Merge reports from multiple runs located in the specified directory (value from `--merge-reports` if not specified). This value can also be set on `config.mergeReports` (by default, it will read `.vitest-reports` folder). + +Note that the `directory` will always be resolved relative to the working directory. + +This method is called automatically by [`startVitest`](/guide/advanced/tests) if `config.mergeReports` is set. + +## collect + +```ts +function collect(filters?: string[]): Promise +``` + +Execute test files without running test callbacks. `collect` returns unhandled errors and an array of [test modules](/api/advanced/test-module). It accepts string filters to match the test files - these are the same filters that [CLI supports](/guide/filtering#cli). + +This method resolves tests specifications based on the config `include`, `exclude`, and `includeSource` values. Read more at [`project.globTestFiles`](/api/advanced/test-project#globtestfiles). If `--changed` flag was specified, the list will be filtered to include only files that changed. + +::: warning +Note that Vitest doesn't use static analysis to collect tests. Vitest will run every test file in isolation, just like it runs regular tests. + +This makes this method very slow, unless you disable isolation before collecting tests. +::: + +## start + +```ts +function start(filters?: string[]): Promise +``` + +Initialize reporters, the coverage provider, and run tests. This method accepts string filters to match the test files - these are the same filters that [CLI supports](/guide/filtering#cli). + +::: warning +This method should not be called if [`vitest.init()`](#init) is also invoked. Use [`runTestSpecifications`](#runtestspecifications) or [`rerunTestSpecifications`](#reruntestspecifications) instead if you need to run tests after Vitest was initialised. +::: + +This method is called automatically by [`startVitest`](/guide/advanced/tests) if `config.mergeReports` and `config.standalone` are not set. + +## init + +```ts +function init(): Promise +``` + +Initialize reporters and the coverage provider. This method doesn't run any tests. If the `--watch` flag is provided, Vitest will still run changed tests even if this method was not called. + +Internally, this method is called only if [`--standalone`](/guide/cli#standalone) flag is enabled. + +::: warning +This method should not be called if [`vitest.start()`](#start) is also invoked. +::: + +This method is called automatically by [`startVitest`](/guide/advanced/tests) if `config.standalone` is set. + +## getModuleSpecifications + +```ts +function getModuleSpecifications(moduleId: string): TestSpecification[] +``` + +Returns a list of test specifications related to the module ID. The ID should already be resolved to an absolute file path. If ID doesn't match `include` or `includeSource` patterns, the returned array will be empty. + +This method can return already cached specifications based on the `moduleId` and `pool`. But note that [`project.createSpecification`](/api/advanced/test-project#createspecification) always returns a new instance and it's not cached automatically. However, specifications are automatically cached when [`runTestSpecifications`](#runtestspecifications) is called. + +::: warning +As of Vitest 3, this method uses a cache to check if the file is a test. To make sure that the cache is not empty, call [`globTestSpecifications`](#globtestspecifications) at least once. +::: + +## clearSpecificationsCache + +```ts +function clearSpecificationsCache(moduleId?: string): void +``` + +Vitest automatically caches test specifications for each file when [`globTestSpecifications`](#globtestspecifications) or [`runTestSpecifications`](#runtestspecifications) is called. This method clears the cache for the given file or the whole cache altogether depending on the first argument. + +## runTestSpecifications + +```ts +function runTestSpecifications( + specifications: TestSpecification[], + allTestsRun = false +): Promise +``` + +This method runs every test based on the received [specifications](/api/advanced/test-specification). The second argument, `allTestsRun`, is used by the coverage provider to determine if it needs to include uncovered files in report. + +::: warning +This method doesn't trigger `onWatcherRerun`, `onWatcherStart` and `onTestsRerun` callbacks. If you are rerunning tests based on the file change, consider using [`rerunTestSpecifications`](#reruntestspecifications) instead. +::: + +## rerunTestSpecifications + +```ts +function rerunTestSpecifications( + specifications: TestSpecification[], + allTestsRun = false +): Promise +``` + +This method emits `reporter.onWatcherRerun` and `onTestsRerun` events, then it runs tests with [`runTestSpecifications`](#runtestspecifications). If there were no errors in the main process, it will emit `reporter.onWatcherStart` event. + +## updateSnapshot + +```ts +function updateSnapshot(files?: string[]): Promise +``` + +Update snapshots in specified files. If no files are provided, it will update files with failed tests and obsolete snapshots. + +## collectTests + +```ts +function collectTests( + specifications: TestSpecification[] +): Promise +``` + +Execute test files without running test callbacks. `collectTests` returns unhandled errors and an array of [test modules](/api/advanced/test-module). + +This method works exactly the same as [`collect`](#collect), but you need to provide test specifications yourself. + +::: warning +Note that Vitest doesn't use static analysis to collect tests. Vitest will run every test file in isolation, just like it runs regular tests. + +This makes this method very slow, unless you disable isolation before collecting tests. +::: + +## cancelCurrentRun + +```ts +function cancelCurrentRun(reason: CancelReason): Promise +``` + +This method will gracefully cancel all ongoing tests. It will wait for started tests to finish running and will not run tests that were scheduled to run but haven't started yet. + +## setGlobalTestNamePattern + +```ts +function setGlobalTestNamePattern(pattern: string | RegExp): void +``` + +This methods overrides the global [test name pattern](/config/#testnamepattern). + +::: warning +This method doesn't start running any tests. To run tests with updated pattern, call [`runTestSpecifications`](#runtestspecifications). +::: + +## getGlobalTestNamePattern 4.0.0 {#getglobaltestnamepattern} + +```ts +function getGlobalTestNamePattern(): RegExp | undefined +``` + +Returns the regexp used for the global test name pattern. + +## resetGlobalTestNamePattern + +```ts +function resetGlobalTestNamePattern(): void +``` + +This methods resets the [test name pattern](/config/#testnamepattern). It means Vitest won't skip any tests now. + +::: warning +This method doesn't start running any tests. To run tests without a pattern, call [`runTestSpecifications`](#runtestspecifications). +::: + +## enableSnapshotUpdate + +```ts +function enableSnapshotUpdate(): void +``` + +Enable the mode that allows updating snapshots when running tests. Every test that runs after this method is called will update snapshots. To disable the mode, call [`resetSnapshotUpdate`](#resetsnapshotupdate). + +::: warning +This method doesn't start running any tests. To update snapshots, run tests with [`runTestSpecifications`](#runtestspecifications). +::: + +## resetSnapshotUpdate + +```ts +function resetSnapshotUpdate(): void +``` + +Disable the mode that allows updating snapshots when running tests. This method doesn't start running any tests. + +## invalidateFile + +```ts +function invalidateFile(filepath: string): void +``` + +This method invalidates the file in the cache of every project. It is mostly useful if you rely on your own watcher because Vite's cache persist in memory. + +::: danger +If you disable Vitest's watcher but keep Vitest running, it is important to manually clear the cache with this method because there is no way to disable the cache. This method will also invalidate file's importers. +::: + +## import + + + +Import a file using Vite module runner. The file will be transformed by Vite with the global config and executed in a separate context. Note that `moduleId` will be relative to the `config.root`. + +::: danger +`project.import` reuses Vite's module graph, so importing the same module using a regular import will return a different module: + +```ts +import * as staticExample from './example.js' +const dynamicExample = await vitest.import('./example.js') + +dynamicExample !== staticExample // ✅ +``` +::: + +::: info +Internally, Vitest uses this method to import global setups, custom coverage providers, and custom reporters, meaning all of them share the same module graph as long as they belong to the same Vite server. +::: + +## close + +```ts +function close(): Promise +``` + +Closes all projects and their associated resources. This can only be called once; the closing promise is cached until the server restarts. + +## exit + +```ts +function exit(force = false): Promise +``` + +Closes all projects and exit the process. If `force` is set to `true`, the process will exit immediately after closing the projects. + +This method will also forcefully call `process.exit()` if the process is still active after [`config.teardownTimeout`](/config/#teardowntimeout) milliseconds. + +## shouldKeepServer + +```ts +function shouldKeepServer(): boolean +``` + +This method will return `true` if the server should be kept running after the tests are done. This usually means that the `watch` mode was enabled. + +## onServerRestart + +```ts +function onServerRestart(fn: OnServerRestartHandler): void +``` + +Register a handler that will be called when the server is restarted due to a config change. + +## onCancel + +```ts +function onCancel(fn: (reason: CancelReason) => Awaitable): () => void +``` + +Register a handler that will be called when the test run is cancelled with [`vitest.cancelCurrentRun`](#cancelcurrentrun). + +::: warning EXPERIMENTAL +Since 4.0.10, `onCancel` returns a teardown function that will remove the listener. +::: + +## onClose + +```ts +function onClose(fn: () => Awaitable): void +``` + +Register a handler that will be called when the server is closed. + +## onTestsRerun + +```ts +function onTestsRerun(fn: OnTestsRerunHandler): void +``` + +Register a handler that will be called when the tests are rerunning. The tests can rerun when [`rerunTestSpecifications`](#reruntestspecifications) is called manually or when a file is changed and the built-in watcher schedules a rerun. + +## onFilterWatchedSpecification + +```ts +function onFilterWatchedSpecification( + fn: (specification: TestSpecification) => boolean +): void +``` +Register a handler that will be called when a file is changed. This callback should return `true` or `false`, indicating whether the test file needs to be rerun. + +With this method, you can hook into the default watcher logic to delay or discard tests that the user doesn't want to keep track of at the moment: + +```ts +const continuesTests: string[] = [] + +myCustomWrapper.onContinuesRunEnabled(testItem => + continuesTests.push(item.fsPath) +) + +vitest.onFilterWatchedSpecification(specification => + continuesTests.includes(specification.moduleId) +) +``` + +Vitest can create different specifications for the same file depending on the `pool` or `locations` options, so do not rely on the reference. Vitest can also return cached specification from [`vitest.getModuleSpecifications`](#getmodulespecifications) - the cache is based on the `moduleId` and `pool`. Note that [`project.createSpecification`](/api/advanced/test-project#createspecification) always returns a new instance. + +## matchesProjectFilter 3.1.0 {#matchesprojectfilter} + +```ts +function matchesProjectFilter(name: string): boolean +``` + +Check if the name matches the current [project filter](/guide/cli#project). If there is no project filter, this will always return `true`. + +It is not possible to programmatically change the `--project` CLI option. + +## waitForTestRunEnd 4.0.0 {#waitfortestrunend} + +```ts +function waitForTestRunEnd(): Promise +``` + +If there is a test run happening, returns a promise that will resolve when the test run is finished. + +## createCoverageProvider 4.0.0 {#createcoverageprovider} + +```ts +function createCoverageProvider(): Promise +``` + +Creates a coverage provider if `coverage` is enabled in the config. This is done automatically if you are running tests with [`start`](#start) or [`init`](#init) methods. + +::: warning +This method will also clean all previous reports if [`coverage.clean`](/config/#coverage-clean) is not set to `false`. +::: + +## enableCoverage 4.0.0 {#enablecoverage} + +```ts +function enableCoverage(): Promise +``` + +This method enables coverage for tests that run after this call. `enableCoverage` doesn't run any tests; it only sets up Vitest to collect coverage. + +It creates a new coverage provider if one doesn't already exist. + +## disableCoverage 4.0.0 {#disablecoverage} + +```ts +function disableCoverage(): void +``` + +This method disables coverage collection for tests that run afterwards. + +## getSeed 4.0.0 {#getseed} + +```ts +function getSeed(): number | null +``` + +Returns the seed, if tests are running in a random order. + +## experimental_parseSpecification 4.0.0 {#parsespecification} + +```ts +function experimental_parseSpecification( + specification: TestSpecification +): Promise +``` + +This function will collect all tests inside the file without running it. It uses rollup's `parseAst` function on top of Vite's `ssrTransform` to statically analyse the file and collect all tests that it can. + +::: warning +If Vitest could not analyse the name of the test, it will inject a `dynamic: true` property to the test or a suite. The `id` will also have a postfix with `-dynamic` to not break tests that were collected properly. + +Vitest always injects this property in tests with `for` or `each` modifier or tests with a dynamic name (like, `hello ${property}` or `'hello' + ${property}`). Vitest will still assign a name to the test, but it cannot be used to filter tests. + +There is nothing Vitest can do to make it possible to filter dynamic tests, but you can turn a test with `for` or `each` modifier into a name pattern with `escapeTestName` function: + +```ts +import { escapeTestName } from 'vitest/node' + +// turns into /hello, .+?/ +const escapedPattern = new RegExp(escapeTestName('hello, %s', true)) +``` +::: + +::: warning +Vitest will only collect tests defined in the file. It will never follow imports to other files. + +Vitest collects all `it`, `test`, `suite` and `describe` definitions even if they were not imported from the `vitest` entry point. +::: + +## experimental_parseSpecifications 4.0.0 {#parsespecifications} + +```ts +function experimental_parseSpecifications( + specifications: TestSpecification[], + options?: { + concurrency?: number + } +): Promise +``` + +This method will [collect tests](#parsespecification) from an array of specifications. By default, Vitest will run only `os.availableParallelism()` number of specifications at a time to reduce the potential performance degradation. You can specify a different number in a second argument. + +## experimental_clearCache 4.0.11 {#clearcache} + +```ts +function experimental_clearCache(): Promise +``` + +Deletes all Vitest caches, including [`experimental.fsModuleCache`](/config/experimental#experimental-fsmodulecache). + +## experimental_getSourceModuleDiagnostic 4.0.15 {#getsourcemodulediagnostic} + +```ts +export function experimental_getSourceModuleDiagnostic( + moduleId: string, + testModule?: TestModule, +): Promise +``` + +::: details Types +```ts +export interface ModuleDefinitionLocation { + line: number + column: number +} + +export interface SourceModuleLocations { + modules: ModuleDefinitionDiagnostic[] + untracked: ModuleDefinitionDiagnostic[] +} + +export interface ModuleDefinitionDiagnostic { + start: ModuleDefinitionLocation + end: ModuleDefinitionLocation + startIndex: number + endIndex: number + url: string + resolvedId: string +} + +export interface ModuleDefinitionDurationsDiagnostic extends ModuleDefinitionDiagnostic { + selfTime: number + totalTime: number + external?: boolean +} + +export interface UntrackedModuleDefinitionDiagnostic { + url: string + resolvedId: string + selfTime: number + totalTime: number + external?: boolean +} + +export interface SourceModuleDiagnostic { + modules: ModuleDefinitionDurationsDiagnostic[] + untrackedModules: UntrackedModuleDefinitionDiagnostic[] +} +``` +::: + +Returns module's diagnostic. If [`testModule`](/api/advanced/test-module) is not provided, `selfTime` and `totalTime` will be aggregated across all tests that were running the last time. If the module was not transformed or executed, the diagnostic will be empty. + +::: warning +At the moment, the [browser](/guide/browser/) modules are not supported. +::: diff --git a/api/assert-type.md b/api/assert-type.md index 973b9f6c..9491cd9c 100644 --- a/api/assert-type.md +++ b/api/assert-type.md @@ -1,13 +1,13 @@ # assertType -- **类型:** `(value: T): void` - -你可以使用此函数作为 [`expectTypeOf`](/api/expect-typeof) 的替代方法,以轻松地断言参数类型等于提供的泛型。 - ::: warning -在运行时,此函数不执行任何操作。要 [启用类型检查](/guide/testing-types#run-typechecking),不要忘记传递 `--typecheck` 标志。 +During runtime this function doesn't do anything. To [enable typechecking](/guide/testing-types#run-typechecking), don't forget to pass down `--typecheck` flag. ::: +- **Type:** `(value: T): void` + +You can use this function as an alternative for [`expectTypeOf`](/api/expect-typeof) to easily assert that the argument type is equal to the generic provided. + ```ts import { assertType } from 'vitest' @@ -17,6 +17,6 @@ function concat(a: string | number, b: string | number): string | number assertType(concat('a', 'b')) assertType(concat(1, 2)) -// @ts-expect-error 错误类型 +// @ts-expect-error wrong types assertType(concat('a', 2)) ``` diff --git a/api/assert.md b/api/assert.md index 8f484404..a7329a2b 100644 --- a/api/assert.md +++ b/api/assert.md @@ -1,12 +1,12 @@ # assert -Vitest 从 [`chai`](https://www.chaijs.com/api/assert/) 重新导出了 `assert` 方法,用于验证不变量。 +Vitest reexports the `assert` method from [`chai`](https://www.chaijs.com/api/assert/) for verifying invariants. ## assert -- **类型:** `(expression: any, message?: string) => asserts expression` +- **Type:** `(expression: any, message?: string) => asserts expression` -断言给定的 `expression` 是 true,否则断言失败。 +Assert that the given `expression` is truthy, otherwise the assertion fails. ```ts import { assert, test } from 'vitest' @@ -18,11 +18,11 @@ test('assert', () => { ## fail -- **类型:** +- **Type:** - `(message?: string) => never` - `(actual: T, expected: T, message?: string, operator?: string) => never` -强制断言失败。 +Force an assertion failure. ```ts import { assert, test } from 'vitest' @@ -35,10 +35,10 @@ test('assert.fail', () => { ## isOk -- **类型:** `(value: T, message?: string) => asserts value` +- **Type:** `(value: T, message?: string) => asserts value` - **Alias** `ok` -断言给定的 `value` 是 true 。 +Assert that the given `value` is truthy. ```ts import { assert, test } from 'vitest' @@ -51,10 +51,10 @@ test('assert.isOk', () => { ## isNotOk -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` - **Alias** `notOk` -断言给定的 `value` 是 false 。 +Assert that the given `value` is falsy. ```ts import { assert, test } from 'vitest' @@ -67,9 +67,9 @@ test('assert.isNotOk', () => { ## equal -- **类型:** `(actual: T, expected: T, message?: string) => void` +- **Type:** `(actual: T, expected: T, message?: string) => void` -断言 `actual` 和 `expected` 非严格相等 (==)。 +Asserts non-strict equality (==) of `actual` and `expected`. ```ts import { assert, test } from 'vitest' @@ -81,9 +81,9 @@ test('assert.equal', () => { ## notEqual -- **类型:** `(actual: T, expected: T, message?: string) => void` +- **Type:** `(actual: T, expected: T, message?: string) => void` -断言 `actual` 和 `expected` 非严格不等 (!=)。 +Asserts non-strict inequality (!=) of `actual` and `expected`. ```ts import { assert, test } from 'vitest' @@ -95,9 +95,9 @@ test('assert.equal', () => { ## strictEqual -- **类型:** `(actual: T, expected: T, message?: string) => void` +- **Type:** `(actual: T, expected: T, message?: string) => void` -断言 `actual` 和 `expected` 严格相等 (===)。 +Asserts strict equality (===) of `actual` and `expected`. ```ts import { assert, test } from 'vitest' @@ -109,9 +109,9 @@ test('assert.strictEqual', () => { ## deepEqual -- **类型:** `(actual: T, expected: T, message?: string) => void` +- **Type:** `(actual: T, expected: T, message?: string) => void` -断言 `actual` 深度等于 `expected` 。 +Asserts that `actual` is deeply equal to `expected`. ```ts import { assert, test } from 'vitest' @@ -123,9 +123,9 @@ test('assert.deepEqual', () => { ## notDeepEqual -- **类型:** `(actual: T, expected: T, message?: string) => void` +- **Type:** `(actual: T, expected: T, message?: string) => void` -断言 `actual` 不深度等于 `expected` 。 +Assert that `actual` is not deeply equal to `expected`. ```ts import { assert, test } from 'vitest' @@ -137,9 +137,9 @@ test('assert.notDeepEqual', () => { ## isAbove -- **类型:** `(valueToCheck: number, valueToBeAbove: number, message?: string) => void` +- **Type:** `(valueToCheck: number, valueToBeAbove: number, message?: string) => void` -断言 `valueToCheck` 严格大于 (>) `valueToBeAbove` 。 +Assert that `valueToCheck` is strictly greater than (>) `valueToBeAbove`. ```ts import { assert, test } from 'vitest' @@ -151,9 +151,9 @@ test('assert.isAbove', () => { ## isAtLeast -- **类型:** `(valueToCheck: number, valueToBeAtLeast: number, message?: string) => void` +- **Type:** `(valueToCheck: number, valueToBeAtLeast: number, message?: string) => void` -断言 `valueToCheck` 大于等于 (>=) `valueToBeAtLeast` 。 +Assert that `valueToCheck` is greater than or equal to (>=) `valueToBeAtLeast`. ```ts import { assert, test } from 'vitest' @@ -166,9 +166,9 @@ test('assert.isAtLeast', () => { ## isBelow -- **类型:** `(valueToCheck: number, valueToBeBelow: number, message?: string) => void` +- **Type:** `(valueToCheck: number, valueToBeBelow: number, message?: string) => void` -断言 `valueToCheck` 严格小于 (<) `valueToBeBelow` 。 +Asserts `valueToCheck` is strictly less than (<) `valueToBeBelow`. ```ts import { assert, test } from 'vitest' @@ -180,9 +180,9 @@ test('assert.isBelow', () => { ## isAtMost -- **类型:** `(valueToCheck: number, valueToBeAtMost: number, message?: string) => void` +- **Type:** `(valueToCheck: number, valueToBeAtMost: number, message?: string) => void` -断言 `valueToCheck` 小于等于 (<=) `valueToBeAtMost` 。 +Asserts `valueToCheck` is less than or equal to (<=) `valueToBeAtMost`. ```ts import { assert, test } from 'vitest' @@ -195,9 +195,9 @@ test('assert.isAtMost', () => { ## isTrue -- **类型:** `(value: T, message?: string) => asserts value is true` +- **Type:** `(value: T, message?: string) => asserts value is true` -断言 `value` 是 true 。 +Asserts that `value` is true. ```ts import { assert, test } from 'vitest' @@ -211,9 +211,9 @@ test('assert.isTrue', () => { ## isNotTrue -- **类型:** `(value: T, message?: string) => asserts value is Exclude` +- **Type:** `(value: T, message?: string) => asserts value is Exclude` -断言 `value` 不是 true 。 +Asserts that `value` is not true. ```ts import { assert, test } from 'vitest' @@ -227,9 +227,9 @@ test('assert.isNotTrue', () => { ## isFalse -- **类型:** `(value: T, message?: string) => asserts value is false` +- **Type:** `(value: T, message?: string) => asserts value is false` -断言 `value` 是 false 。 +Asserts that `value` is false. ```ts import { assert, test } from 'vitest' @@ -243,9 +243,9 @@ test('assert.isFalse', () => { ## isNotFalse -- **类型:** `(value: T, message?: string) => asserts value is Exclude` +- **Type:** `(value: T, message?: string) => asserts value is Exclude` -断言 `value` 不是 false 。 +Asserts that `value` is not false. ```ts import { assert, test } from 'vitest' @@ -259,9 +259,9 @@ test('assert.isNotFalse', () => { ## isNull -- **类型:** `(value: T, message?: string) => asserts value is null` +- **Type:** `(value: T, message?: string) => asserts value is null` -断言 `value` 是 null 。 +Asserts that `value` is null. ```ts import { assert, test } from 'vitest' @@ -275,9 +275,9 @@ test('assert.isNull', () => { ## isNotNull -- **类型:** `(value: T, message?: string) => asserts value is Exclude` +- **Type:** `(value: T, message?: string) => asserts value is Exclude` -断言 `value` 不是 null 。 +Asserts that `value` is not null. ```ts import { assert, test } from 'vitest' @@ -291,9 +291,9 @@ test('assert.isNotNull', () => { ## isNaN -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是 NaN 。 +Asserts that `value` is NaN. ```ts import { assert, test } from 'vitest' @@ -307,9 +307,9 @@ test('assert.isNaN', () => { ## isNotNaN -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是 NaN 。 +Asserts that `value` is not NaN. ```ts import { assert, test } from 'vitest' @@ -323,9 +323,9 @@ test('assert.isNotNaN', () => { ## exists -- **类型:** `(value: T, message?: string) => asserts value is NonNullable` +- **Type:** `(value: T, message?: string) => asserts value is NonNullable` -断言 `value` 既不是 null 也不是 undefined 。 +Asserts that `value` is neither null nor undefined. ```ts import { assert, test } from 'vitest' @@ -339,9 +339,9 @@ test('assert.exists', () => { ## notExists -- **类型:** `(value: T, message?: string) => asserts value is null | undefined` +- **Type:** `(value: T, message?: string) => asserts value is null | undefined` -断言 `value` 是 null 或 undefined 。 +Asserts that `value` is either null nor undefined. ```ts import { assert, test } from 'vitest' @@ -357,9 +357,9 @@ test('assert.notExists', () => { ## isUndefined -- **类型:** `(value: T, message?: string) => asserts value is undefined` +- **Type:** `(value: T, message?: string) => asserts value is undefined` -断言 `value` 是 undefined 。 +Asserts that `value` is undefined. ```ts import { assert, test } from 'vitest' @@ -373,9 +373,9 @@ test('assert.isUndefined', () => { ## isDefined -- **类型:** `(value: T, message?: string) => asserts value is Exclude` +- **Type:** `(value: T, message?: string) => asserts value is Exclude` -断言 `value` 不是 undefined 。 +Asserts that `value` is not undefined. ```ts import { assert, test } from 'vitest' @@ -389,16 +389,14 @@ test('assert.isDefined', () => { ## isFunction -- **类型:** `(value: T, message?: string) => void` -- **别名:** `isCallable` - 断言 `value` 是一个函数。 +- **Type:** `(value: T, message?: string) => void` +- **Alias:** `isCallable` +Asserts that `value` is a function. ```ts import { assert, test } from 'vitest' -function name() { - return 'foo' -} +function name() { return 'foo' }; test('assert.isFunction', () => { assert.isFunction(name, 'name is function') @@ -407,10 +405,10 @@ test('assert.isFunction', () => { ## isNotFunction -- **类型:** `(value: T, message?: string) => void` -- **别名:** `isNotCallable` +- **Type:** `(value: T, message?: string) => void` +- **Alias:** `isNotCallable` -断言 `value` 不是一个函数。 +Asserts that `value` is not a function. ```ts import { assert, test } from 'vitest' @@ -424,9 +422,9 @@ test('assert.isNotFunction', () => { ## isObject -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个类型为 Object 的对象 (由 Object.prototype.toString 确定)。 此断言不匹配子类对象。 +Asserts that `value` is an object of type Object (as revealed by Object.prototype.toString). The assertion does not match subclassed objects. ```ts import { assert, test } from 'vitest' @@ -440,9 +438,9 @@ test('assert.isObject', () => { ## isNotObject -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是一个类型为 Object 的对象 (如 Object.prototype.toString 确定)。 该断言不匹配子类对象。 +Asserts that `value` is not an object of type Object (as revealed by Object.prototype.toString). The assertion does not match subclassed objects. ```ts import { assert, test } from 'vitest' @@ -456,9 +454,9 @@ test('assert.isNotObject', () => { ## isArray -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个数组。 +Asserts that `value` is an array. ```ts import { assert, test } from 'vitest' @@ -472,9 +470,9 @@ test('assert.isArray', () => { ## isNotArray -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是一个数组。 +Asserts that `value` is not an array. ```ts import { assert, test } from 'vitest' @@ -488,9 +486,9 @@ test('assert.isNotArray', () => { ## isString -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个字符串。 +Asserts that `value` is a string. ```ts import { assert, test } from 'vitest' @@ -504,9 +502,9 @@ test('assert.isString', () => { ## isNotString -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是一个字符串。 +Asserts that `value` is not a string. ```ts import { assert, test } from 'vitest' @@ -520,9 +518,9 @@ test('assert.isNotString', () => { ## isNumber -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个数字。 +Asserts that `value` is a number. ```ts import { assert, test } from 'vitest' @@ -536,9 +534,9 @@ test('assert.isNumber', () => { ## isNotNumber -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是一个数字。 +Asserts that `value` is not a number. ```ts import { assert, test } from 'vitest' @@ -552,9 +550,9 @@ test('assert.isNotNumber', () => { ## isFinite -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个有限数字。(不是 NaN, Infinity)。 +Asserts that `value` is a finite number (not NaN, Infinity). ```ts import { assert, test } from 'vitest' @@ -568,9 +566,9 @@ test('assert.isFinite', () => { ## isBoolean -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 是一个布尔值。 +Asserts that `value` is a boolean. ```ts import { assert, test } from 'vitest' @@ -584,9 +582,9 @@ test('assert.isBoolean', () => { ## isNotBoolean -- **类型:** `(value: T, message?: string) => void` +- **Type:** `(value: T, message?: string) => void` -断言 `value` 不是一个布尔值。 +Asserts that `value` is not a boolean. ```ts import { assert, test } from 'vitest' @@ -600,9 +598,9 @@ test('assert.isBoolean', () => { ## typeOf -- **类型:** `(value: T, name: string, message?: string) => void` +- **Type:** `(value: T, name: string, message?: string) => void` -断言 `value` 的类型是 `name`, 由 Object.prototype.toString 确定。 +Asserts that `value`’s type is `name`, as determined by Object.prototype.toString. ```ts import { assert, test } from 'vitest' @@ -619,9 +617,9 @@ test('assert.typeOf', () => { ## notTypeOf -- **类型:** `(value: T, name: string, message?: string) => void` +- **Type:** `(value: T, name: string, message?: string) => void` -断言 `value` 的类型不是 `name`,由 Object.prototype.toString 确定。 +Asserts that `value`’s type is not `name`, as determined by Object.prototype.toString. ```ts import { assert, test } from 'vitest' @@ -633,16 +631,14 @@ test('assert.notTypeOf', () => { ## instanceOf -- **类型:** `(value: T, constructor: Function, message?: string) => asserts value is T` +- **Type:** `(value: T, constructor: Function, message?: string) => asserts value is T` -断言 `value` 是 `constructor` 的实例。 +Asserts that `value` is an instance of `constructor`. ```ts import { assert, test } from 'vitest' -function Person(name) { - this.name = name -} +function Person(name) { this.name = name } const foo = new Person('foo') class Tea { @@ -660,16 +656,14 @@ test('assert.instanceOf', () => { ## notInstanceOf -- **类型:** `(value: T, constructor: Function, message?: string) => asserts value is Exclude` +- **Type:** `(value: T, constructor: Function, message?: string) => asserts value is Exclude` -断言 `value` 不是 `constructor` 的实例。 +Asserts that `value` is not an instance of `constructor`. ```ts import { assert, test } from 'vitest' -function Person(name) { - this.name = name -} +function Person(name) { this.name = name } const foo = new Person('foo') class Tea { @@ -686,13 +680,13 @@ test('assert.instanceOf', () => { ## include -- **类型:** +- **Type:** - `(haystack: string, needle: string, message?: string) => void` - `(haystack: readonly T[] | ReadonlySet | ReadonlyMap, needle: T, message?: string) => void` - `(haystack: WeakSet, needle: T, message?: string) => void` - `(haystack: T, needle: Partial, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言数组中是否包含一个值、字符串中是否包含一个子字符串、或者对象中是否包含一组属性。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a value in an array, a substring in a string, or a subset of properties in an object. ```ts import { assert, test } from 'vitest' @@ -700,23 +694,19 @@ import { assert, test } from 'vitest' test('assert.include', () => { assert.include([1, 2, 3], 2, 'array contains value') assert.include('foobar', 'foo', 'string contains substring') - assert.include( - { foo: 'bar', hello: 'universe' }, - { foo: 'bar' }, - 'object contains property' - ) + assert.include({ foo: 'bar', hello: 'universe' }, { foo: 'bar' }, 'object contains property') }) ``` ## notInclude -- **类型:** +- **Type:** - `(haystack: string, needle: string, message?: string) => void` - `(haystack: readonly T[] | ReadonlySet | ReadonlyMap, needle: T, message?: string) => void` - `(haystack: WeakSet, needle: T, message?: string) => void` - `(haystack: T, needle: Partial, message?: string) => void` -断言 `haystack` 不包含 `needle` 。可以用来断言数组中是否不包含一个值、字符串中是否不包含一个子字符串、或者对象中是否不包含一组属性。 +Asserts that `haystack` does not include `needle`. It can be used to assert the absence of a value in an array, a substring in a string, or a subset of properties in an object. ```ts import { assert, test } from 'vitest' @@ -730,12 +720,12 @@ test('assert.notInclude', () => { ## deepInclude -- **类型:** +- **Type:** - `(haystack: string, needle: string, message?: string) => void` - `(haystack: readonly T[] | ReadonlySet | ReadonlyMap, needle: T, message?: string) => void` - `(haystack: T, needle: T extends WeakSet ? never : Partial, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言数组中是否包含一个值或对象中是否包含一组属性。使用深度相等。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a value in an array or a subset of properties in an object. Deep equality is used. ```ts import { assert, test } from 'vitest' @@ -751,12 +741,12 @@ test('assert.deepInclude', () => { ## notDeepInclude -- **类型:** +- **Type:** - `(haystack: string, needle: string, message?: string) => void` - `(haystack: readonly T[] | ReadonlySet | ReadonlyMap, needle: T, message?: string) => void` - `(haystack: T, needle: T extends WeakSet ? never : Partial, message?: string) => void` -断言 `haystack` 不包含 `needle` 。可以用来断言数组中是否不包含一个值或对象中是否不包含一组属性。使用深度相等。 +Asserts that `haystack` does not include `needle`. It can be used to assert the absence of a value in an array or a subset of properties in an object. Deep equality is used. ```ts import { assert, test } from 'vitest' @@ -772,9 +762,9 @@ test('assert.notDeepInclude', () => { ## nestedInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 包含 `needle` 。 可以用来断言对象中是否包含一组属性。允许使用点和括号表示法来引用嵌套属性。属性名中的 ‘[]’ 和 ‘.’ 可以使用双反斜杠转义。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a subset of properties in an object. Enables the use of dot- and bracket-notation for referencing nested properties. ‘[]’ and ‘.’ in property names can be escaped using double backslashes. ```ts import { assert, test } from 'vitest' @@ -787,9 +777,9 @@ test('assert.nestedInclude', () => { ## notNestedInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 不包含 `needle` 。可以用来断言对象中是否不包含一组属性。允许使用点和括号表示法来引用嵌套属性。属性名中的 ‘[]’ 和 ‘.’ 可以使用双反斜杠转义。 +Asserts that `haystack` does not include `needle`. Can be used to assert the inclusion of a subset of properties in an object. Enables the use of dot- and bracket-notation for referencing nested properties. ‘[]’ and ‘.’ in property names can be escaped using double backslashes. ```ts import { assert, test } from 'vitest' @@ -802,45 +792,39 @@ test('assert.nestedInclude', () => { ## deepNestedInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言对象中是否包含一组属性,同时检查深度相等性。允许使用点和括号表示法来引用嵌套属性。属性名中的 ‘[]’ 和 ‘.’ 可以使用双反斜杠转义。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a subset of properties in an object while checking for deep equality. Enables the use of dot- and bracket-notation for referencing nested properties. ‘[]’ and ‘.’ in property names can be escaped using double backslashes. ```ts import { assert, test } from 'vitest' test('assert.deepNestedInclude', () => { assert.deepNestedInclude({ a: { b: [{ x: 1 }] } }, { 'a.b[0]': { x: 1 } }) - assert.deepNestedInclude( - { '.a': { '[b]': { x: 1 } } }, - { '\\.a.\\[b\\]': { x: 1 } } - ) + assert.deepNestedInclude({ '.a': { '[b]': { x: 1 } } }, { '\\.a.\\[b\\]': { x: 1 } }) }) ``` ## notDeepNestedInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 不包含 `needle` 。可以用来断言对象中是否不包含一组属性,同时检查深度相等性。允许使用点和括号表示法来引用嵌套属性。属性名中的 ‘[]’ 和 ‘.’ 可以使用双反斜杠转义。 +Asserts that `haystack` not includes `needle`. Can be used to assert the absence of a subset of properties in an object while checking for deep equality. Enables the use of dot- and bracket-notation for referencing nested properties. ‘[]’ and ‘.’ in property names can be escaped using double backslashes. ```ts import { assert, test } from 'vitest' test('assert.notDeepNestedInclude', () => { assert.notDeepNestedInclude({ a: { b: [{ x: 1 }] } }, { 'a.b[0]': { y: 1 } }) - assert.notDeepNestedInclude( - { '.a': { '[b]': { x: 1 } } }, - { '\\.a.\\[b\\]': { y: 2 } } - ) + assert.notDeepNestedInclude({ '.a': { '[b]': { x: 1 } } }, { '\\.a.\\[b\\]': { y: 2 } }) }) ``` ## ownInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言对象中是否包含一组属性,同时忽略继承的属性。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a subset of properties in an object while ignoring inherited properties. ```ts import { assert, test } from 'vitest' @@ -852,15 +836,15 @@ test('assert.ownInclude', () => { ## notOwnInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言对象中是否不包含一组属性,同时忽略继承的属性 +Asserts that `haystack` includes `needle`. Can be used to assert the absence of a subset of properties in an object while ignoring inherited properties. ```ts import { assert, test } from 'vitest' const obj1 = { - b: 2, + b: 2 } const obj2 = object.create(obj1) @@ -873,9 +857,9 @@ test('assert.notOwnInclude', () => { ## deepOwnInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 包含 `needle` 。可以用来断言对象中是否包含一组属性,同时忽略继承的属性并检查深度相等性。 +Asserts that `haystack` includes `needle`. Can be used to assert the inclusion of a subset of properties in an object while ignoring inherited properties and checking for deep equality. ```ts import { assert, test } from 'vitest' @@ -887,9 +871,9 @@ test('assert.deepOwnInclude', () => { ## notDeepOwnInclude -- **类型:** `(haystack: any, needle: any, message?: string) => void` +- **Type:** `(haystack: any, needle: any, message?: string) => void` -断言 `haystack` 不包含 `needle` 。可以用来断言对象中是否不包含一组属性,同时忽略继承的属性并检查深度相等性。 +Asserts that `haystack` not includes `needle`. Can be used to assert the absence of a subset of properties in an object while ignoring inherited properties and checking for deep equality. ```ts import { assert, test } from 'vitest' @@ -901,9 +885,9 @@ test('assert.notDeepOwnInclude', () => { ## match -- **类型:** `(value: string, regexp: RegExp, message?: string) => void` +- **Type:** `(value: string, regexp: RegExp, message?: string) => void` -断言 `value` 匹配正则表达式 `regexp` 。 +Asserts that `value` matches the regular expression `regexp`. ```ts import { assert, test } from 'vitest' @@ -915,9 +899,9 @@ test('assert.match', () => { ## notMatch -- **类型:** `(value: string, regexp: RegExp, message?: string) => void` +- **Type:** `(value: string, regexp: RegExp, message?: string) => void` -断言 `value` 不匹配正则表达式 `regexp` 。 +Asserts that `value` does not matches the regular expression `regexp`. ```ts import { assert, test } from 'vitest' @@ -929,9 +913,9 @@ test('assert.notMatch', () => { ## property -- **类型:** `(object: T, property: string, message?: string) => void` +- **Type:** `(object: T, property: string, message?: string) => void` -断言 `object` 具有由 `property` 指定的直接或继承属性。 +Asserts that `object` has a direct or inherited property named by `property` ```ts import { assert, test } from 'vitest' @@ -944,9 +928,9 @@ test('assert.property', () => { ## notProperty -- **类型:** `(object: T, property: string, message?: string) => void` +- **Type:** `(object: T, property: string, message?: string) => void` -断言 `object` 没有由 `property` 指定的直接或继承属性。 +Asserts that `object` does not have a direct or inherited property named by `property` ```ts import { assert, test } from 'vitest' @@ -958,9 +942,9 @@ test('assert.notProperty', () => { ## propertyVal -- **类型:** `(object: T, property: string, value: V, message?: string) => void` +- **Type:** `(object: T, property: string, value: V, message?: string) => void` -断言 `object` 具有由 `property` 指定的直接或继承属性,其值为 `value` 。使用严格相等检查(===)。 +Asserts that `object` has a direct or inherited property named by `property` with a value given by `value`. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -972,9 +956,9 @@ test('assert.notPropertyVal', () => { ## notPropertyVal -- **类型:** `(object: T, property: string, value: V, message?: string) => void` +- **Type:** `(object: T, property: string, value: V, message?: string) => void` -断言 `object` 没有由 `property` 指定的直接或继承属性,其值为 `value` 。使用严格相等检查(===)。 +Asserts that `object` does not have a direct or inherited property named by `property` with a value given by `value`. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -987,47 +971,39 @@ test('assert.notPropertyVal', () => { ## deepPropertyVal -- **类型:** `(object: T, property: string, value: V, message?: string) => void` +- **Type:** `(object: T, property: string, value: V, message?: string) => void` -断言 `object` 具有由 `property` 指定的直接或继承属性,其值为 `value` 。使用深度相等检查。 +Asserts that `object` has a direct or inherited property named by `property` with a value given by `value`. Uses a deep equality check. ```ts import { assert, test } from 'vitest' test('assert.deepPropertyVal', () => { - assert.deepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { - green: 'matcha', - }) + assert.deepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'matcha' }) }) ``` ## notDeepPropertyVal -- **类型:** `(object: T, property: string, value: V, message?: string) => void` +- **Type:** `(object: T, property: string, value: V, message?: string) => void` -断言 `object` 没有由 `property` 指定的直接或继承属性,其值为 `value` 。使用深度相等检查。 +Asserts that `object` does not have a direct or inherited property named by `property` with a value given by `value`. Uses a deep equality check. ```ts import { assert, test } from 'vitest' test('assert.deepPropertyVal', () => { - assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { - black: 'matcha', - }) - assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { - green: 'oolong', - }) - assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { - green: 'matcha', - }) + assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { black: 'matcha' }) + assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'oolong' }) + assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { green: 'matcha' }) }) ``` ## nestedProperty -- **类型:** `(object: T, property: string, message?: string) => void` +- **Type:** `(object: T, property: string, message?: string) => void` -断言 `object` 具有由 `property` 指定的直接或继承属性,它可以是一个字符串,使用点和括号表示法来引用嵌套的引用。 +Asserts that `object` has a direct or inherited property named by `property`, which can be a string using dot- and bracket-notation for nested reference. ```ts import { assert, test } from 'vitest' @@ -1039,9 +1015,9 @@ test('assert.deepPropertyVal', () => { ## notNestedProperty -- **类型:** `(object: T, property: string, message?: string) => void` +- **Type:** `(object: T, property: string, message?: string) => void` -断言 `object` 没有由 `property` 指定的属性,它可以是一个字符串,使用点和括号表示法来引用嵌套的引用。该属性不能存在于对象上,也不能存在于其原型链中的任何地方。 +Asserts that `object` does not have a direct or inherited property named by `property`, which can be a string using dot- and bracket-notation for nested reference. ```ts import { assert, test } from 'vitest' @@ -1053,9 +1029,9 @@ test('assert.deepPropertyVal', () => { ## nestedPropertyVal -- **类型:** `(object: T, property: string, value: any, message?: string) => void` +- **Type:** `(object: T, property: string, value: any, message?: string) => void` -断言 `object` 具有由 `property` 指定的属性,其值为 `value` 给出。 `property` 可以使用点和方括号表示法进行嵌套引用。使用严格相等检查 (===)。 +Asserts that `object` has a property named by `property` with value given by `value`. `property` can use dot- and bracket-notation for nested reference. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1067,9 +1043,9 @@ test('assert.nestedPropertyVal', () => { ## notNestedPropertyVal -- **类型:** `(object: T, property: string, value: any, message?: string) => void` +- **Type:** `(object: T, property: string, value: any, message?: string) => void` -断言 `object` 没有由 `property` 指定的属性,其值为 `value` 给出。 `property` 可以使用点和方括号表示法进行嵌套引用。使用严格相等检查 (===)。 +Asserts that `object` does not have a property named by `property` with value given by `value`. `property` can use dot- and bracket-notation for nested reference. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1082,9 +1058,9 @@ test('assert.notNestedPropertyVal', () => { ## deepNestedPropertyVal -- **类型:** `(object: T, property: string, value: any, message?: string) => void` +- **Type:** `(object: T, property: string, value: any, message?: string) => void` -断言 `object` 具有由 `property` 指定的属性,其值为 `value` 给出。 `property` 可以使用点和方括号表示法进行嵌套引用。使用深度相等检查。 +Asserts that `object` has a property named by `property` with a value given by `value`. `property` can use dot- and bracket-notation for nested reference. Uses a deep equality check (===). ```ts import { assert, test } from 'vitest' @@ -1097,9 +1073,9 @@ test('assert.notNestedPropertyVal', () => { ## notDeepNestedPropertyVal -- **类型:** `(object: T, property: string, value: any, message?: string) => void` +- **Type:** `(object: T, property: string, value: any, message?: string) => void` -断言 `object` 没有由 `property` 指定的属性,其值为 `value` 给出。 `property` 可以使用点和方括号表示法进行嵌套引用。使用深度相等检查。 +Asserts that `object` does not have a property named by `property` with value given by `value`. `property` can use dot- and bracket-notation for nested reference. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1113,9 +1089,9 @@ test('assert.notDeepNestedPropertyVal', () => { ## lengthOf -- **类型:** `(object: T, length: number, message?: string) => void` +- **Type:** `(object: T, length: number, message?: string) => void` -断言 `object` 具有预期的 `length` 或 `size` 值。 +Asserts that `object` has a `length` or `size` with the expected value. ```ts import { assert, test } from 'vitest' @@ -1130,9 +1106,9 @@ test('assert.lengthOf', () => { ## hasAnyKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 至少拥有一个提供的 `keys` 。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` has at least one of the `keys` provided. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' @@ -1140,16 +1116,16 @@ import { assert, test } from 'vitest' test('assert.hasAnyKeys', () => { assert.hasAnyKeys({ foo: 1, bar: 2, baz: 3 }, ['foo', 'iDontExist', 'baz']) assert.hasAnyKeys({ foo: 1, bar: 2, baz: 3 }, { foo: 30, iDontExist: 99, baz: 1337 }) - assert.hasAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ foo: 1 }, 'key']) + assert.hasAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ foo: 1 }, 'key']) assert.hasAnyKeys(new Set([{ foo: 'bar' }, 'anotherKey']), [{ foo: 'bar' }, 'anotherKey']) }) ``` ## hasAllKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 拥有且仅拥有所有提供的 `keys` 。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` has all and only all of the `keys` provided. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' @@ -1157,16 +1133,16 @@ import { assert, test } from 'vitest' test('assert.hasAllKeys', () => { assert.hasAllKeys({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar', 'baz']) assert.hasAllKeys({ foo: 1, bar: 2, baz: 3 }, { foo: 30, bar: 99, baz: 1337 }) - assert.hasAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ foo: 1 }, 'key']) + assert.hasAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ foo: 1 }, 'key']) assert.hasAllKeys(new Set([{ foo: 'bar' }, 'anotherKey'], [{ foo: 'bar' }, 'anotherKey'])) }) ``` ## containsAllKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 拥有所有提供的 `keys`,但可能还有更多未列出的键。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` has all of the `keys` provided but may have more keys not listed. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' @@ -1176,8 +1152,8 @@ test('assert.containsAllKeys', () => { assert.containsAllKeys({ foo: 1, bar: 2, baz: 3 }, ['foo', 'bar', 'baz']) assert.containsAllKeys({ foo: 1, bar: 2, baz: 3 }, { foo: 30, baz: 1337 }) assert.containsAllKeys({ foo: 1, bar: 2, baz: 3 }, { foo: 30, bar: 99, baz: 1337 }) - assert.containsAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ foo: 1 }]) - assert.containsAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ foo: 1 }, 'key']) + assert.containsAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ foo: 1 }]) + assert.containsAllKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ foo: 1 }, 'key']) assert.containsAllKeys(new Set([{ foo: 'bar' }, 'anotherKey'], [{ foo: 'bar' }])) assert.containsAllKeys(new Set([{ foo: 'bar' }, 'anotherKey'], [{ foo: 'bar' }, 'anotherKey'])) }) @@ -1185,135 +1161,135 @@ test('assert.containsAllKeys', () => { ## doesNotHaveAnyKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 不拥有任何提供的 `keys` 。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` has none of the `keys` provided. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.doesNotHaveAnyKeys', () => { - assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, ['one', 'two', 'example',]) + assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, ['one', 'two', 'example']) assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, { one: 1, two: 2, example: 'foo' }) - assert.doesNotHaveAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ one: 'two' }, 'example']) + assert.doesNotHaveAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ one: 'two' }, 'example']) assert.doesNotHaveAnyKeys(new Set([{ foo: 'bar' }, 'anotherKey'], [{ one: 'two' }, 'example'])) }) ``` ## doesNotHaveAllKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 至少不拥有一个提供的 `keys` 。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` does not have at least one of the `keys` provided. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.hasAnyKeys', () => { - assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, ['one', 'two', 'example',]) + assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, ['one', 'two', 'example']) assert.doesNotHaveAnyKeys({ foo: 1, bar: 2, baz: 3 }, { one: 1, two: 2, example: 'foo' }) - assert.doesNotHaveAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value'],]), [{ one: 'two' }, 'example']) - assert.doesNotHaveAnyKeys(new Set([{ foo: 'bar' }, 'anotherKey']), [{ one: 'two' }, 'example',]) + assert.doesNotHaveAnyKeys(new Map([[{ foo: 1 }, 'bar'], ['key', 'value']]), [{ one: 'two' }, 'example']) + assert.doesNotHaveAnyKeys(new Set([{ foo: 'bar' }, 'anotherKey']), [{ one: 'two' }, 'example']) }) ``` ## hasAnyDeepKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 至少拥有一个提供的 `keys` 。由于 Set 和 Map 可以拥有对象作为键,你可以使用这个断言来进行深度比较。你也可以提供一个单独的对象而不是一个 keys 数组,它的键将被用作预期的键集。 +Asserts that `object` has at least one of the `keys` provided. Since Sets and Maps can have objects as keys you can use this assertion to perform a deep comparison. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.hasAnyDeepKeys', () => { - assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2],]), { one: 'one' }) - assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2],]), [{ one: 'one' }, { two: 'two' }]) - assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo'],]), [{ one: 'one' }, { two: 'two' }]) - assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { one: 'one', }) - assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { three: 'three' },]) - assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' },]) + assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2]]), { one: 'one' }) + assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2]]), [{ one: 'one' }, { two: 'two' }]) + assert.hasAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo']]), [{ one: 'one' }, { two: 'two' }]) + assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { one: 'one' }) + assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { three: 'three' }]) + assert.hasAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' }]) }) ``` ## hasAllDeepKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 拥有且仅拥有所有提供的 `keys` 。由于 Set 和 Map 可以拥有对象作为键,你可以使用这个断言来进行深度比较。你也可以提供一个单独的对象而不是一个 keys 数组,它的键将被用作预期的键集。 +Asserts that `object` has all and only all of the `keys` provided. Since Sets and Maps can have objects as keys you can use this assertion to perform a deep comparison. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.hasAnyDeepKeys', () => { - assert.hasAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne']]), { one: 'one', }) - assert.hasAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo'],]), [{ one: 'one' }, { two: 'two' }]) + assert.hasAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne']]), { one: 'one' }) + assert.hasAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo']]), [{ one: 'one' }, { two: 'two' }]) assert.hasAllDeepKeys(new Set([{ one: 'one' }]), { one: 'one' }) - assert.hasAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' },]) + assert.hasAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' }]) }) ``` ## containsAllDeepKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 包含所有提供的 `keys` 。由于 Set 和 Map 可以拥有对象作为键,你可以使用这个断言来进行深度比较。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` contains all of the `keys` provided. Since Sets and Maps can have objects as keys you can use this assertion to perform a deep comparison. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.containsAllDeepKeys', () => { - assert.containsAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2],]), { one: 'one' }) - assert.containsAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo'],]), [{ one: 'one' }, { two: 'two' }]) - assert.containsAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { one: 'one', }) - assert.containsAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' },]) + assert.containsAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2]]), { one: 'one' }) + assert.containsAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo']]), [{ one: 'one' }, { two: 'two' }]) + assert.containsAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { one: 'one' }) + assert.containsAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { two: 'two' }]) }) ``` ## doesNotHaveAnyDeepKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 不拥有任何提供的 `keys` 。由于 Set 和 Map 可以拥有对象作为键,你可以使用这个断言来进行深度比较。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` has none of the `keys` provided. Since Sets and Maps can have objects as keys you can use this assertion to perform a deep comparison. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.doesNotHaveAnyDeepKeys', () => { - assert.doesNotHaveAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2],]), { thisDoesNot: 'exist' }) - assert.doesNotHaveAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo'],]), [{ twenty: 'twenty' }, { fifty: 'fifty' }]) - assert.doesNotHaveAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { twenty: 'twenty', }) - assert.doesNotHaveAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ twenty: 'twenty' }, { fifty: 'fifty' },]) + assert.doesNotHaveAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2]]), { thisDoesNot: 'exist' }) + assert.doesNotHaveAnyDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo']]), [{ twenty: 'twenty' }, { fifty: 'fifty' }]) + assert.doesNotHaveAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { twenty: 'twenty' }) + assert.doesNotHaveAnyDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ twenty: 'twenty' }, { fifty: 'fifty' }]) }) ``` ## doesNotHaveAllDeepKeys -- **类型:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` +- **Type:** `(object: T, keys: Array | { [key: string]: any }, message?: string) => void` -断言 `object` 至少不拥有一个提供的 `keys` 。由于 Set 和 Map 可以拥有对象作为键,你可以使用这个断言来进行深度比较。你也可以提供一个单独的对象而不是一个 `keys` 数组,它的键将被用作预期的键集。 +Asserts that `object` does not have at least one of the `keys` provided. Since Sets and Maps can have objects as keys you can use this assertion to perform a deep comparison. You can also provide a single object instead of a keys array and its keys will be used as the expected set of keys. ```ts import { assert, test } from 'vitest' test('assert.doesNotHaveAllDeepKeys', () => { - assert.doesNotHaveAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2],]), { thisDoesNot: 'exist' }) - assert.doesNotHaveAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo'],]), [{ twenty: 'twenty' }, { one: 'one' }]) - assert.doesNotHaveAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { twenty: 'twenty', }) - assert.doesNotHaveAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { fifty: 'fifty' },]) + assert.doesNotHaveAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [1, 2]]), { thisDoesNot: 'exist' }) + assert.doesNotHaveAllDeepKeys(new Map([[{ one: 'one' }, 'valueOne'], [{ two: 'two' }, 'valueTwo']]), [{ twenty: 'twenty' }, { one: 'one' }]) + assert.doesNotHaveAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), { twenty: 'twenty' }) + assert.doesNotHaveAllDeepKeys(new Set([{ one: 'one' }, { two: 'two' }]), [{ one: 'one' }, { fifty: 'fifty' }]) }) ``` ## throws -- **类型:** +- **Type:** - `(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string) => void` - `(fn: () => void, errorLike?: ErrorConstructor | Error | null, errMsgMatcher?: RegExp | string | null, message?: string) => void` -- **别名:** +- **Alias:** - `throw` - `Throw` -如果 `errorLike` 是一个 Error 构造函数,则断言 `fn` 将抛出一个 errorLike 实例的错误。如果 `errorLike` 是一个 Error 实例,则断言抛出的错误与 `errorLike` 是同一个实例。如果提供了 `errMsgMatcher`,它还断言抛出的错误将具有与 `errMsgMatcher` 相匹配的消息。 +If `errorLike` is an Error constructor, asserts that `fn` will throw an error that is an instance of `errorLike`. If errorLike is an Error instance, asserts that the error thrown is the same instance as `errorLike`. If `errMsgMatcher` is provided, it also asserts that the error thrown will have a message matching `errMsgMatcher`. ```ts import { assert, test } from 'vitest' @@ -1332,10 +1308,10 @@ test('assert.throws', () => { ## doesNotThrow -- **类型:** `(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string) => void` -- **类型:** `(fn: () => void, errorLike?: ErrorConstructor | Error | null, errMsgMatcher?: RegExp | string | null, message?: string) => void` +- **Type:** `(fn: () => void, errMsgMatcher?: RegExp | string, ignored?: any, message?: string) => void` +- **Type:** `(fn: () => void, errorLike?: ErrorConstructor | Error | null, errMsgMatcher?: RegExp | string | null, message?: string) => void` -如果 `errorLike` 是一个 Error 构造函数,则断言 `fn` 不会 抛出一个 errorLike 实例的错误。如果 `errorLike` 是一个 Error 实例,则断言抛出的错误不是与 errorLike 是同一个实例。如果提供了 `errMsgMatcher`,它还断言抛出的错误不会 具有与 `errMsgMatcher` 相匹配的消息。 +If `errorLike` is an Error constructor, asserts that `fn` will not throw an error that is an instance of `errorLike`. If errorLike is an Error instance, asserts that the error thrown is not the same instance as `errorLike`. If `errMsgMatcher` is provided, it also asserts that the error thrown will not have a message matching `errMsgMatcher`. ```ts import { assert, test } from 'vitest' @@ -1354,9 +1330,9 @@ test('assert.doesNotThrow', () => { ## operator -- **类型:** `(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string) => void` +- **Type:** `(val1: OperatorComparable, operator: Operator, val2: OperatorComparable, message?: string) => void` -使用 `operator` 比较 `val1` 和 `val2` 。 +Compare `val1` and `val2` using `operator`. ```ts import { assert, test } from 'vitest' @@ -1368,10 +1344,10 @@ test('assert.operator', () => { ## closeTo -- **类型:** `(actual: number, expected: number, delta: number, message?: string) => void` -- **别名:** `approximately` +- **Type:** `(actual: number, expected: number, delta: number, message?: string) => void` +- **Alias:** `approximately` -断言 `actual` 等于 `expected`,误差范围控制在 +/- `delta` 内。 +Asserts that the `actual` is equal `expected`, to within a +/- `delta` range. ```ts import { assert, test } from 'vitest' @@ -1383,9 +1359,9 @@ test('assert.closeTo', () => { ## sameMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 具有相同的成员,但顺序可以不同。使用严格相等检查 (===)。 +Asserts that `set1` and `set2` have the same members in any order. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1397,9 +1373,9 @@ test('assert.sameMembers', () => { ## notSameMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 不具有相同的成员,但顺序可以不同。使用严格相等检查 (===)。 +Asserts that `set1` and `set2` don't have the same members in any order. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1411,9 +1387,9 @@ test('assert.sameMembers', () => { ## sameDeepMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 具有相同的成员,但顺序可以不同。使用深度相等检查。 +Asserts that `set1` and `set2` have the same members in any order. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1425,9 +1401,9 @@ test('assert.sameDeepMembers', () => { ## notSameDeepMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 不具有相同的成员,但顺序可以不同。使用深度相等检查。 +Asserts that `set1` and `set2` don’t have the same members in any order. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1439,9 +1415,9 @@ test('assert.sameDeepMembers', () => { ## sameOrderedMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 具有相同的成员,并且顺序也相同。使用严格相等检查 (===)。 +Asserts that `set1` and `set2` have the same members in the same order. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1453,9 +1429,9 @@ test('assert.sameOrderedMembers', () => { ## notSameOrderedMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 的成员不相同或顺序不同。使用严格相等比较 (===)。 +Asserts that `set1` and `set2` have the same members in the same order. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1467,9 +1443,9 @@ test('assert.notSameOrderedMembers', () => { ## sameDeepOrderedMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 的成员相同且顺序相同。使用深度相等比较。 +Asserts that `set1` and `set2` have the same members in the same order. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1481,9 +1457,9 @@ test('assert.sameDeepOrderedMembers', () => { ## notSameDeepOrderedMembers -- **类型:** `(set1: T[], set2: T[], message?: string) => void` +- **Type:** `(set1: T[], set2: T[], message?: string) => void` -断言 `set1` 和 `set2` 的成员不相同或顺序不同。使用深度相等比较。 +Asserts that `set1` and `set2` don’t have the same members in the same order. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1496,9 +1472,9 @@ test('assert.notSameDeepOrderedMembers', () => { ## includeMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 被包含在 `superset` 中,顺序可以不同。使用严格相等比较 (===)。忽略重复项。 +Asserts that `subset` is included in `superset` in any order. Uses a strict equality check (===). Duplicates are ignored. ```ts import { assert, test } from 'vitest' @@ -1510,9 +1486,9 @@ test('assert.includeMembers', () => { ## notIncludeMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 未被包含在 `superset` 中,顺序可以不同。使用严格相等比较 (===)。忽略重复项。 +Asserts that `subset` isn't included in `superset` in any order. Uses a strict equality check (===). Duplicates are ignored. ```ts import { assert, test } from 'vitest' @@ -1524,9 +1500,9 @@ test('assert.notIncludeMembers', () => { ## includeDeepMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 被包含在 `superset` 中,顺序可以不同。使用深度相等比较。忽略重复项。 +Asserts that `subset` is included in `superset` in any order. Uses a deep equality check. Duplicates are ignored. ```ts import { assert, test } from 'vitest' @@ -1538,9 +1514,9 @@ test('assert.includeDeepMembers', () => { ## notIncludeDeepMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 未被包含在 `superset` 中,顺序可以不同。使用深度相等比较。忽略重复项。 +Asserts that `subset` isn’t included in `superset` in any order. Uses a deep equality check. Duplicates are ignored. ```ts import { assert, test } from 'vitest' @@ -1552,9 +1528,9 @@ test('assert.notIncludeDeepMembers', () => { ## includeOrderedMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 被包含在 `superset` 中,顺序相同,从 `superset` 中的第一个元素开始。使用严格相等比较 (===)。 +Asserts that `subset` is included in `superset` in the same order beginning with the first element in `superset`. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1566,9 +1542,9 @@ test('assert.includeOrderedMembers', () => { ## notIncludeOrderedMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 未被包含在 `superset` 中,顺序相同,从 `superset` 中的第一个元素开始。使用严格相等比较 (===)。 +Asserts that `subset` isn't included in `superset` in the same order beginning with the first element in `superset`. Uses a strict equality check (===). ```ts import { assert, test } from 'vitest' @@ -1581,9 +1557,9 @@ test('assert.notIncludeOrderedMembers', () => { ## includeDeepOrderedMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 被包含在 `superset` 中,顺序相同,从 `superset` 中的第一个元素开始。使用深度相等比较。 +Asserts that `subset` is included in `superset` in the same order beginning with the first element in `superset`. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1595,9 +1571,9 @@ test('assert.includeDeepOrderedMembers', () => { ## notIncludeDeepOrderedMembers -- **类型:** `(superset: T[], subset: T[], message?: string) => void` +- **Type:** `(superset: T[], subset: T[], message?: string) => void` -断言 `subset` 未被包含在 `superset` 中,顺序相同,从 `superset` 中的第一个元素开始。使用深度相等比较。 +Asserts that `subset` isn’t included in `superset` in the same order beginning with the first element in superset. Uses a deep equality check. ```ts import { assert, test } from 'vitest' @@ -1611,9 +1587,9 @@ test('assert.includeDeepOrderedMembers', () => { ## oneOf -- **类型:** `(inList: T, list: T[], message?: string) => void` +- **Type:** `(inList: T, list: T[], message?: string) => void` -断言非对象、非数组值 `inList` 出现在扁平数组 list 中。 +Asserts that non-object, non-array value `inList` appears in the flat array `list`. ```ts import { assert, test } from 'vitest' @@ -1625,9 +1601,9 @@ test('assert.oneOf', () => { ## changes -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 用于修改 `property` 所属 `object` 。 +Asserts that a `modifier` changes the `object` of a `property`. ```ts import { assert, test } from 'vitest' @@ -1641,9 +1617,9 @@ test('assert.changes', () => { ## changesBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 通过 `change` 修改 `property` 所属的 `object` 。 +Asserts that a `modifier` changes the `object` of a `property` by a `change`. ```ts import { assert, test } from 'vitest' @@ -1657,9 +1633,9 @@ test('assert.changesBy', () => { ## doesNotChange -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 不会通过 `change` 修改 `property` 或 `函数` 返回值的 `object` 。 +Asserts that a `modifier` does not changes the `object` of a `property`. ```ts import { assert, test } from 'vitest' @@ -1673,9 +1649,9 @@ test('assert.doesNotChange', () => { ## changesButNotBy -- **类型:** `(modifier: Function, object: T, property: string, change:number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change:number, message?: string) => void` -断言 `函数` 不会通过 `change` 修改 `property` 或 `函数` 返回值的所属对象。 +Asserts that a `modifier` does not change the `object` of a `property` or of a `modifier` return value by a `change`. ```ts import { assert, test } from 'vitest' @@ -1689,9 +1665,9 @@ test('assert.changesButNotBy', () => { ## increases -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 会增加数值类型对象属性。 +Asserts that a `modifier` increases a numeric `object`'s `property`. ```ts import { assert, test } from 'vitest' @@ -1705,9 +1681,9 @@ test('assert.increases', () => { ## increasesBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 会通过 `change` 增加数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` increases a numeric `object`'s `property` or a `modifier` return value by an `change`. ```ts import { assert, test } from 'vitest' @@ -1721,9 +1697,9 @@ test('assert.increasesBy', () => { ## doesNotIncrease -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 不会增加数值类型对象属性。 +Asserts that a `modifier` does not increases a numeric `object`'s `property`. ```ts import { assert, test } from 'vitest' @@ -1737,9 +1713,9 @@ test('assert.doesNotIncrease', () => { ## increasesButNotBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 不会通过 `change` 增加数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` does not increases a numeric `object`'s `property` or a `modifier` return value by an `change`. ```ts import { assert, test } from 'vitest' @@ -1753,9 +1729,9 @@ test('assert.increasesButNotBy', () => { ## decreases -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 不会通过 `change` 增加数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` decreases a numeric `object`'s `property`. ```ts import { assert, test } from 'vitest' @@ -1769,9 +1745,9 @@ test('assert.decreases', () => { ## decreasesBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 会通过 `change` 减少数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` decreases a numeric `object`'s `property` or a `modifier` return value by a `change`. ```ts import { assert, test } from 'vitest' @@ -1785,9 +1761,9 @@ test('assert.decreasesBy', () => { ## doesNotDecrease -- **类型:** `(modifier: Function, object: T, property: string, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, message?: string) => void` -断言 `函数` 不会减少数值类型对象属性。 +Asserts that a `modifier` dose not decrease a numeric `object`'s `property`. ```ts import { assert, test } from 'vitest' @@ -1801,9 +1777,9 @@ test('assert.doesNotDecrease', () => { ## doesNotDecreaseBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 不会通过 `change` 减少数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` does not decrease a numeric `object`'s `property` or a `modifier` return value by a `change`. ```ts import { assert, test } from 'vitest' @@ -1817,9 +1793,9 @@ test('assert.doesNotDecreaseBy', () => { ## decreasesButNotBy -- **类型:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` +- **Type:** `(modifier: Function, object: T, property: string, change: number, message?: string) => void` -断言 `函数` 不会通过 change 减少数值类型对象属性或 `函数` 返回值的数值。 +Asserts that a `modifier` does not decrease a numeric `object`'s `property` or a `modifier` return value by a `change`. ```ts import { assert, test } from 'vitest' @@ -1833,25 +1809,25 @@ test('assert.decreasesButNotBy', () => { ## ifError -- **类型:** `(object: T, message?: string) => void` +- **Type:** `(object: T, message?: string) => void` -断言 `object` 是否为假值,如果它是真值则抛出错误。这是为了允许 chai 作为 Node 的 assert 类的一个直接替代品。 +Asserts if `object` is not a false value, and throws if it is a true value. This is added to allow for chai to be a drop-in replacement for Node’s assert class. ```ts import { assert, test } from 'vitest' test('assert.ifError', () => { const err = new Error('I am a custom error') - assert.ifError(err) // 重新抛出错误! + assert.ifError(err) // Rethrows err! }) ``` ## isExtensible -- **类型:** `(object: T, message?: string) => void` -- **别名:** `extensible` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `extensible` -断言 `object` 是可扩展的(可以向其添加新的属性)。 +Asserts that `object` is extensible (can have new properties added to it). ```ts import { assert, test } from 'vitest' @@ -1863,10 +1839,10 @@ test('assert.isExtensible', () => { ## isNotExtensible -- **类型:** `(object: T, message?: string) => void` -- **别名:** `notExtensible` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `notExtensible` -断言 `object` 是不可扩展的 (不能添加新属性)。 +Asserts that `object` is not extensible (can not have new properties added to it). ```ts import { assert, test } from 'vitest' @@ -1884,10 +1860,10 @@ test('assert.isNotExtensible', () => { ## isSealed -- **类型:** `(object: T, message?: string) => void` -- **别名:** `sealed` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `sealed` -断言 `object` 是密封的(不能向其添加新的属性,也不能删除其现有属性)。 +Asserts that `object` is sealed (cannot have new properties added to it and its existing properties cannot be removed). ```ts import { assert, test } from 'vitest' @@ -1903,10 +1879,10 @@ test('assert.isSealed', () => { ## isNotSealed -- **类型:** `(object: T, message?: string) => void` -- **别名:** `notSealed` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `notSealed` -断言 `object` 未被密封(可以添加新属性,并且可以删除其现有属性)。 +Asserts that `object` is not sealed (can have new properties added to it and its existing properties can be removed). ```ts import { assert, test } from 'vitest' @@ -1918,10 +1894,10 @@ test('assert.isNotSealed', () => { ## isFrozen -- **类型:** `(object: T, message?: string) => void` -- **别名:** `frozen` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `frozen` -断言 `object` 是冻结的(不能向其添加新的属性,也不能修改其现有属性)。 +Asserts that object is frozen (cannot have new properties added to it and its existing properties cannot be modified). ```ts import { assert, test } from 'vitest' @@ -1934,10 +1910,10 @@ test('assert.isFrozen', () => { ## isNotFrozen -- **类型:** `(object: T, message?: string) => void` -- **别名:** `notFrozen` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `notFrozen` -断言 `object` 未被冻结(可以向其添加新属性,并且可以修改其现有属性)。 +Asserts that `object` is not frozen (can have new properties added to it and its existing properties can be modified). ```ts import { assert, test } from 'vitest' @@ -1949,10 +1925,10 @@ test('assert.isNotFrozen', () => { ## isEmpty -- **类型:** `(target: T, message?: string) => void` -- **别名:** `empty` +- **Type:** `(target: T, message?: string) => void` +- **Alias:** `empty` -断言 `target` 不包含任何值。对于数组和字符串,它检查 length 属性。对于 Map 和 Set 实例,它检查 size 属性。对于非函数对象,它获取自身可枚举字符串键的数量。 +Asserts that the `target` does not contain any values. For arrays and strings, it checks the length property. For Map and Set instances, it checks the size property. For non-function objects, it gets the count of its own enumerable string keys. ```ts import { assert, test } from 'vitest' @@ -1967,10 +1943,10 @@ test('assert.isEmpty', () => { ## isNotEmpty -- **类型:** `(object: T, message?: string) => void` -- **别名:** `notEmpty` +- **Type:** `(object: T, message?: string) => void` +- **Alias:** `notEmpty` -断言 `target` 包含值。对于数组和字符串,它检查 length 属性。对于 Map 和 Set 实例,它检查 size 属性。对于非函数对象,它获取自身可枚举字符串键的数量。 +Asserts that the `target` contains values. For arrays and strings, it checks the length property. For Map and Set instances, it checks the size property. For non-function objects, it gets the count of its own enumerable string keys. ```ts import { assert, test } from 'vitest' diff --git a/api/browser/assertions.md b/api/browser/assertions.md new file mode 100644 index 00000000..953bb93a --- /dev/null +++ b/api/browser/assertions.md @@ -0,0 +1,1282 @@ +--- +title: Assertion API | Browser Mode +--- + +# Assertion API + +Vitest provides a wide range of DOM assertions out of the box forked from [`@testing-library/jest-dom`](https://github.com/testing-library/jest-dom) library with the added support for locators and built-in retry-ability. + +::: tip TypeScript Support +If you are using [TypeScript](/guide/browser/#typescript) or want to have correct type hints in `expect`, make sure you have `vitest/browser` referenced somewhere. If you never imported from there, you can add a `reference` comment in any file that's covered by your `tsconfig.json`: + +```ts +/// +``` +::: + +Tests in the browser might fail inconsistently due to their asynchronous nature. Because of this, it is important to have a way to guarantee that assertions succeed even if the condition is delayed (by a timeout, network request, or animation, for example). For this purpose, Vitest provides retriable assertions out of the box via the [`expect.poll`](/api/expect#poll) and `expect.element` APIs: + +```ts +import { expect, test } from 'vitest' +import { page } from 'vitest/browser' + +test('error banner is rendered', async () => { + triggerError() + + // This creates a locator that will try to find the element + // when any of its methods are called. + // This call by itself doesn't check the existence of the element. + const banner = page.getByRole('alert', { + name: /error/i, + }) + + // Vitest provides `expect.element` with built-in retry-ability + // It will repeatedly check that the element exists in the DOM and that + // the content of `element.textContent` is equal to "Error!" + // until all the conditions are met + await expect.element(banner).toHaveTextContent('Error!') +}) +``` + +We recommend to always use `expect.element` when working with `page.getBy*` locators to reduce test flakiness. Note that `expect.element` accepts a second option: + +```ts +interface ExpectPollOptions { + // The interval to retry the assertion for in milliseconds + // Defaults to "expect.poll.interval" config option + interval?: number + // Time to retry the assertion for in milliseconds + // Defaults to "expect.poll.timeout" config option + timeout?: number + // The message printed when the assertion fails + message?: string +} +``` + +::: tip +`expect.element` is a shorthand for `expect.poll(() => element)` and works in exactly the same way. + +`toHaveTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism: + +```ts +// will fail immediately if .textContent is not `'Error!'` +expect(banner).toHaveTextContent('Error!') +``` +::: + +## toBeDisabled + +```ts +function toBeDisabled(): Promise +``` + +Allows you to check whether an element is disabled from the user's perspective. + +Matches if the element is a form control and the `disabled` attribute is specified on this element or the +element is a descendant of a form element with a `disabled` attribute. + +Note that only native control elements such as HTML `button`, `input`, `select`, `textarea`, `option`, `optgroup` +can be disabled by setting "disabled" attribute. "disabled" attribute on other elements is ignored, unless it's a custom element. + +```html + +``` + +```ts +await expect.element(getByTestId('button')).toBeDisabled() // ✅ +await expect.element(getByTestId('button')).not.toBeDisabled() // ❌ +``` + +## toBeEnabled + +```ts +function toBeEnabled(): Promise +``` + +Allows you to check whether an element is not disabled from the user's perspective. + +Works like [`not.toBeDisabled()`](#tobedisabled). Use this matcher to avoid double negation in your tests. + +```html + +``` + +```ts +await expect.element(getByTestId('button')).toBeEnabled() // ✅ +await expect.element(getByTestId('button')).not.toBeEnabled() // ❌ +``` + +## toBeEmptyDOMElement + +```ts +function toBeEmptyDOMElement(): Promise +``` + +This allows you to assert whether an element has no visible content for the user. It ignores comments but will fail if the element contains white-space. + +```html + + + +``` + +```ts +await expect.element(getByTestId('empty')).toBeEmptyDOMElement() +await expect.element(getByTestId('not-empty')).not.toBeEmptyDOMElement() +await expect.element( + getByTestId('with-whitespace') +).not.toBeEmptyDOMElement() +``` + +## toBeInTheDocument + +```ts +function toBeInTheDocument(): Promise +``` + +Assert whether an element is present in the document or not. + +```html + +``` + +```ts +await expect.element(getByTestId('svg-element')).toBeInTheDocument() +await expect.element(getByTestId('does-not-exist')).not.toBeInTheDocument() +``` + +::: warning +This matcher does not find detached elements. The element must be added to the document to be found by `toBeInTheDocument`. If you desire to search in a detached element, please use: [`toContainElement`](#tocontainelement). +::: + +## toBeInvalid + +```ts +function toBeInvalid(): Promise +``` + +This allows you to check if an element, is currently invalid. + +An element is invalid if it has an [`aria-invalid` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) with no value or a value of `"true"`, or if the result of [`checkValidity()`](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) is `false`. + +```html + + + + + +
+ +
+ +
+ +
+``` + +```ts +await expect.element(getByTestId('no-aria-invalid')).not.toBeInvalid() +await expect.element(getByTestId('aria-invalid')).toBeInvalid() +await expect.element(getByTestId('aria-invalid-value')).toBeInvalid() +await expect.element(getByTestId('aria-invalid-false')).not.toBeInvalid() + +await expect.element(getByTestId('valid-form')).not.toBeInvalid() +await expect.element(getByTestId('invalid-form')).toBeInvalid() +``` + +## toBeRequired + +```ts +function toBeRequired(): Promise +``` + +This allows you to check if a form element is currently required. + +An element is required if it is having a `required` or `aria-required="true"` attribute. + +```html + + + + + + + + +
+
+``` + +```ts +await expect.element(getByTestId('required-input')).toBeRequired() +await expect.element(getByTestId('aria-required-input')).toBeRequired() +await expect.element(getByTestId('conflicted-input')).toBeRequired() +await expect.element(getByTestId('aria-not-required-input')).not.toBeRequired() +await expect.element(getByTestId('optional-input')).not.toBeRequired() +await expect.element(getByTestId('unsupported-type')).not.toBeRequired() +await expect.element(getByTestId('select')).toBeRequired() +await expect.element(getByTestId('textarea')).toBeRequired() +await expect.element(getByTestId('supported-role')).not.toBeRequired() +await expect.element(getByTestId('supported-role-aria')).toBeRequired() +``` + +## toBeValid + +```ts +function toBeValid(): Promise +``` + +This allows you to check if the value of an element, is currently valid. + +An element is valid if it has no [`aria-invalid` attribute](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) or an attribute value of "false". The result of [`checkValidity()`](https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation) must also be `true` if it's a form element. + +```html + + + + + +
+ +
+ +
+ +
+``` + +```ts +await expect.element(getByTestId('no-aria-invalid')).toBeValid() +await expect.element(getByTestId('aria-invalid')).not.toBeValid() +await expect.element(getByTestId('aria-invalid-value')).not.toBeValid() +await expect.element(getByTestId('aria-invalid-false')).toBeValid() + +await expect.element(getByTestId('valid-form')).toBeValid() +await expect.element(getByTestId('invalid-form')).not.toBeValid() +``` + +## toBeVisible + +```ts +function toBeVisible(): Promise +``` + +This allows you to check if an element is currently visible to the user. + +Element is considered visible when it has non-empty bounding box and does not have `visibility:hidden` computed style. + +Note that according to this definition: + +- Elements of zero size **are not** considered visible. +- Elements with `display:none` **are not** considered visible. +- Elements with `opacity:0` **are** considered visible. + +To check that at least one element from the list is visible, use `locator.first()`. + +```ts +// A specific element is visible. +await expect.element(page.getByText('Welcome')).toBeVisible() + +// At least one item in the list is visible. +await expect.element(page.getByTestId('todo-item').first()).toBeVisible() + +// At least one of the two elements is visible, possibly both. +await expect.element( + page.getByRole('button', { name: 'Sign in' }) + .or(page.getByRole('button', { name: 'Sign up' })) + .first() +).toBeVisible() +``` + +## toBeInViewport 4.0.0 {#tobeinviewport} + +```ts +function toBeInViewport(options: { ratio?: number }): Promise +``` + +This allows you to check if an element is currently in viewport with [IntersectionObserver API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API). + +You can pass `ratio` argument as option, which means the minimal ratio of the element should be in viewport. `ratio` should be in 0~1. + +```ts +// A specific element is in viewport. +await expect.element(page.getByText('Welcome')).toBeInViewport() + +// 50% of a specific element should be in viewport +await expect.element(page.getByText('To')).toBeInViewport({ ratio: 0.5 }) + +// Full of a specific element should be in viewport +await expect.element(page.getByText('Vitest')).toBeInViewport({ ratio: 1 }) +``` + +## toContainElement + +```ts +function toContainElement(element: HTMLElement | SVGElement | Locator | null): Promise +``` + +This allows you to assert whether an element contains another element as a descendant or not. + +```html + +``` + +```ts +const ancestor = getByTestId('ancestor') +const descendant = getByTestId('descendant') +const nonExistantElement = getByTestId('does-not-exist') + +await expect.element(ancestor).toContainElement(descendant) +await expect.element(descendant).not.toContainElement(ancestor) +await expect.element(ancestor).not.toContainElement(nonExistantElement) +``` + +## toContainHTML + +```ts +function toContainHTML(htmlText: string): Promise +``` + +Assert whether a string representing a HTML element is contained in another element. The string should contain valid html, and not any incomplete html. + +```html + +``` + +```ts +// These are valid usages +await expect.element(getByTestId('parent')).toContainHTML('') +await expect.element(getByTestId('parent')).toContainHTML('') +await expect.element(getByTestId('parent')).not.toContainHTML('
') + +// These won't work +await expect.element(getByTestId('parent')).toContainHTML('data-testid="child"') +await expect.element(getByTestId('parent')).toContainHTML('data-testid') +await expect.element(getByTestId('parent')).toContainHTML('
') +``` + +::: warning +Chances are you probably do not need to use this matcher. We encourage testing from the perspective of how the user perceives the app in a browser. That's why testing against a specific DOM structure is not advised. + +It could be useful in situations where the code being tested renders html that was obtained from an external source, and you want to validate that that html code was used as intended. + +It should not be used to check DOM structure that you control. Please, use [`toContainElement`](#tocontainelement) instead. +::: + +## toHaveAccessibleDescription + +```ts +function toHaveAccessibleDescription(description?: string | RegExp): Promise +``` + +This allows you to assert that an element has the expected +[accessible description](https://w3c.github.io/accname/). + +You can pass the exact string of the expected accessible description, or you can +make a partial match passing a regular expression, or by using +[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching). + +```html +Start +About +User profile pic +Company logo +The logo of Our Company +Company logo +``` + +```ts +await expect.element(getByTestId('link')).toHaveAccessibleDescription() +await expect.element(getByTestId('link')).toHaveAccessibleDescription('A link to start over') +await expect.element(getByTestId('link')).not.toHaveAccessibleDescription('Home page') +await expect.element(getByTestId('extra-link')).not.toHaveAccessibleDescription() +await expect.element(getByTestId('avatar')).not.toHaveAccessibleDescription() +await expect.element(getByTestId('logo')).not.toHaveAccessibleDescription('Company logo') +await expect.element(getByTestId('logo')).toHaveAccessibleDescription( + 'The logo of Our Company', +) +await expect.element(getByTestId('logo2')).toHaveAccessibleDescription( + 'The logo of Our Company', +) +``` + +## toHaveAccessibleErrorMessage + +```ts +function toHaveAccessibleErrorMessage(message?: string | RegExp): Promise +``` + +This allows you to assert that an element has the expected +[accessible error message](https://w3c.github.io/aria/#aria-errormessage). + +You can pass the exact string of the expected accessible error message. +Alternatively, you can perform a partial match by passing a regular expression +or by using +[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching). + +```html + + + + + +``` + +```ts +// Inputs with Valid Error Messages +await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage() +await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage( + 'This field is invalid', +) +await expect.element(getByRole('textbox', { name: 'Has Error' })).toHaveAccessibleErrorMessage( + /invalid/i, +) +await expect.element( + getByRole('textbox', { name: 'Has Error' }), +).not.toHaveAccessibleErrorMessage('This field is absolutely correct!') + +// Inputs without Valid Error Messages +await expect.element( + getByRole('textbox', { name: 'No Error Attributes' }), +).not.toHaveAccessibleErrorMessage() + +await expect.element( + getByRole('textbox', { name: 'Not Invalid' }), +).not.toHaveAccessibleErrorMessage() +``` + +## toHaveAccessibleName + +```ts +function toHaveAccessibleName(name?: string | RegExp): Promise +``` + +This allows you to assert that an element has the expected +[accessible name](https://w3c.github.io/accname/). It is useful, for instance, +to assert that form elements and buttons are properly labelled. + +You can pass the exact string of the expected accessible name, or you can make a +partial match passing a regular expression, or by using +[`expect.stringContaining`](/api/expect#expect-stringcontaining) or [`expect.stringMatching`](/api/expect#expect-stringmatching). + +```html +Test alt + +Test title + +

Test content

+ +``` + +```ts +const button = getByTestId('ok-button') + +await expect.element(button).toHaveAttribute('disabled') +await expect.element(button).toHaveAttribute('type', 'submit') +await expect.element(button).not.toHaveAttribute('type', 'button') + +await expect.element(button).toHaveAttribute( + 'type', + expect.stringContaining('sub') +) +await expect.element(button).toHaveAttribute( + 'type', + expect.not.stringContaining('but') +) +``` + +## toHaveClass + +```ts +function toHaveClass(...classNames: string[], options?: { exact: boolean }): Promise +function toHaveClass(...classNames: (string | RegExp)[]): Promise +``` + +This allows you to check whether the given element has certain classes within +its `class` attribute. You must provide at least one class, unless you are +asserting that an element does not have any classes. + +The list of class names may include strings and regular expressions. Regular +expressions are matched against each individual class in the target element, and +it is NOT matched against its full `class` attribute value as whole. + +::: warning +Note that you cannot use `exact: true` option when only regular expressions are provided. +::: + +```html + + +``` + +```ts +const deleteButton = getByTestId('delete-button') +const noClasses = getByTestId('no-classes') + +await expect.element(deleteButton).toHaveClass('extra') +await expect.element(deleteButton).toHaveClass('btn-danger btn') +await expect.element(deleteButton).toHaveClass(/danger/, 'btn') +await expect.element(deleteButton).toHaveClass('btn-danger', 'btn') +await expect.element(deleteButton).not.toHaveClass('btn-link') +await expect.element(deleteButton).not.toHaveClass(/link/) + +// ⚠️ regexp matches against individual classes, not the whole classList +await expect.element(deleteButton).not.toHaveClass(/btn extra/) + +// the element has EXACTLY a set of classes (in any order) +await expect.element(deleteButton).toHaveClass('btn-danger extra btn', { + exact: true +}) +// if it has more than expected it is going to fail +await expect.element(deleteButton).not.toHaveClass('btn-danger extra', { + exact: true +}) + +await expect.element(noClasses).not.toHaveClass() +``` + +## toHaveFocus + +```ts +function toHaveFocus(): Promise +``` + +This allows you to assert whether an element has focus or not. + +```html +
+``` + +```ts +const input = page.getByTestId('element-to-focus') +input.element().focus() +await expect.element(input).toHaveFocus() +input.element().blur() +await expect.element(input).not.toHaveFocus() +``` + +## toHaveFormValues + +```ts +function toHaveFormValues(expectedValues: Record): Promise +``` + +This allows you to check if a form or fieldset contains form controls for each given name, and having the specified value. + +::: tip +It is important to stress that this matcher can only be invoked on a [form](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement) or a [fieldset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFieldSetElement) element. + +This allows it to take advantage of the [`.elements`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements) property in `form` and `fieldset` to reliably fetch all form controls within them. + +This also avoids the possibility that users provide a container that contains more than one `form`, thereby intermixing form controls that are not related, and could even conflict with one another. +::: + +This matcher abstracts away the particularities with which a form control value +is obtained depending on the type of form control. For instance, `` +elements have a `value` attribute, but `` elements return the value as a **number**, instead of + a string. +- `` elements: + - if there's a single one with the given `name` attribute, it is treated as a + **boolean**, returning `true` if the checkbox is checked, `false` if + unchecked. + - if there's more than one checkbox with the same `name` attribute, they are + all treated collectively as a single form control, which returns the value + as an **array** containing all the values of the selected checkboxes in the + collection. +- `` elements are all grouped by the `name` attribute, and + such a group treated as a single form control. This form control returns the + value as a **string** corresponding to the `value` attribute of the selected + radio button within the group. +- `` elements return the value as a **string**. This also + applies to `` elements having any other possible `type` attribute + that's not explicitly covered in different rules above (e.g. `search`, + `email`, `date`, `password`, `hidden`, etc.) +- `` elements return the value as an **array** containing all + the values of the [selected options](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions). +- ` + + + + + + +``` + +```ts +const input = page.getByLabelText('First name') +const textarea = page.getByLabelText('Description') +const selectSingle = page.getByLabelText('Fruit') +const selectMultiple = page.getByLabelText('Fruits') + +await expect.element(input).toHaveDisplayValue('Luca') +await expect.element(input).toHaveDisplayValue(/Luc/) +await expect.element(textarea).toHaveDisplayValue('An example description here.') +await expect.element(textarea).toHaveDisplayValue(/example/) +await expect.element(selectSingle).toHaveDisplayValue('Select a fruit...') +await expect.element(selectSingle).toHaveDisplayValue(/Select/) +await expect.element(selectMultiple).toHaveDisplayValue([/Avocado/, 'Banana']) +``` + +## toBeChecked + +```ts +function toBeChecked(): Promise +``` + +This allows you to check whether the given element is checked. It accepts an +`input` of type `checkbox` or `radio` and elements with a `role` of `checkbox`, +`radio` or `switch` with a valid `aria-checked` attribute of `"true"` or +`"false"`. + +```html + + +
+
+ + + +
+
+
+
+``` + +```ts +const inputCheckboxChecked = getByTestId('input-checkbox-checked') +const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked') +const ariaCheckboxChecked = getByTestId('aria-checkbox-checked') +const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked') +await expect.element(inputCheckboxChecked).toBeChecked() +await expect.element(inputCheckboxUnchecked).not.toBeChecked() +await expect.element(ariaCheckboxChecked).toBeChecked() +await expect.element(ariaCheckboxUnchecked).not.toBeChecked() + +const inputRadioChecked = getByTestId('input-radio-checked') +const inputRadioUnchecked = getByTestId('input-radio-unchecked') +const ariaRadioChecked = getByTestId('aria-radio-checked') +const ariaRadioUnchecked = getByTestId('aria-radio-unchecked') +await expect.element(inputRadioChecked).toBeChecked() +await expect.element(inputRadioUnchecked).not.toBeChecked() +await expect.element(ariaRadioChecked).toBeChecked() +await expect.element(ariaRadioUnchecked).not.toBeChecked() + +const ariaSwitchChecked = getByTestId('aria-switch-checked') +const ariaSwitchUnchecked = getByTestId('aria-switch-unchecked') +await expect.element(ariaSwitchChecked).toBeChecked() +await expect.element(ariaSwitchUnchecked).not.toBeChecked() +``` + +## toBePartiallyChecked + +```typescript +function toBePartiallyChecked(): Promise +``` + +This allows you to check whether the given element is partially checked. It +accepts an `input` of type `checkbox` and elements with a `role` of `checkbox` +with a `aria-checked="mixed"`, or `input` of type `checkbox` with +`indeterminate` set to `true` + +```html + + + +
+
+ +``` + +```ts +const ariaCheckboxMixed = getByTestId('aria-checkbox-mixed') +const inputCheckboxChecked = getByTestId('input-checkbox-checked') +const inputCheckboxUnchecked = getByTestId('input-checkbox-unchecked') +const ariaCheckboxChecked = getByTestId('aria-checkbox-checked') +const ariaCheckboxUnchecked = getByTestId('aria-checkbox-unchecked') +const inputCheckboxIndeterminate = getByTestId('input-checkbox-indeterminate') + +await expect.element(ariaCheckboxMixed).toBePartiallyChecked() +await expect.element(inputCheckboxChecked).not.toBePartiallyChecked() +await expect.element(inputCheckboxUnchecked).not.toBePartiallyChecked() +await expect.element(ariaCheckboxChecked).not.toBePartiallyChecked() +await expect.element(ariaCheckboxUnchecked).not.toBePartiallyChecked() + +inputCheckboxIndeterminate.element().indeterminate = true +await expect.element(inputCheckboxIndeterminate).toBePartiallyChecked() +``` + +## toHaveRole + +```ts +function toHaveRole(role: ARIARole): Promise +``` + +This allows you to assert that an element has the expected [role](https://www.w3.org/TR/html-aria/#docconformance). + +This is useful in cases where you already have access to an element via some query other than the role itself, and want to make additional assertions regarding its accessibility. + +The role can match either an explicit role (via the `role` attribute), or an implicit one via the [implicit ARIA semantics](https://www.w3.org/TR/html-aria/#docconformance). + +```html + +
Continue + +About +Invalid link +``` + +```ts +await expect.element(getByTestId('button')).toHaveRole('button') +await expect.element(getByTestId('button-explicit')).toHaveRole('button') +await expect.element(getByTestId('button-explicit-multiple')).toHaveRole('button') +await expect.element(getByTestId('button-explicit-multiple')).toHaveRole('switch') +await expect.element(getByTestId('link')).toHaveRole('link') +await expect.element(getByTestId('link-invalid')).not.toHaveRole('link') +await expect.element(getByTestId('link-invalid')).toHaveRole('generic') +``` + +::: warning +Roles are matched literally by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like `checkbox` will not include elements with a subclass role like `switch`. + +Also note that unlike `testing-library`, Vitest ignores all custom roles except the first valid one, following Playwright's behaviour: + +```jsx +
+ +await expect.element(getByTestId('switch')).toHaveRole('switch') // ✅ +await expect.element(getByTestId('switch')).toHaveRole('alert') // ❌ +``` +::: + +## toHaveSelection + +```ts +function toHaveSelection(selection?: string): Promise +``` + +This allows to assert that an element has a +[text selection](https://developer.mozilla.org/en-US/docs/Web/API/Selection). + +This is useful to check if text or part of the text is selected within an +element. The element can be either an input of type text, a textarea, or any +other element that contains text, such as a paragraph, span, div etc. + +::: warning +The expected selection is a string, it does not allow to check for +selection range indices. +::: + +```html +
+ + +

prev

+

+ text selected text +

+

next

+
+``` + +```ts +getByTestId('text').element().setSelectionRange(5, 13) +await expect.element(getByTestId('text')).toHaveSelection('selected') + +getByTestId('textarea').element().setSelectionRange(0, 5) +await expect.element('textarea').toHaveSelection('text ') + +const selection = document.getSelection() +const range = document.createRange() +selection.removeAllRanges() +selection.empty() +selection.addRange(range) + +// selection of child applies to the parent as well +range.selectNodeContents(getByTestId('child').element()) +await expect.element(getByTestId('child')).toHaveSelection('selected') +await expect.element(getByTestId('parent')).toHaveSelection('selected') + +// selection that applies from prev all, parent text before child, and part child. +range.setStart(getByTestId('prev').element(), 0) +range.setEnd(getByTestId('child').element().childNodes[0], 3) +await expect.element(queryByTestId('prev')).toHaveSelection('prev') +await expect.element(queryByTestId('child')).toHaveSelection('sel') +await expect.element(queryByTestId('parent')).toHaveSelection('text sel') +await expect.element(queryByTestId('next')).not.toHaveSelection() + +// selection that applies from part child, parent text after child and part next. +range.setStart(getByTestId('child').element().childNodes[0], 3) +range.setEnd(getByTestId('next').element().childNodes[0], 2) +await expect.element(queryByTestId('child')).toHaveSelection('ected') +await expect.element(queryByTestId('parent')).toHaveSelection('ected text') +await expect.element(queryByTestId('prev')).not.toHaveSelection() +await expect.element(queryByTestId('next')).toHaveSelection('ne') +``` + +## toMatchScreenshot experimental {#tomatchscreenshot} + +```ts +function toMatchScreenshot( + options?: ScreenshotMatcherOptions, +): Promise +function toMatchScreenshot( + name?: string, + options?: ScreenshotMatcherOptions, +): Promise +``` + +::: tip +The `toMatchScreenshot` assertion can be configured globally in your +[Vitest config](/config/browser/expect#tomatchscreenshot). +::: + +This assertion allows you to perform visual regression testing by comparing +screenshots of elements or pages against stored reference images. + +When differences are detected beyond the configured threshold, the test fails. +To help identify the changes, the assertion generates: + +- The actual screenshot captured during the test +- The expected reference screenshot +- A diff image highlighting the differences (when possible) + +::: warning Screenshots Stability +The assertion automatically retries taking screenshots until two consecutive +captures yield the same result. This helps reduce flakiness caused by +animations, loading states, or other dynamic content. You can control this +behavior with the `timeout` option. + +However, browser rendering can vary across: + +- Different browsers and browser versions +- Operating systems (Windows, macOS, Linux) +- Screen resolutions and pixel densities +- GPU drivers and hardware acceleration +- Font rendering and system fonts + +It is recommended to read the +[Visual Regression Testing guide](/guide/browser/visual-regression-testing) to +implement this testing strategy efficiently. +::: + +::: tip +When a screenshot comparison fails due to **intentional changes**, you can +update the reference screenshot by pressing the `u` key in watch mode, or by +running tests with the `-u` or `--update` flags. +::: + +```html + +``` + +```ts +// basic usage, auto-generates screenshot name +await expect.element(getByTestId('button')).toMatchScreenshot() + +// with custom name +await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button') + +// with options +await expect.element(getByTestId('button')).toMatchScreenshot({ + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, +}) + +// with both name and options +await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', { + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, +}) +``` + +### Options + +- `comparatorName: "pixelmatch" = "pixelmatch"` + + The name of the algorithm/library used for comparing images. + + Currently, [`"pixelmatch"`](https://github.com/mapbox/pixelmatch) is the only + supported comparator. + +- `comparatorOptions: object` + + These options allow changing the behavior of the comparator. What properties + can be set depends on the chosen comparator algorithm. + + Vitest has set default values out of the box, but they can be overridden. + + - [`"pixelmatch"` options](#pixelmatch-comparator-options) + + ::: warning + **Always explicitly set `comparatorName` to get proper type inference for + `comparatorOptions`**. + + Without it, TypeScript won't know which options are valid: + + ```ts + // ❌ TypeScript can't infer the correct options + await expect.element(button).toMatchScreenshot({ + comparatorOptions: { + // might error when new comparators are added + allowedMismatchedPixelRatio: 0.01, + }, + }) + + // ✅ TypeScript knows these are pixelmatch options + await expect.element(button).toMatchScreenshot({ + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, + }) + ``` + ::: + +- `screenshotOptions: object` + + The same options allowed by + [`locator.screenshot()`](/api/browser/locators.html#screenshot), except for: + + - `'base64'` + - `'path'` + - `'save'` + - `'type'` + +- `timeout: number = 5_000` + + Time to wait until a stable screenshot is found. + + Setting this value to `0` disables the timeout, but if a stable screenshot + can't be determined the process will not end. + +#### `"pixelmatch"` comparator options + +The following options are available when using the `"pixelmatch"` comparator: + +- `allowedMismatchedPixelRatio: number | undefined = undefined` + + The maximum allowed ratio of differing pixels between the captured screenshot + and the reference image. + + Must be a value between `0` and `1`. + + For example, `allowedMismatchedPixelRatio: 0.02` means the test will pass + if up to 2% of pixels differ, but fail if more than 2% differ. + +- `allowedMismatchedPixels: number | undefined = undefined` + + The maximum number of pixels that are allowed to differ between the captured + screenshot and the stored reference image. + + If set to `undefined`, any non-zero difference will cause the test to fail. + + For example, `allowedMismatchedPixels: 10` means the test will pass if 10 or + fewer pixels differ, but fail if 11 or more differ. + +- `threshold: number = 0.1` + + Acceptable perceived color difference between the same pixel in two images. + + Value ranges from `0` (strict) to `1` (very lenient). Lower values mean small + differences will be detected. + + The comparison uses the [YIQ color space](https://en.wikipedia.org/wiki/YIQ). + +- `includeAA: boolean = false` + + If `true`, disables detection and ignoring of anti-aliased pixels. + +- `alpha: number = 0.1` + + Blending level of unchanged pixels in the diff image. + + Ranges from `0` (white) to `1` (original brightness). + +- `aaColor: [r: number, g: number, b: number] = [255, 255, 0]` + + Color used for anti-aliased pixels in the diff image. + +- `diffColor: [r: number, g: number, b: number] = [255, 0, 0]` + + Color used for differing pixels in the diff image. + +- `diffColorAlt: [r: number, g: number, b: number] | undefined = undefined` + + Optional alternative color for dark-on-light differences, to help show what's + added vs. removed. + + If not set, `diffColor` is used for all differences. + +- `diffMask: boolean = false` + + If `true`, shows only the diff as a mask on a transparent background, instead + of overlaying it on the original image. + + Anti-aliased pixels won't be shown (if detected). + +::: warning +When both `allowedMismatchedPixels` and `allowedMismatchedPixelRatio` are set, +the more restrictive value is used. + +For example, if you allow 100 pixels or 2% ratio, and your image has 10,000 +pixels, the effective limit would be 100 pixels instead of 200. +::: diff --git a/api/browser/commands.md b/api/browser/commands.md new file mode 100644 index 00000000..c53503fd --- /dev/null +++ b/api/browser/commands.md @@ -0,0 +1,157 @@ +--- +title: Commands | Browser Mode +outline: deep +--- + +# Commands + +Command is a function that invokes another function on the server and passes down the result back to the browser. Vitest exposes several built-in commands you can use in your browser tests. + +## Built-in Commands + +### Files Handling + +You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files in your browser tests. Since Vitest 3.2, all paths are resolved relative to the [project](/guide/projects) root (which is `process.cwd()`, unless overridden manually). Previously, paths were resolved relative to the test file. + +By default, Vitest uses `utf-8` encoding but you can override it with options. + +::: tip +This API follows [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) limitations for security reasons. +::: + +```ts +import { server } from 'vitest/browser' + +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 exposes access to raw Chrome DevTools Protocol via the `cdp` method exported from `vitest/browser`. It is mostly useful to library authors to build tools on top of it. + +```ts +import { cdp } from 'vitest/browser' + +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 works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. +::: + +## Custom Commands + +You can also add your own commands via [`browser.commands`](/config/browser/commands) config option. If you develop a library, you can provide them via a `config` hook inside a plugin: + +```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, + } + } + } + } + } + } +} +``` + +Then you can call it inside your test by importing it from `vitest/browser`: + +```ts +import { commands } from 'vitest/browser' +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' { + interface BrowserCommands { + myCustomCommand: (arg1: string, arg2: string) => Promise<{ + someValue: true + }> + } +} +``` + +::: warning +Custom functions will override built-in ones if they have the same name. +::: + +### Custom `playwright` commands + +Vitest exposes several `playwright` specific properties on the command context. + +- `page` references the full page that contains the test iframe. This is the orchestrator HTML and you most likely shouldn't touch it to not break things. +- `frame` is an async method that will resolve tester [`Frame`](https://playwright.dev/docs/api/class-frame). It has a similar API to the `page`, but it doesn't support certain methods. If you need to query an element, you should prefer using `context.iframe` instead because it is more stable and faster. +- `iframe` is a [`FrameLocator`](https://playwright.dev/docs/api/class-framelocator) that should be used to query other elements on the page. +- `context` refers to the unique [BrowserContext](https://playwright.dev/docs/api/class-browsercontext). + +```ts +import { BrowserCommand } from 'vitest/node' + +export const myCommand: BrowserCommand<[string, number]> = async ( + ctx, + arg1: string, + arg2: number +) => { + if (ctx.provider.name === 'playwright') { + const element = await ctx.iframe.findByRole('alert') + const screenshot = await element.screenshot() + // do something with the screenshot + return difference + } +} +``` + +### Custom `webdriverio` commands + +Vitest exposes some `webdriverio` specific properties on the context object. + +- `browser` is the `WebdriverIO.Browser` API. + +Vitest automatically switches the `webdriver` context to the test iframe by calling `browser.switchFrame` before the command is called, so `$` and `$$` methods refer to the elements inside the iframe, not in the orchestrator, but non-webdriver APIs will still refer to the parent frame context. diff --git a/api/browser/context.md b/api/browser/context.md new file mode 100644 index 00000000..b16d098e --- /dev/null +++ b/api/browser/context.md @@ -0,0 +1,233 @@ +--- +title: Context API | Browser Mode +--- + +# Context API + +Vitest exposes a context module via `vitest/browser` entry point. As of 2.0, it exposes a small set of utilities that might be useful to you in tests. + +## `userEvent` + +::: tip +The `userEvent` API is explained in detail at [Interactivity API](/api/browser/interactivity). +::: + +```ts +/** + * 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 + cleanup: () => Promise + 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 +} +``` + +## `commands` + +::: tip +This API is explained in detail at [Commands API](/api/browser/commands). +::: + +```ts +/** + * Available commands for the browser. + * A shortcut to `server.commands`. + */ +export const commands: BrowserCommands +``` + +## `page` + +The `page` export provides utilities to interact with the current `page`. + +::: warning +While it exposes some utilities from Playwright's `page`, it is not the same object. Since the browser context is evaluated in the browser, your tests don't have access to Playwright's `page` because it runs on the server. + +Use [Commands API](/api/browser/commands) if you need to have access to Playwright's `page` object. +::: + +```ts +export const page: { + /** + * 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 + /** + * Extend default `page` object with custom methods. + */ + extend(methods: Partial): BrowserPage + /** + * Wrap an HTML element in a `Locator`. When querying for elements, the search will always return this element. + */ + elementLocator(element: Element): Locator + /** + * The iframe locator. This is a document locator that enters the iframe body + * and works similarly to the `page` object. + * **Warning:** At the moment, this is supported only by the `playwright` provider. + */ + frameLocator(iframeElement: Locator): FrameLocator + + /** + * Locator APIs. See its documentation for more details. + */ + getByRole(role: ARIARole | string, options?: LocatorByRoleOptions): Locator + getByLabelText(text: string | RegExp, options?: LocatorOptions): Locator + getByTestId(text: string | RegExp): Locator + getByAltText(text: string | RegExp, options?: LocatorOptions): Locator + getByPlaceholder(text: string | RegExp, options?: LocatorOptions): Locator + getByText(text: string | RegExp, options?: LocatorOptions): Locator + getByTitle(text: string | RegExp, options?: LocatorOptions): Locator +} +``` + +::: tip +The `getBy*` API is explained at [Locators API](/api/browser/locators). +::: + +::: warning WARNING 3.2.0 +Note that `screenshot` will always return a base64 string if `save` is set to `false`. +The `path` is also ignored in that case. +::: + +### frameLocator + +```ts +function frameLocator(iframeElement: Locator): FrameLocator +``` + +The `frameLocator` method returns a `FrameLocator` instance that can be used to find elements inside the iframe. + +The frame locator is similar to `page`. It does not refer to the Iframe HTML element, but to the iframe's document. + +```ts +const frame = page.frameLocator( + page.getByTestId('iframe') +) + +await frame.getByText('Hello World').click() // ✅ +await frame.click() // ❌ Not available +``` + +::: danger IMPORTANT +At the moment, the `frameLocator` method is only supported by the `playwright` provider. + +The interactive methods (like `click` or `fill`) are always available on elements within the iframe, but assertions with `expect.element` require the iframe to have the [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). +::: + +## `cdp` + +```ts +function cdp(): CDPSession +``` + +The `cdp` export returns the current Chrome DevTools Protocol session. It is mostly useful to library authors to build tools on top of it. + +::: warning +CDP session works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. +::: + +```ts +export const cdp: () => CDPSession +``` + +## `server` + +The `server` export represents the Node.js environment where the Vitest server is running. It is mostly useful for debugging or limiting your tests based on the environment. + +```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 + /** + * Serialized test config. + */ + config: SerializedConfig +} +``` + +## `utils` + +Utility functions useful for custom render libraries. + +```ts +export const utils: { + /** + * This is simillar to calling `page.elementLocator`, but it returns only + * locator selectors. + */ + getElementLocatorSelectors(element: Element): LocatorSelectors + /** + * Prints prettified HTML of an element. + */ + debug( + el?: Element | Locator | null | (Element | Locator)[], + maxLength?: number, + options?: PrettyDOMOptions, + ): void + /** + * Returns prettified HTML of an element. + */ + prettyDOM( + dom?: Element | Locator | undefined | null, + maxLength?: number, + prettyFormatOptions?: PrettyDOMOptions, + ): string + /** + * Configures default options of `prettyDOM` and `debug` functions. + * This will also affect `vitest-browser-{framework}` package. + * @experimental + */ + configurePrettyDOM(options: StringifyOptions): void + /** + * Creates "Cannot find element" error. Useful for custom locators. + */ + getElementError(selector: string, container?: Element): Error +} +``` diff --git a/api/browser/interactivity.md b/api/browser/interactivity.md new file mode 100644 index 00000000..8d2697b6 --- /dev/null +++ b/api/browser/interactivity.md @@ -0,0 +1,614 @@ +--- +title: Interactivity API | Browser Mode +--- + +# Interactivity API + +Vitest implements a subset of [`@testing-library/user-event`](https://testing-library.com/docs/user-event/intro) APIs using [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) or [webdriver](https://www.w3.org/TR/webdriver/) instead of faking events which makes the browser behaviour more reliable and consistent with how users interact with a page. + +```ts +import { userEvent } from 'vitest/browser' + +await userEvent.click(document.querySelector('.button')) +``` + +Almost every `userEvent` method inherits its provider options. + +## userEvent.setup + +```ts +function setup(): UserEvent +``` + +Creates a new user event instance. This is useful if you need to keep the state of keyboard to press and release buttons correctly. + +::: warning +Unlike `@testing-library/user-event`, the default `userEvent` instance from `vitest/browser` is created once, not every time its methods are called! You can see the difference in how it works in this snippet: + +```ts +import { userEvent as vitestUserEvent } from 'vitest/browser' +import { userEvent as originalUserEvent } from '@testing-library/user-event' + +await vitestUserEvent.keyboard('{Shift}') // press shift without releasing +await vitestUserEvent.keyboard('{/Shift}') // releases shift + +await originalUserEvent.keyboard('{Shift}') // press shift without releasing +await originalUserEvent.keyboard('{/Shift}') // DID NOT release shift because the state is different +``` + +This behaviour is more useful because we do not emulate the keyboard, we actually press the Shift, so keeping the original behaviour would cause unexpected issues when typing in the field. +::: + +## userEvent.click + +```ts +function click( + element: Element | Locator, + options?: UserEventClickOptions, +): Promise +``` + +Click on an element. Inherits provider's options. Please refer to your provider's documentation for detailed explanation about how this method works. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('clicks on an element', async () => { + const logo = page.getByRole('img', { name: /logo/ }) + + await userEvent.click(logo) + // or you can access it directly on the locator + await logo.click() + + // With WebdriverIO, this uses either ElementClick (with no arguments) or + // actions (with arguments). Use an empty object to force the use of actions. + await logo.click({}) +}) +``` + +### Clicking with a modifier + +With either WebdriverIO or Playwright: + +```ts +await userEvent.keyboard('{Shift>}') +// By using an empty object as the option, this opts in to using a chain of actions +// instead of an ElementClick in webdriver. +// Firefox has a bug that makes this necessary. +// Follow https://bugzilla.mozilla.org/show_bug.cgi?id=1456642 to know when this +// will be fixed. +await userEvent.click(element, {}) +await userEvent.keyboard('{/Shift}') +``` + +With Playwright: +```ts +await userEvent.click(element, { modifiers: ['Shift'] }) +``` + +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 + +```ts +function dblClick( + element: Element | Locator, + options?: UserEventDoubleClickOptions, +): Promise +``` + +Triggers a double click event on an element. + +Please refer to your provider's documentation for detailed explanation about how this method works. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('triggers a double click on an element', async () => { + const logo = page.getByRole('img', { name: /logo/ }) + + await userEvent.dblClick(logo) + // or you can access it directly on the locator + await logo.dblClick() +}) +``` + +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 + +```ts +function tripleClick( + element: Element | Locator, + 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 { page, userEvent } from 'vitest/browser' + +test('triggers a triple click on an element', async () => { + const logo = page.getByRole('img', { name: /logo/ }) + let tripleClickFired = false + logo.addEventListener('click', (evt) => { + if (evt.detail === 3) { + tripleClickFired = true + } + }) + + await userEvent.tripleClick(logo) + // or you can access it directly on the locator + await logo.tripleClick() + + 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 + +```ts +function fill( + element: Element | Locator, + text: string, +): Promise +``` + +Set a value to the `input`/`textarea`/`contenteditable` field. This will remove any existing text in the input before setting the new value. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('update input', async () => { + const input = page.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} + + // or you can access it directly on the locator + await input.fill('foo') // input.value == foo +}) +``` + +This methods focuses the element, fills it and triggers an `input` event after filling. You can use an empty string to clear the field. + +::: tip +This API is faster than using [`userEvent.type`](#userevent-type) or [`userEvent.keyboard`](#userevent-keyboard), but it **doesn't support** [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard) (e.g., `{Shift}{selectall}`). + +We recommend using this API over [`userEvent.type`](#userevent-type) in situations when you don't need to enter special characters or have granular control over keypress events. +::: + +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 + +```ts +function keyboard(text: string): Promise +``` + +The `userEvent.keyboard` allows you to trigger keyboard strokes. If any input has a focus, it will type characters into that input. Otherwise, it will trigger keyboard events on the currently focused element (`document.body` if there are no focused elements). + +This API supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard). + +```ts +import { userEvent } from 'vitest/browser' + +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 `Keyboard` API](https://playwright.dev/docs/api/class-keyboard) +- [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 + +```ts +function tab(options?: UserEventTabOptions): Promise +``` + +Sends a `Tab` key event. This is a shorthand for `userEvent.keyboard('{tab}')`. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('tab works', async () => { + const [input1, input2] = page.getByRole('input').elements() + + expect(input1).toHaveFocus() + + await userEvent.tab() + + expect(input2).toHaveFocus() + + await userEvent.tab({ shift: true }) + + expect(input1).toHaveFocus() +}) +``` + +References: + +- [Playwright `Keyboard` API](https://playwright.dev/docs/api/class-keyboard) +- [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 + +```ts +function type( + element: Element | Locator, + text: string, + options?: UserEventTypeOptions, +): Promise +``` + +::: warning +If you don't rely on [special characters](https://testing-library.com/docs/user-event/keyboard) (e.g., `{shift}` or `{selectall}`), it is recommended to use [`userEvent.fill`](#userevent-fill) instead for better performance. +::: + +The `type` method implements `@testing-library/user-event`'s [`type`](https://testing-library.com/docs/user-event/utility/#type) utility built on top of [`keyboard`](https://testing-library.com/docs/user-event/keyboard) API. + +This function allows you to type characters into an `input`/`textarea`/`contenteditable` element. It supports [user-event `keyboard` syntax](https://testing-library.com/docs/user-event/keyboard). + +If you just need to press characters without an input, use [`userEvent.keyboard`](#userevent-keyboard) API. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('update input', async () => { + const input = page.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[ +}) +``` + +::: info +Vitest doesn't expose `.type` method on the locator like `input.type` because it exists only for compatibility with the `userEvent` library. Consider using `.fill` instead as it is faster. +::: + +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 + +```ts +function clear(element: Element | Locator, options?: UserEventClearOptions): Promise +``` + +This method clears the input element content. + +```ts +import { page, userEvent } from 'vitest/browser' + +test('clears input', async () => { + const input = page.getByRole('input') + + await userEvent.fill(input, 'foo') + expect(input).toHaveValue('foo') + + await userEvent.clear(input) + // or you can access it directly on the locator + await input.clear() + + 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 + +```ts +function selectOptions( + element: Element | Locator, + values: + | HTMLElement + | HTMLElement[] + | Locator + | Locator[] + | string + | string[], + options?: UserEventSelectOptions, +): Promise +``` + +The `userEvent.selectOptions` allows selecting a value in a ` + + +
+ +``` + +You can locate each element by its implicit role: + +```ts +await expect.element( + page.getByRole('heading', { name: 'Sign up' }) +).toBeVisible() + +await page.getByRole('textbox', { name: 'Login' }).fill('admin') +await page.getByRole('textbox', { name: 'Password' }).fill('admin') + +await page.getByRole('button', { name: /submit/i }).click() +``` + +::: warning +Roles are matched by string equality, without inheriting from the ARIA role hierarchy. As a result, querying a superclass role like `checkbox` will not include elements with a subclass role like `switch`. + +By default, many semantic elements in HTML have a role; for example, `` has the "radio" role. Non-semantic elements in HTML do not have a role; `
` and `` without added semantics return `null`. The `role` attribute can provide semantics. + +Providing roles via `role` or `aria-*` attributes to built-in elements that already have an implicit role is **highly discouraged** by ARIA guidelines. +::: + +##### Options + +- `exact: boolean` + + Whether the `name` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `name` is a regular expression. Note that exact match still trims whitespace. + + ```tsx + + + page.getByRole('button', { name: 'hello world' }) // ✅ + page.getByRole('button', { name: 'hello world', exact: true }) // ❌ + page.getByRole('button', { name: 'Hello World', exact: true }) // ✅ + ``` + +- `checked: boolean` + + Should checked elements (set by `aria-checked` or ``) be included or not. By default, the filter is not applied. + + See [`aria-checked`](https://www.w3.org/TR/wai-aria-1.2/#aria-checked) for more information + + ```tsx + <> + + + page.getByRole('button', { name: 'Click Me!' }) // ✅ + page.getByRole('button', { name: 'click me!' }) // ✅ + page.getByRole('button', { name: 'Click Me?' }) // ❌ + ``` + +- `pressed: boolean` + + Should pressed elements be included or not. By default, the filter is not applied. + + See [`aria-pressed`](https://www.w3.org/TR/wai-aria-1.2/#aria-pressed) for more information + + ```tsx + + + page.getByRole('button', { pressed: true }) // ✅ + page.getByRole('button', { pressed: false }) // ❌ + ``` + +- `selected: boolean` + + Should selected elements be included or not. By default, the filter is not applied. + + See [`aria-selected`](https://www.w3.org/TR/wai-aria-1.2/#aria-selected) for more information + + ```tsx + + + page.getByRole('button', { selected: true }) // ✅ + page.getByRole('button', { selected: false }) // ❌ + ``` + +##### See also + +- [List of ARIA roles at MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) +- [List of ARIA roles at w3.org](https://www.w3.org/TR/wai-aria-1.2/#role_definitions) +- [testing-library's `ByRole`](https://testing-library.com/docs/queries/byrole/) + +## getByAltText + +```ts +function getByAltText( + text: string | RegExp, + options?: LocatorOptions, +): Locator +``` + +Creates a locator capable of finding an element with an `alt` attribute that matches the text. Unlike testing-library's implementation, Vitest will match any element that has a matching `alt` attribute. + +```tsx +Incredibles 2 Poster + +page.getByAltText(/incredibles.*? poster/i) // ✅ +page.getByAltText('non existing alt text') // ❌ +``` + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByAltText`](https://testing-library.com/docs/queries/byalttext/) + +## getByLabelText + +```ts +function getByLabelText( + text: string | RegExp, + options?: LocatorOptions, +): Locator +``` + +Creates a locator capable of finding an element that has an associated label. + +The `page.getByLabelText('Username')` locator will find every input in the example below: + +```html +// for/htmlFor relationship between label and form element id + + + +// The aria-labelledby attribute with form elements + + + +// Wrapper labels + + +// Wrapper labels where the label text is in another child element + + +// aria-label attributes +// Take care because this is not a label that users can see on the page, +// so the purpose of your input must be obvious to visual users. + +``` + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByLabelText`](https://testing-library.com/docs/queries/bylabeltext/) + +## getByPlaceholder + +```ts +function getByPlaceholder( + text: string | RegExp, + options?: LocatorOptions, +): Locator +``` + +Creates a locator capable of finding an element that has the specified `placeholder` attribute. Vitest will match any element that has a matching `placeholder` attribute, not just `input`. + +```tsx + + +page.getByPlaceholder('Username') // ✅ +page.getByPlaceholder('not found') // ❌ +``` + +::: warning +It is generally better to rely on a label using [`getByLabelText`](#getbylabeltext) than a placeholder. +::: + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByPlaceholderText`](https://testing-library.com/docs/queries/byplaceholdertext/) + +## getByText + +```ts +function getByText( + text: string | RegExp, + options?: LocatorOptions, +): Locator +``` + +Creates a locator capable of finding an element that contains the specified text. The text will be matched against TextNode's [`nodeValue`](https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeValue) or input's value if the type is `button` or `reset`. Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace. + +```tsx +About ℹ️ + +page.getByText(/about/i) // ✅ +page.getByText('about', { exact: true }) // ❌ +``` + +::: tip +This locator is useful for locating non-interactive elements. If you need to locate an interactive element, like a button or an input, prefer [`getByRole`](#getbyrole). +::: + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByText`](https://testing-library.com/docs/queries/bytext/) + +## getByTitle + +```ts +function getByTitle( + text: string | RegExp, + options?: LocatorOptions, +): Locator +``` + +Creates a locator capable of finding an element that has the specified `title` attribute. Unlike testing-library's `getByTitle`, Vitest cannot find `title` elements within an SVG. + +```tsx + + +page.getByTitle('Delete') // ✅ +page.getByTitle('Create') // ❌ +``` + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByTitle`](https://testing-library.com/docs/queries/bytitle/) + +## getByTestId + +```ts +function getByTestId(text: string | RegExp): Locator +``` + +Creates a locator capable of finding an element that matches the specified test id attribute. You can configure the attribute name with [`browser.locators.testIdAttribute`](/config/browser/locators#testidattribute). + +```tsx +
+ +page.getByTestId('custom-element') // ✅ +page.getByTestId('non-existing-element') // ❌ +``` + +::: warning +It is recommended to use this only after the other locators don't work for your use case. Using `data-testid` attributes does not resemble how your software is used and should be avoided if possible. +::: + +#### Options + +- `exact: boolean` + + Whether the `text` is matched exactly: case-sensitive and whole-string. Disabled by default. This option is ignored if `text` is a regular expression. Note that exact match still trims whitespace. + +#### See also + +- [testing-library's `ByTestId`](https://testing-library.com/docs/queries/bytestid/) + +## nth + +```ts +function nth(index: number): Locator +``` + +This method returns a new locator that matches only a specific index within a multi-element query result. It's zero based, `nth(0)` selects the first element. Unlike `elements()[n]`, the `nth` locator will be retried until the element is present. + +```html +
+
+``` + +```tsx +page.getByRole('textbox').nth(0) // ✅ +page.getByRole('textbox').nth(4) // ❌ +``` + +::: tip +Before resorting to `nth`, you may find it useful to use chained locators to narrow down your search. +Sometimes there is no better way to distinguish than by element position; although this can lead to flake, it's better than nothing. +::: + +```tsx +page.getByLabel('two').getByRole('input') // ✅ better alternative to page.getByRole('textbox').nth(3) +page.getByLabel('one').getByRole('input') // ❌ too ambiguous +page.getByLabel('one').getByRole('input').nth(1) // ✅ pragmatic compromise +``` + +## first + +```ts +function first(): Locator +``` + +This method returns a new locator that matches only the first index of a multi-element query result. +It is sugar for `nth(0)`. + +```html + +``` + +```tsx +page.getByRole('textbox').first() // ✅ +``` + +## last + +```ts +function last(): Locator +``` + +This method returns a new locator that matches only the last index of a multi-element query result. +It is sugar for `nth(-1)`. + +```html + +``` + +```tsx +page.getByRole('textbox').last() // ✅ +``` + +## and + +```ts +function and(locator: Locator): Locator +``` + +This method creates a new locator that matches both the parent and provided locator. The following example finds a button with a specific title: + +```ts +page.getByRole('button').and(page.getByTitle('Subscribe')) +``` + +## or + +```ts +function or(locator: Locator): Locator +``` + +This method creates a new locator that matches either one or both locators. + +::: warning +Note that if locator matches more than a single element, calling another method might throw an error if it expects a single element: + +```tsx +<> + + Error happened! + + +page.getByRole('button') + .or(page.getByRole('link')) + .click() // ❌ matches multiple elements +``` +::: + +## filter + +```ts +function filter(options: LocatorOptions): Locator +``` + +This methods narrows down the locator according to the options, such as filtering by text. It can be chained to apply multiple filters. + +### has + +- **Type:** `Locator` + +This options narrows down the selector to match elements that contain other elements matching provided locator. For example, with this HTML: + +```html{1,3} +
+
Vitest
+
+
+
Rolldown
+
+``` + +We can narrow down the locator to only find the `article` with `Vitest` text inside: + +```ts +page.getByRole('article').filter({ has: page.getByText('Vitest') }) // ✅ +``` + +::: warning +Provided locator (`page.getByText('Vitest')` in the example) must be relative to the parent locator (`page.getByRole('article')` in the example). It will be queried starting with the parent locator, not the document root. + +Meaning, you cannot pass down a locator that queries the element outside of the parent locator: + +```ts +page.getByText('Vitest').filter({ has: page.getByRole('article') }) // ❌ +``` + +This example will fail because the `article` element is outside the element with `Vitest` text. +::: + +::: tip +This method can be chained to narrow down the element even further: + +```ts +page.getByRole('article') + .filter({ has: page.getByRole('button', { name: 'delete row' }) }) + .filter({ has: page.getByText('Vitest') }) +``` +::: + +### hasNot + +- **Type:** `Locator` + +This option narrows down the selector to match elements that do not contain other elements matching provided locator. For example, with this HTML: + +```html{1,3} +
+
Vitest
+
+
+
Rolldown
+
+``` + +We can narrow down the locator to only find the `article` that doesn't have `Rolldown` inside. + +```ts +page.getByRole('article') + .filter({ hasNot: page.getByText('Rolldown') }) // ✅ +page.getByRole('article') + .filter({ hasNot: page.getByText('Vitest') }) // ❌ +``` + +::: warning +Note that provided locator is queried against the parent, not the document root, just like [`has`](#has) option. +::: + +### hasText + +- **Type:** `string | RegExp` + +This options narrows down the selector to only match elements that contain provided text somewhere inside. When the `string` is passed, matching is case-insensitive and searches for a substring. + +```html{1,3} +
+
Vitest
+
+
+
Rolldown
+
+``` + +Both locators will find the same element because the search is case-insensitive: + +```ts +page.getByRole('article').filter({ hasText: 'Vitest' }) // ✅ +page.getByRole('article').filter({ hasText: 'Vite' }) // ✅ +``` + +### hasNotText + +- **Type:** `string | RegExp` + +This options narrows down the selector to only match elements that do not contain provided text somewhere inside. When the `string` is passed, matching is case-insensitive and searches for a substring. + +## Methods + +All methods are asynchronous and must be awaited. Since Vitest 3, tests will fail if a method is not awaited. + +### click + +```ts +function click(options?: UserEventClickOptions): Promise +``` + +Click on an element. You can use the options to set the cursor position. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('img', { name: 'Rose' }).click() +``` + +- [See more at `userEvent.click`](/api/browser/interactivity#userevent-click) + +### dblClick + +```ts +function dblClick(options?: UserEventDoubleClickOptions): Promise +``` + +Triggers a double click event on an element. You can use the options to set the cursor position. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('img', { name: 'Rose' }).dblClick() +``` + +- [See more at `userEvent.dblClick`](/api/browser/interactivity#userevent-dblclick) + +### tripleClick + +```ts +function tripleClick(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. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('img', { name: 'Rose' }).tripleClick() +``` + +- [See more at `userEvent.tripleClick`](/api/browser/interactivity#userevent-tripleclick) + +### clear + +```ts +function clear(options?: UserEventClearOptions): Promise +``` + +Clears the input element content. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('textbox', { name: 'Full Name' }).clear() +``` + +- [See more at `userEvent.clear`](/api/browser/interactivity#userevent-clear) + +### hover + +```ts +function hover(options?: UserEventHoverOptions): Promise +``` + +Moves the cursor position to the selected element. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('img', { name: 'Rose' }).hover() +``` + +- [See more at `userEvent.hover`](/api/browser/interactivity#userevent-hover) + +### unhover + +```ts +function unhover(options?: UserEventHoverOptions): Promise +``` + +This works the same as [`locator.hover`](#hover), but moves the cursor to the `document.body` element instead. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('img', { name: 'Rose' }).unhover() +``` + +- [See more at `userEvent.unhover`](/api/browser/interactivity#userevent-unhover) + +### fill + +```ts +function fill(text: string, options?: UserEventFillOptions): Promise +``` + +Sets the value of the current `input`, `textarea` or `contenteditable` element. + +```ts +import { page } from 'vitest/browser' + +await page.getByRole('input', { name: 'Full Name' }).fill('Mr. Bean') +``` + +- [See more at `userEvent.fill`](/api/browser/interactivity#userevent-fill) + +### dropTo + +```ts +function dropTo( + target: Locator, + options?: UserEventDragAndDropOptions, +): Promise +``` + +Drags the current element to the target location. + +```ts +import { page } from 'vitest/browser' + +const paris = page.getByText('Paris') +const france = page.getByText('France') + +await paris.dropTo(france) +``` + +- [See more at `userEvent.dragAndDrop`](/api/browser/interactivity#userevent-draganddrop) + +### selectOptions + +```ts +function selectOptions( + values: + | HTMLElement + | HTMLElement[] + | Locator + | Locator[] + | string + | string[], + options?: UserEventSelectOptions, +): Promise +``` + +Choose one or more values from a `