import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { mount, flushPromises } from '@vue/test-utils' import { nextTick } from 'vue' import UserFormModal from '@/components/UserFormModal.vue' 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: { post: vi.fn(), put: vi.fn(), }, })) const mockUserData = { id: 1, username: 'testuser', email: 'test@test.com', status: 1, role_id: 1, ext: null, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z', } describe('UserFormModal', () => { let wrapper: ReturnType beforeEach(() => { vi.restoreAllMocks() document.body.innerHTML = '' }) afterEach(() => { wrapper?.unmount() document.body.innerHTML = '' }) async function mountModal(props = {}) { wrapper = mount(UserFormModal, { props: { open: true, mode: 'create' as const, userData: null, ...props, }, attachTo: document.body, }) await flushPromises() await nextTick() return wrapper } // 查找 teleported 到 body 的模态框内容 function queryBody(selector: string) { return document.body.querySelectorAll(selector) } describe('create mode', () => { it('renders create title', async () => { await mountModal() const title = document.body.querySelector('.ant-modal-title') expect(title?.textContent).toBe('新建用户') }) it('shows password field in create mode', async () => { await mountModal() const labels = Array.from(queryBody('.ant-form-item label')) .map((el) => el.textContent?.trim()) expect(labels).toContain('密码') }) it('shows all four form fields', async () => { await mountModal() const formItems = queryBody('.ant-form-item') expect(formItems).toHaveLength(4) // username, password, email, status }) it('emits update:open false on cancel', async () => { const w = await mountModal() // 找到取消按钮(模态框 footer 中非 primary 按钮) const buttons = Array.from(queryBody('.ant-modal-footer .ant-btn')) const cancelBtn = buttons.find((b) => !b.classList.contains('ant-btn-primary')) cancelBtn?.dispatchEvent(new Event('click')) await flushPromises() expect(w.emitted('update:open')?.[0]).toEqual([false]) }) }) describe('edit mode', () => { it('renders edit title', async () => { await mountModal({ mode: 'edit', userData: mockUserData }) const title = document.body.querySelector('.ant-modal-title') expect(title?.textContent).toBe('编辑用户') }) it('hides password field in edit mode', async () => { await mountModal({ mode: 'edit', userData: mockUserData }) const labels = Array.from(queryBody('.ant-form-item label')) .map((el) => el.textContent?.trim()) expect(labels).not.toContain('密码') }) it('shows three form fields (no password)', async () => { await mountModal({ mode: 'edit', userData: mockUserData }) const formItems = queryBody('.ant-form-item') expect(formItems).toHaveLength(3) // username, email, status }) it('prefills form with user data', async () => { // 先以 open=false 挂载,再切换为 true 触发 watch 预填数据(与实际使用流程一致) const w = await mountModal({ mode: 'edit', userData: mockUserData, open: false }) await w.setProps({ open: true }) await flushPromises() await nextTick() const inputs = Array.from(queryBody('input')) as HTMLInputElement[] const values = inputs.map((i) => i.value) expect(values).toContain('testuser') expect(values).toContain('test@test.com') }) }) describe('form submission', () => { it('calls api.post for create mode', async () => { const { api } = await import('@/utils/request') vi.mocked(api.post).mockResolvedValueOnce({}) await mountModal() // 填充表单 const inputs = Array.from(queryBody('.ant-form-item input')) as HTMLInputElement[] expect(inputs.length).toBeGreaterThanOrEqual(3) inputs[0]!.value = 'newuser' inputs[0]!.dispatchEvent(new Event('input')) inputs[1]!.value = 'password123' inputs[1]!.dispatchEvent(new Event('input')) inputs[2]!.value = 'new@test.com' inputs[2]!.dispatchEvent(new Event('input')) await flushPromises() // 点击确定按钮 const okBtn = document.body.querySelector('.ant-modal-footer .ant-btn-primary') as HTMLElement okBtn?.click() await flushPromises() }) it('calls api.put for edit mode', async () => { const { api } = await import('@/utils/request') vi.mocked(api.put).mockResolvedValueOnce({}) await mountModal({ mode: 'edit', userData: mockUserData }) // 点击确定按钮 const okBtn = document.body.querySelector('.ant-modal-footer .ant-btn-primary') as HTMLElement expect(okBtn).toBeTruthy() okBtn?.click() await flushPromises() }) }) })