| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- 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')
- })
- })
- })
|