vitest-browser-svelte
由社区提供的 vitest-browser-svelte 包可在 浏览器模式 中渲染 Svelte 组件。
import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
import Component from './Component.svelte'
test('counter button increments the count', async () => {
const screen = await render(Component, {
initialCount: 1,
})
await screen.getByRole('button', { name: 'Increment' }).click()
await expect.element(screen.getByText('Count is 2')).toBeVisible()
})WARNING
该库的灵感来至于 @testing-library/svelte。
如果你之前使用过 @testing-library/svelte,仍可以继续延用。但 vitest-browser-svelte 包提供了浏览器模式下特有的优势,这些是 @testing-library/svelte 所不具备的:
vitest-browser-svelte 返回的 API 能与内置的 定位器、用户事件 及 断言 更好的协作。例如:即使组件在断言间被重新渲染,Vitest 仍会自动重试元素查找,直至断言成功。
该包提供两个入口点:vitest-browser-svelte 和 vitest-browser-svelte/pure。两者暴露完全相同的 API,但在下一个测试开始前 pure 不会添加移除组件处理程序。
渲染函数
export function render<C extends Component>(
Component: ComponentImport<C>,
options?: ComponentOptions<C>,
renderOptions?: SetupOptions
): RenderResult<C> & PromiseLike<RenderResult<C>>The render function records a svelte.render trace mark, visible in the Trace View.
WARNING
Synchronous usage of render is deprecated and will be removed in the next major version. Please always await the result:
const screen = render(Component)
const screen = await render(Component) 选项
render 函数支持两种传参方式,一种是向 mount 传入配置选项,另一种则是直接向组件传入属性:
const screen = await render(Component, {
props: {
initialCount: 1,
},
initialCount: 1,
})props
组件属性。
target
默认情况下,Vitest 会创建一个 div 并添加到 document.body 上,然后在该节点中渲染你的组件。如果提供自定义的 HTMLElement 容器,则不会自动添加,你需要在调用 render 前手动执行 document.body.appendChild(container)。
例如,当测试 tbody 元素时,它不能作为 div 的子元素。在这个例子中,你可以指定一个 table 作为渲染容器。
const table = document.createElement('table')
const screen = await render(TableBody, {
props,
// ⚠️ 渲染前需手动将元素添加到 `body`
target: document.body.appendChild(table),
})baseElement
baseElement 可通过第三个参数传递。除非特殊情况,否则极少需要使用此选项。
如果指定了 target 参数,则默认以此为根元素,否则将默认使用 document.body。该元素既作为查询操作的根节点,也会在使用 debug() 时被输出展示。
渲染结果
除文档记载的返回值外,render 函数还会返回相对于 baseElement 的所有可用 定位器,包括 自定义定位器。
const screen = await render(TableBody, props)
await screen.getByRole('link', { name: 'Expand' }).click()container
Svelte 组件将被渲染到 container 这个 DOM 容器中。这是一个常规 DOM 节点,因此理论上可以通过 container.querySelector 等方式检查子元素。
DANGER
如果你需通过 container 查询渲染元素,你应该重新考虑测试方法!定位器 专为应对组件变更而设计,比直接查询容器更具稳定性。应避免使用 container 查询元素!
component
已挂载的 Svelte 组件实例。如需访问组件方法和属性,可通过此实例进行操作。
const { component } = await render(Counter, {
initialCount: 0,
})
// 访问组件导出项locator
container 的 定位器。适用于在组件范围内查找元素或传递给其他断言语句场景:
import { render } from 'vitest-browser-svelte'
const { locator } = await render(NumberDisplay, {
number: 2,
})
await locator.getByRole('button').click()
await expect.element(locator).toHaveTextContent('Hello World')debug
function debug(
el?: HTMLElement | HTMLElement[] | Locator | Locator[],
): void此方法是 console.log(prettyDOM(baseElement)) 的简写形式,用于在控制台输出容器或指定元素的 DOM 内容。
rerender
function rerender(props: Partial<ComponentProps<T>>): Promise<void>Updates the component's props and waits for Svelte to apply the changes. Use this to test how your component responds to prop changes. Also records a svelte.rerender trace mark in the Trace View.
import { render } from 'vitest-browser-svelte'
const { rerender } = await render(NumberDisplay, {
number: 1,
})
// 使用新属性重新渲染同一个组件
await rerender({ number: 2 })unmount
function unmount(): Promise<void>Unmount and destroy the Svelte component. Also records a svelte.unmount trace mark in the Trace View. This is useful for testing what happens when your component is removed from the page (like testing that you don't leave event handlers hanging around causing memory leaks).
WARNING
Synchronous usage of unmount is deprecated and will be removed in the next major version. Please always await the result.
import { render } from 'vitest-browser-svelte'
const { container, unmount } = render(Component)
unmount()
// 组件已被卸载,此时:container.innerHTML === ''cleanup
export function cleanup(): void移除所有通过 render 方法渲染的组件。
扩展查询
如果想扩展定位器的查询方法,详情参阅 “自定义定位器”。例如,要为 render 扩展一个新的定位器,可使用 locators.extend API 进行定义:
import { locators } from 'vitest/browser'
import { render } from 'vitest-browser-svelte'
locators.extend({
getByArticleTitle(title) {
return `[data-title="${title}"]`
},
})
const screen = await render(Component)
await expect.element(
screen.getByArticleTitle('Hello World')
).toBeVisible()代码片段
对于简单的代码片段,您可以使用包装组件和 “占位” 子元素进行测试。通过设置 data-testid 属性帮助测试插槽内容。
import { render } from 'vitest-browser-svelte'
import { expect, test } from 'vitest'
import SubjectTest from './basic-snippet.test.svelte'
test('basic snippet', async () => {
const screen = await render(SubjectTest)
const heading = screen.getByRole('heading')
const child = heading.getByTestId('child')
await expect.element(child).toBeInTheDocument()
})<script>
let { children } = $props()
</script>
<h1>
{@render children?.()}
</h1><script>
import Subject from './basic-snippet.svelte'
</script>
<Subject>
<span data-testid="child"></span>
</Subject>对于更复杂的代码片段(例如需要检查参数的情况),可以使用 Svelte 的 createRawSnippet API。
import { render } from 'vitest-browser-svelte'
import { createRawSnippet } from 'svelte'
import { expect, test } from 'vitest'
import Subject from './complex-snippet.svelte'
test('renders greeting in message snippet', async () => {
const screen = await render(Subject, {
name: 'Alice',
message: createRawSnippet(greeting => ({
render: () => `<span data-testid="message">${greeting()}</span>`,
})),
})
const message = screen.getByTestId('message')
await expect.element(message).toHaveTextContent('Hello, Alice!')
})<script>
let { name, message } = $props()
const greeting = $derived(`Hello, ${name}!`)
</script>
<p>
{@render message?.(greeting)}
</p>