VuePress 生态系统VuePress 生态系统
  • 主题指南
  • 默认主题
  • Hope 主题
  • Plume 主题
  • Reco 主题
  • 功能插件
  • Markdown 插件
  • 搜索插件
  • 博客插件
  • 渐进式应用插件
  • 统计分析插件
  • 搜索引擎优化插件
  • 开发插件
  • 工具插件
  • AI 插件
  • @vuepress/helper
  • @vuepress/test-utils
  • English
  • 简体中文
GitHub
  • 主题指南
  • 默认主题
  • Hope 主题
  • Plume 主题
  • Reco 主题
  • 功能插件
  • Markdown 插件
  • 搜索插件
  • 博客插件
  • 渐进式应用插件
  • 统计分析插件
  • 搜索引擎优化插件
  • 开发插件
  • 工具插件
  • AI 插件
  • @vuepress/helper
  • @vuepress/test-utils
  • English
  • 简体中文
GitHub
  • @vuepress/helper
    • Node

      • 打包器相关
      • 多语言相关
      • 页面相关
    • 客户端相关
    • 共享方法
    • 样式
  • @vuepress/test-utils

test-utils

@vuepress/test-utils

VuePress 的单元测试工具。

vuepress/client 会在模块作用域引用生成模块(@internal/*)与编译期 define(__VUEPRESS_DEV__ 等),因此无法在单元测试中直接导入。该包会替换它们,使插件或主题的客户端代码可以在单元测试中被渲染与交互,而无需运行沉重的 e2e 测试。

Usage

npm i -D @vuepress/test-utils@next @vue/test-utils happy-dom
vitest.config.ts
import { vuepressTestPlugin } from '@vuepress/test-utils'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [vuepressTestPlugin()],
})

若要测试 .vue 单文件组件,按常规在 plugins 中加入 Vue 插件(如 @vitejs/plugin-vue)即可。

若你的包使用了 resolve.alias 无法表达的模块别名(如 @vuepress/theme-default 的 @theme/*,它可能落到不同目录),请给插件传入 resolve 函数。

vitest.config.ts
export default defineConfig({
  plugins: [
    vue(),
    vuepressTestPlugin({
      resolve: (id) =>
        id.startsWith('@theme/') ? resolveThemeFile(id) : undefined,
    }),
  ],
})

Guide

渲染客户端代码

renderVuePress() 将客户端 app 渲染为字符串,可在 node 环境下使用,是断言各种选项组合的渲染结果最轻量的方式。

import { renderVuePress } from '@vuepress/test-utils/client'

const html = await renderVuePress({
  content: MyComponent,
  page: { path: '/', title: 'Home' },
  site: { title: 'My Site' },
})

mountVuePress() 通过 @vue/test-utils 挂载客户端 app,需要 DOM 环境:

// @vitest-environment happy-dom
import { mountVuePress } from '@vuepress/test-utils/client'

const wrapper = await mountVuePress({
  content: MyComponent,
  page: { path: '/', title: 'Home' },
})

await wrapper.find('.my-button').trigger('click')

提示

请把 // @vitest-environment happy-dom 放在文件最顶部、所有 import 之前,否则 Vitest 不会读取它。renderVuePress() 不需要它,因此不需要与 DOM 交互的测试请优先使用它。

两者的选项与 createTestClient() 相同。createTestClient() 返回的客户端可以多次渲染与挂载,并暴露客户端数据与路由:

const client = await createTestClient({ page: { path: '/', title: 'Home' } })

expect(await client.renderToString()).toContain('Home')

await client.router.push('/other/')

content 选项是当前页面的内容(即 page chunk),由布局渲染它。vuepress/client 的 Content 组件就是默认布局,因此未指定布局时内容会被直接渲染。若想不经过布局与客户端配置的根组件、直接渲染单个组件,请改用 rootComponent 选项。

clientConfigs 接受插件与主题的客户端配置,并应用它们的 setup、enhance、layouts 与 rootComponents。当某个组合式 API 依赖客户端配置提供的 provider 时,这是必须的。例如 @vuepress/theme-default 的主题数据由 useThemeData() 读取,而它由主题数据插件提供,因此必须把该插件的客户端配置传给 clientConfigs。

测试选项组合

插件选项是通过 define 全局变量而非 props 传给客户端的。collectClientDefines() 从真实插件中读取它们,stubClientDefines() 把它们应用到 globalThis,与 bundler 的行为完全一致。

import {
  collectClientDefines,
  createTestApp,
  stubClientDefines,
} from '@vuepress/test-utils'
import { mountVuePress } from '@vuepress/test-utils/client'

import myClientConfig from '../src/client/config.js'
import { myPlugin } from '../src/node/index.js'

const app = await createTestApp({ plugins: [myPlugin({ duration: 3000 })] })

const restore = stubClientDefines(await collectClientDefines(app))

const wrapper = await mountVuePress({
  clientConfigs: [myClientConfig],
  content: '...',
})

restore()

提示

在模块作用域读取 define 的模块,必须在 stubClientDefines() 之后导入。此时请使用 vi.resetModules() 配合动态 import。

为生成的模块打桩

VuePress 会在构建期生成插件引用的客户端模块:@internal/* 模块,以及由生成的客户端配置创建的 @temp/* 模块(如 @vuepress/plugin-revealjs 的 @temp/revealjs/index.js)。它们在单元测试中并不存在,因此引用它们的模块完全无法导入。stubModule() 会注册这类模块的导出值并使其可解析,在 node 与 happy-dom 环境下均可工作。

import { stubModule, stubTempModule } from '@vuepress/test-utils'

const restoreNotice = stubModule('@internal/noticeOptions', {
  NOTICE_OPTIONS: [{ path: '/', title: 'Notice' }],
})
const restoreReveal = stubTempModule('revealjs', {
  useRevealJs: () => Promise.resolve([Reveal]),
})

vi.resetModules()
const { useNoticeOptions } =
  await import('../src/client/composables/useNoticeOptions.js')

restoreNotice()
restoreReveal()

stubTempModule(name, ...) 是 stubModule('@temp/<name>/index.js', ...) 的快捷方式。任意模块 id 都可以打桩,包括仅在构建期存在的子路径别名,如 @vuepress/plugin-comment 的 @vuepress/plugin-comment/service。

模块 id 会映射到临时目录中的一个生成文件:把 id 追加到该目录,当 id 不以 .js 结尾时补上 .js 扩展名。id、导出名与引用它的模块都必须与真实生成模块一致。由于生成模块会重新导出它们,导出名只支持标识符与 default。

注意

Vite 在模块被转换时解析导入,而这发生在测试运行之前。因此引用被打桩模块的模块必须在 stubModule() 之后动态导入,若它可能已被缓存还需配合 vi.resetModules()。静态导入的模块无法被打桩。

对于必须静态导入的模块,请在仓库配置中通过 vuepressTestPlugin() 的 resolve 选项注册别名:

vitest.config.ts
const CLIENT_ALIASES: Record<string, string> = {
  // 由插件在构建期创建的子路径别名
  '@vuepress/plugin-comment/service':
    './plugins/blog/plugin-comment/src/client/components/WalineComment.js',
}

export default defineConfig({
  plugins: [vuepressTestPlugin({ resolve: (id) => CLIENT_ALIASES[id] })],
})

测试颜色模式

setColorMode() 会伪造 @vuepress/theme-default 用来解析夜间模式的两处来源:(prefers-color-scheme: dark) 媒体查询与 vuepress-color-scheme 本地存储键,同时设置 <html> 上的 data-theme。其他媒体查询会转发给真实的 window.matchMedia()。

// @vitest-environment happy-dom
import { setColorMode } from '@vuepress/test-utils/client'

const restore = setColorMode('dark')

const wrapper = await mountVuePress({ rootComponent: MyComponent })

expect(wrapper.find('.my-button').classes()).toContain('dark')

restore()

传入 { storage: 'auto' } 可在媒体查询仍报告夜间模式的同时把存储值保持为 auto,从而覆盖 useDarkMode() 的 prefers-color-scheme 分支。

测试 Node 侧代码

createTestApp() 使用临时 source 目录、空主题与空 bundler 构建 VuePress app,因此无需真实站点即可测试插件钩子、页面解析与 markdown 扩展。

import {
  createTestApp,
  createTestMarkdown,
  mockLogger,
} from '@vuepress/test-utils'

const app = await createTestApp({
  plugins: [myPlugin()],
  files: { 'README.md': '# Home' },
})

expect(app.pages.map(({ path }) => path)).toContain('/')

app.cleanup()

createTestMarkdown() 使用 VuePress 默认选项创建 markdown-it 实例,createTestPage() 创建无需源文件的页面,mockLogger() 静默 VuePress 日志并返回间谍函数以便断言。

Options

vuepressTestPlugin

basestring
默认值'/'

__VUEPRESS_BASE__ 的值。

devboolean

__VUEPRESS_DEV__ 的值。开发模式会启用 HMR 分支,它会访问单元测试中不存在的 __VUE_HMR_RUNTIME__。

versionstring

__VUEPRESS_VERSION__ 的值。默认读取已安装的 vuepress 包。

resolve(id: string) => string | undefined

解析 resolve.alias 无法表达的模块 id,如 @vuepress/theme-default 的 @theme/*,或仅在构建期存在的子路径别名(如 @vuepress/plugin-comment/service)。它会对每次导入调用,需返回文件路径或 undefined。通过 stubModule() 注册的模块会先于它被解析。

setColorMode options

由 setColorMode() 使用。

storageKeystring
默认值'vuepress-color-scheme'

保存颜色模式的本地存储键。

storage'auto' | 'light' | 'dark'

存储在 storageKey 下的值。默认为 colorMode 参数。

Test client options

由 createTestClient()、renderVuePress() 与 mountVuePress() 共用。

contentComponent | string

当前页面的内容,即 page chunk。字符串会作为原始 HTML 渲染。

rootComponentComponent

根组件。提供时会跳过布局与客户端配置的根组件。

pageTestPageOptions

当前页面。

page.pathstring
默认值'/'

页面的路由路径。

page.titlestring
默认值''

页面的标题。

page.langstring
默认值''

页面的语言。它会回退到站点语言环境的语言。

page.frontmatterPageFrontmatter

页面的 frontmatter。

page.dataRecord<string, unknown>

额外的页面数据,如 filePathRelative。

routestring

当前页面的路由路径。默认为 page.path。

sitePartial<SiteData>

站点数据。例如它的 locales 字段会根据路由路径切换站点语言环境数据。

layoutsRecord<string, Component>

布局组件。Layout 与 NotFound 默认为 Content。

routesRecord<string, TestRouteOptions>

额外的路由。每个路由接受 component 与 pageData。

redirectsRecord<string, string>

重定向。

themeDataRecord<string, unknown>

主题数据,由 @vuepress/plugin-theme-data/client 的 useThemeData() 读取。

clientConfigsClientConfig[]

需要应用的客户端配置。它们的 setup、enhance、layouts 与 rootComponents 都会生效。

ssrboolean

是否模拟 SSR 模式。renderToString() 默认 true,mount() 默认 false。

Test app options

由 createTestApp() 使用。它同时接受 VuePress app 配置的其余字段。

filesRecord<string, string>

需要在 source 目录中生成的 Markdown 文件。键为相对路径。

sourcestring

source 目录。默认为临时目录,会被 app.cleanup() 删除。

themeTheme
默认值emptyTheme

app 的主题。

initboolean

是否初始化 app。

prepareboolean

是否在初始化后准备 app。

在 GitHub 上编辑此页
上次更新: 2026/10/13 16:07
贡献者: Mister-Hope
上一页
@vuepress/helper