import { flushPromises, mount } from '@vue/test-utils' import ElementPlus, { ElMessage } from 'element-plus' import { beforeEach, describe, expect, test, vi } from 'vitest' import Users from './Users.vue' import { organizationsApi } from '../api/organizations' const RoleSelectStub = { props: { modelValue: Array, multiple: Boolean }, emits: ['update:modelValue'], template: '
' } const RoleOptionStub = { props: ['label', 'value', 'disabled'], template: '{{ label }}' } const usersStore = vi.hoisted(() => ({ users: [], loading: false, page: { currentPage: 1, pageSize: 20, total: 0, totalPages: 1 }, fetchUsers: vi.fn(), createUser: vi.fn(), updateUser: vi.fn(), deleteUser: vi.fn() })) vi.mock('../stores/users', () => ({ useUsersStore: () => usersStore })) vi.mock('../stores/auth', () => ({ useAuthStore: () => ({ isAdmin: true }) })) vi.mock('../api/organizations', () => ({ organizationsApi: { listRoles: vi.fn() } })) function mountUsers() { return mount(Users, { global: { plugins: [ElementPlus], stubs: { teleport: true, ElSelect: RoleSelectStub, ElOption: RoleOptionStub } } }) } describe('用户管理角色', () => { beforeEach(() => { usersStore.users = [{ id: 7, username: 'zhangsan', description: '测试用户', created_at: '2026-09-16T09:00:00+08:00', roles: [ { id: 2, code: 'warehouse_admin', name: '仓库管理员', enabled: true, permissions: [] }, { id: 3, code: 'purchaser', name: '采购人员', enabled: true, permissions: [] } ] }] usersStore.fetchUsers.mockReset() organizationsApi.listRoles.mockResolvedValue({ data: [ { id: 2, code: 'warehouse_admin', name: '仓库管理员', enabled: true, permissions: [] }, { id: 3, code: 'purchaser', name: '采购人员', enabled: true, permissions: [] } ] }) }) test('列表显示用户的所有中文角色名称', async () => { const wrapper = mount(Users, { global: { plugins: [ElementPlus], stubs: { teleport: true, ElSelect: RoleSelectStub, ElOption: RoleOptionStub } } }) await flushPromises() expect(wrapper.text()).toContain('仓库管理员') expect(wrapper.text()).toContain('采购人员') expect(wrapper.text()).not.toContain('普通用户') wrapper.unmount() }) test('新增和编辑使用角色与权限中的多选角色', async () => { const wrapper = mount(Users, { global: { plugins: [ElementPlus], stubs: { teleport: true, ElSelect: RoleSelectStub, ElOption: RoleOptionStub } } }) await flushPromises() const addButton = wrapper.findAll('button').find(button => button.text().includes('新增用户')) await addButton.trigger('click') await flushPromises() const roleSelect = wrapper.findComponent(RoleSelectStub) expect(roleSelect.props('multiple')).toBe(true) expect(wrapper.text()).toContain('仓库管理员') expect(wrapper.text()).toContain('采购人员') wrapper.unmount() }) }) describe('新增用户后列表与搜索框保持一致', () => { beforeEach(() => { usersStore.fetchUsers.mockReset() usersStore.createUser.mockReset() usersStore.updateUser.mockReset() usersStore.createUser.mockResolvedValue({ data: {} }) usersStore.updateUser.mockResolvedValue({ data: {} }) organizationsApi.listRoles.mockResolvedValue({ data: [] }) vi.spyOn(ElMessage, 'success').mockImplementation(() => {}) }) // store 里新建后的那次刷新是刻意清空筛选的(好让刚建的用户出现在列表里),页面这份 // 状态(搜索框)必须跟着一起清空:否则会出现搜索框写着用户名、列表却是全量的 // 表里不一,用户会把全量列表当成筛选结果。 test('新增用户成功后搜索框要清空', async () => { const wrapper = mountUsers() await flushPromises() const state = wrapper.vm.$.setupState await wrapper.find('.search-bar input').setValue('zhangsan') await flushPromises() expect(state.searchKeyword).toBe('zhangsan') state.form.username = 'lisi' state.form.password = '123456' state.form.role_ids = [2] await state.handleSave() await flushPromises() expect(usersStore.createUser).toHaveBeenCalled() expect(state.searchKeyword).toBe('') wrapper.unmount() }) // 编辑走的是「沿用搜索」那条路:刷新后列表仍是筛选结果,搜索框不该被动。 test('编辑用户成功后搜索框要保留', async () => { const wrapper = mountUsers() await flushPromises() const state = wrapper.vm.$.setupState await wrapper.find('.search-bar input').setValue('zhangsan') await flushPromises() state.editingId = 7 state.form.username = 'zhangsan' state.form.role_ids = [2] await state.handleSave() await flushPromises() expect(usersStore.updateUser).toHaveBeenCalled() expect(state.searchKeyword).toBe('zhangsan') wrapper.unmount() }) })