import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { setActivePinia, createPinia } from 'pinia' import { mount, flushPromises } from '@vue/test-utils' import { nextTick } from 'vue' import { message } from 'ant-design-vue' import { ApiError } from '@/types/api' import { useUserStore } from '@/stores/user' Object.defineProperty(window, 'matchMedia', { writable: true, value: (query: string) => ({ matches: false, media: query, onchange: null, addListener: () => {}, removeListener: () => {}, addEventListener: () => {}, removeEventListener: () => {}, dispatchEvent: () => false, }), }) vi.mock('@/utils/request', () => ({ api: { get: vi.fn(), post: vi.fn(), put: vi.fn(), }, })) const mockPush = vi.fn() vi.mock('vue-router', () => ({ useRouter: () => ({ push: mockPush }), useRoute: () => ({ path: '/profile/password' }), })) describe('Password Change Page', () => { let wrapper: ReturnType beforeEach(() => { setActivePinia(createPinia()) localStorage.clear() vi.restoreAllMocks() mockPush.mockReset() vi.useFakeTimers() }) afterEach(() => { wrapper?.unmount() vi.useRealTimers() }) async function mountPage() { const store = useUserStore() store.setToken('h.p.s', 'r.p.s', true) store.setUser({ id: 1, username: 'u', email: 'u@e.com', role: 'user', status: 1 }) const { default: PasswordPage } = await import('../password.vue') wrapper = mount(PasswordPage, { attachTo: document.body }) await flushPromises() await nextTick() return wrapper } it('renders three password fields', async () => { await mountPage() const html = wrapper.html() expect(html).toContain('当前密码') expect(html).toContain('新密码') expect(html).toContain('确认新密码') }) it('submits password change and redirects to login', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockResolvedValueOnce(undefined) const successSpy = vi.spyOn(message, 'success').mockImplementation(() => ({}) as never) await mountPage() const vm = wrapper.getCurrentComponent().setupState vm.formState.old_password = 'oldpass' vm.formState.new_password = 'newpass123' vm.formState.new_password_confirmation = 'newpass123' vm.formRef = { validate: vi.fn().mockResolvedValue(true) } await vm.handleSubmit() await flushPromises() expect(api.put).toHaveBeenCalledWith('/api/v1/me/password', { old_password: 'oldpass', new_password: 'newpass123', new_password_confirmation: 'newpass123', }) expect(successSpy).toHaveBeenCalledWith('密码修改成功,请重新登录') vi.advanceTimersByTime(1500) expect(mockPush).toHaveBeenCalledWith('/login') successSpy.mockRestore() }) it('shows error message on ApiError (wrong old password)', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockRejectedValueOnce(new ApiError('旧密码错误', 422)) const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never) await mountPage() const vm = wrapper.getCurrentComponent().setupState vm.formState.old_password = 'wrong' vm.formState.new_password = 'newpass123' vm.formState.new_password_confirmation = 'newpass123' vm.formRef = { validate: vi.fn().mockResolvedValue(true) } await vm.handleSubmit() await flushPromises() expect(errorSpy).toHaveBeenCalledWith('旧密码错误') expect(mockPush).not.toHaveBeenCalled() errorSpy.mockRestore() }) it('shows network error on generic failure', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockRejectedValueOnce(new Error('Network')) const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as never) await mountPage() const vm = wrapper.getCurrentComponent().setupState vm.formRef = { validate: vi.fn().mockResolvedValue(true) } await vm.handleSubmit() await flushPromises() expect(errorSpy).toHaveBeenCalledWith('密码修改失败,请检查网络连接') errorSpy.mockRestore() }) it('does not redirect on error', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockRejectedValueOnce(new ApiError('旧密码错误', 422)) vi.spyOn(message, 'error').mockImplementation(() => ({}) as never) await mountPage() const vm = wrapper.getCurrentComponent().setupState vm.formRef = { validate: vi.fn().mockResolvedValue(true) } await vm.handleSubmit() await flushPromises() vi.advanceTimersByTime(2000) expect(mockPush).not.toHaveBeenCalled() }) })