组件行为、浏览器模式与 CI:练习答案
三层断言的边界可以这样写:
ts
// 纯函数层:无需 DOM
expect(validateEmail('')).toEqual({ ok: false, code: 'empty' })
expect(validateEmail('abc')).toEqual({ ok: false, code: 'invalid' })ts
// jsdom 层:用户找到控件并观察错误
const input = screen.getByRole('textbox', { name: '邮箱' })
await user.type(input, 'abc')
await user.click(screen.getByRole('button', { name: '登录' }))
expect(screen.getByRole('alert')).toHaveTextContent('请输入有效邮箱')
expect(input).toHaveFocus()ts
// Browser/E2E 层:真实地址栏和会话
await page.getByRole('textbox', { name: '邮箱' }).fill('user@example.com')
await page.getByRole('button', { name: '登录' }).click()
await expect(page).toHaveURL('/dashboard')
await page.reload()
await expect(page).toHaveURL('/dashboard')第一条断言验证业务规则,第二条验证 DOM、事件和焦点,第三条验证路由、服务端和浏览器持久化。把第三条塞进 jsdom,通常只能断言 history.pushState 被调用,不能证明真实导航、资源加载和刷新后的会话。
CI 最小脚本示例:
json
{
"scripts": {
"test:unit": "vitest run --project unit",
"test:browser": "vitest run --project browser",
"test:coverage": "vitest run --coverage"
}
}先让 test:unit 独立通过,再运行浏览器项目;不要在 CI 中用 --runInBand 等 Jest 选项猜测并行行为。出现 flaky 时保存重复运行的输入、浏览器版本、trace/截图与首次失败,而不是盲目增加重试次数。
可验证结果:纯函数测试可在 Node 运行;组件测试需要 jsdom;URL 和刷新测试需要 Browser Mode 或 E2E。若环境不具备浏览器,必须把第三层标记为未执行,不能将模拟 DOM 结果当作真实浏览器证据。