20260819173221

This commit is contained in:
oneao committed 2026-08-19 17:32:25 +08:00
1 parent 1ffc8e1ac9
commit a0a4273f4c
326 files changed
+301646 -2314

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { nextTick } from 'vue'
import { flushPromises } from '@vue/test-utils'
import LoginPage from '@/views/login/index.vue'
import { useAuthStore } from '@/stores/auth'
import { mountPage } from '../helpers/app.js'
// —— 单一接缝:mock 业务 API 层,确定性驱动页面状态(无需真实后端)——
vi.mock('@/services/api', () => ({
loginApi: vi.fn(),
}))
// —— mock 命令式 Message(避免 jsdom 下创建/操作 DOM 容器)——
// 该模块同时有具名导出与默认导出(页面用默认导入),两者都要提供。
// 注意:vi.mock 工厂会被提升,不能引用顶层变量,需内联。
vi.mock('@/components/ui/message/message-manager.js', () => {
const m = {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
}
return { Message: m, default: m }
})
import { loginApi } from '@/services/api'
import { Message } from '@/components/ui/message/message-manager.js'
// 把 Form 打桩为可定位、可 emit 的组件:页面测试只关心「自己逻辑」,
// 子组件(Form/Input/Button)由 shallowMount 自动打桩,这里仅给 Form 一个稳定 name 以便触发 @submit。
const formStub = {
name: 'Form',
template: '<form class="login-form-stub"><slot /></form>',
}
const validValues = { orgId: 'ORG1', account: 'admin', password: 'secret' }
beforeEach(() => {
// pinia persist 插件会把状态写入 localStorage,跨测试会泄漏,需清零
localStorage.clear()
sessionStorage.clear()
loginApi.mockReset()
Message.success.mockReset()
Message.error.mockReset()
Message.warning.mockReset()
})
describe('登录页', () => {
it('渲染标题与品牌文案', async () => {
const { wrapper } = await mountPage(LoginPage)
expect(wrapper.text()).toContain('登录 FMS')
expect(wrapper.text()).toContain('企业管理系统')
})
it('无 reason 时不展示会话提示条', async () => {
const { wrapper } = await mountPage(LoginPage)
expect(wrapper.find('[role="alert"]').exists()).toBe(false)
})
it('reason=expired:展示「登录已过期」提示', async () => {
const { wrapper } = await mountPage(LoginPage, { location: '/login?reason=expired' })
await nextTick()
const hint = wrapper.find('[role="alert"]')
expect(hint.exists()).toBe(true)
expect(hint.text()).toContain('登录已过期')
})
it('reason=session-replaced:展示「已在其他设备登录」提示', async () => {
const { wrapper } = await mountPage(LoginPage, { location: '/login?reason=session-replaced' })
await nextTick()
expect(wrapper.find('[role="alert"]').text()).toContain('已在其他设备登录')
})
it('表单校验失败:触发 onFailed,弹出 warning', async () => {
const { wrapper } = await mountPage(LoginPage, { stubs: { Form: formStub } })
await wrapper.findComponent({ name: 'Form' }).vm.$emit('submit-failed')
await nextTick()
expect(Message.warning).toHaveBeenCalledWith('请填写完整的登录信息')
})
it('登录成功:调用 loginApi、写入会话、跳转 redirect、弹成功', async () => {
loginApi.mockResolvedValue({
data: { token: 'TK', user: { id: 1, account: 'admin' }, orgid: 'ORG1' },
code: 0,
success: true,
})
const { wrapper, router } = await mountPage(LoginPage, {
location: '/login?redirect=/dashboard',
stubs: { Form: formStub },
})
await wrapper.findComponent({ name: 'Form' }).vm.$emit('submit', validValues)
await flushPromises()
await nextTick()
await flushPromises()
// 请求按页面入参发出
expect(loginApi).toHaveBeenCalledWith('ORG1', 'admin', 'secret')
// 会话已写入
const auth = useAuthStore()
expect(auth.isAuthenticated).toBe(true)
expect(auth.loginInfo.token).toBe('TK')
expect(auth.userInfo.orgId).toBe('ORG1')
// 跳转目标
expect(router.currentRoute.value.fullPath).toBe('/dashboard')
// 成功提示
expect(Message.success).toHaveBeenCalledWith('登录成功')
})
it('登录失败:弹 error,且不写入会话', async () => {
loginApi.mockRejectedValue(new Error('账号或密码错误'))
const { wrapper } = await mountPage(LoginPage, { stubs: { Form: formStub } })
await wrapper.findComponent({ name: 'Form' }).vm.$emit('submit', validValues)
await flushPromises()
await nextTick()
await flushPromises()
expect(Message.error).toHaveBeenCalledWith('账号或密码错误')
expect(useAuthStore().isAuthenticated).toBe(false)
})
})