import { describe, it, expect, vi, beforeEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import { createI18n } from 'vue-i18n' import LoginView from '@/views/login/index.vue' import { useUserStore } from '@/store/user' import { wrapResponse, wrapErrorResponse, mockLoginResponse } from '../../../fixtures' // Mock vue-router const mockPush = vi.fn() const mockRoute = { query: {} } vi.mock('vue-router', () => ({ useRouter: () => ({ push: mockPush }), useRoute: () => mockRoute })) // Mock element-plus vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn(), error: vi.fn(), info: vi.fn() } })) // Mock __APP_VERSION__ vi.stubGlobal('__APP_VERSION__', '1.0.0') // Create i18n instance for tests const i18n = createI18n({ legacy: false, locale: 'zh-CN', messages: { 'zh-CN': { 用户名: '用户名', 密码: '密码', 登录: '登录', 记住我: '记住我', 请输入用户名: '请输入用户名', 请输入密码: '请输入密码', 登录成功: '登录成功', 登录失败: '登录失败', '登录失败,请检查网络': '登录失败,请检查网络', 摄像头管理系统: '摄像头管理系统', 欢迎回来: '欢迎回来', '登录您的管理后台,开始管理您的业务': '登录您的管理后台,开始管理您的业务', 用户: '用户', 稳定性: '稳定性', 技术支持: '技术支持', '忘记密码?': '忘记密码?', 请联系管理员重置密码: '请联系管理员重置密码' } } }) describe('Login View', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() mockPush.mockClear() mockRoute.query = {} localStorage.clear() }) const mountLogin = () => { return mount(LoginView, { global: { plugins: [createPinia(), i18n], stubs: { LangDropdown: true } } }) } describe('表单渲染', () => { it('应该正确渲染登录表单', () => { const wrapper = mountLogin() expect(wrapper.find('input[placeholder="用户名"]').exists()).toBe(true) expect(wrapper.find('input[placeholder="密码"]').exists()).toBe(true) expect(wrapper.find('button[type="submit"]').exists()).toBe(true) }) it('密码输入框默认为密码类型', () => { const wrapper = mountLogin() const passwordInput = wrapper.find('input[placeholder="密码"]') expect(passwordInput.attributes('type')).toBe('password') }) it('点击显示密码按钮切换密码可见性', async () => { const wrapper = mountLogin() const passwordInput = wrapper.find('input[placeholder="密码"]') const toggleBtn = wrapper.find('.login__password-toggle') expect(passwordInput.attributes('type')).toBe('password') await toggleBtn.trigger('click') expect(passwordInput.attributes('type')).toBe('text') await toggleBtn.trigger('click') expect(passwordInput.attributes('type')).toBe('password') }) }) describe('表单验证', () => { it('空用户名提交显示错误', async () => { const wrapper = mountLogin() await wrapper.find('input[placeholder="密码"]').setValue('password') await wrapper.find('form').trigger('submit') expect(wrapper.text()).toContain('请输入用户名') }) it('空密码提交显示错误', async () => { const wrapper = mountLogin() await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('form').trigger('submit') expect(wrapper.text()).toContain('请输入密码') }) it('用户名和密码都为空时显示两个错误', async () => { const wrapper = mountLogin() await wrapper.find('form').trigger('submit') expect(wrapper.text()).toContain('请输入用户名') expect(wrapper.text()).toContain('请输入密码') }) }) describe('登录功能', () => { it('登录成功后跳转到首页', async () => { const wrapper = mountLogin() const userStore = useUserStore() // Mock loginAction vi.spyOn(userStore, 'loginAction').mockResolvedValue(wrapResponse(mockLoginResponse)) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') await flushPromises() expect(userStore.loginAction).toHaveBeenCalledWith({ username: 'admin', password: '123456' }) expect(mockPush).toHaveBeenCalledWith('/') }) it('登录成功后跳转到 redirect 参数指定的页面', async () => { mockRoute.query = { redirect: '/dashboard' } const wrapper = mountLogin() const userStore = useUserStore() vi.spyOn(userStore, 'loginAction').mockResolvedValue(wrapResponse(mockLoginResponse)) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') await flushPromises() expect(mockPush).toHaveBeenCalledWith('/dashboard') }) it('登录失败显示错误消息', async () => { const { ElMessage } = await import('element-plus') const wrapper = mountLogin() const userStore = useUserStore() vi.spyOn(userStore, 'loginAction').mockResolvedValue(wrapErrorResponse('用户名或密码错误', 401) as any) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('wrongpassword') await wrapper.find('form').trigger('submit') await flushPromises() expect(ElMessage.error).toHaveBeenCalledWith('用户名或密码错误') expect(mockPush).not.toHaveBeenCalled() }) it('登录请求异常显示网络错误', async () => { const { ElMessage } = await import('element-plus') const wrapper = mountLogin() const userStore = useUserStore() vi.spyOn(userStore, 'loginAction').mockRejectedValue(new Error('Network Error')) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') await flushPromises() expect(ElMessage.error).toHaveBeenCalled() expect(mockPush).not.toHaveBeenCalled() }) it('登录时显示加载状态', async () => { const wrapper = mountLogin() const userStore = useUserStore() // Mock a slow login vi.spyOn(userStore, 'loginAction').mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve(wrapResponse(mockLoginResponse)), 100)) ) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') // Button should be disabled during loading const submitBtn = wrapper.find('button[type="submit"]') expect(submitBtn.attributes('disabled')).toBeDefined() await flushPromises() }) }) describe('记住我功能', () => { it('记住我选中时保存用户名到 localStorage', async () => { const wrapper = mountLogin() const userStore = useUserStore() vi.spyOn(userStore, 'loginAction').mockResolvedValue(wrapResponse(mockLoginResponse)) const checkbox = wrapper.find('.login__checkbox') await checkbox.setValue(true) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') await flushPromises() expect(localStorage.getItem('login_remember')).toBe('admin') }) it('记住我未选中时不保存用户名', async () => { const wrapper = mountLogin() const userStore = useUserStore() vi.spyOn(userStore, 'loginAction').mockResolvedValue(wrapResponse(mockLoginResponse)) const checkbox = wrapper.find('.login__checkbox') await checkbox.setValue(false) await wrapper.find('input[placeholder="用户名"]').setValue('admin') await wrapper.find('input[placeholder="密码"]').setValue('123456') await wrapper.find('form').trigger('submit') await flushPromises() expect(localStorage.getItem('login_remember')).toBeNull() }) it('页面加载时自动填充保存的用户名', async () => { localStorage.setItem('login_remember', 'saveduser') const wrapper = mountLogin() await flushPromises() const usernameInput = wrapper.find('input[placeholder="用户名"]') expect((usernameInput.element as HTMLInputElement).value).toBe('saveduser') }) }) })