123 lines
4.5 KiB
JavaScript
123 lines
4.5 KiB
JavaScript
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)
|
|
})
|
|
})
|