import { createPinia, setActivePinia } from 'pinia' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { flushPromises, shallowMount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createPlatformRole, listPlatformPermissions, listPlatformRoles, type PlatformRoleResource } from '@/modules/iam/api/platformIam' import { useAuthStore } from '@/core/auth/auth' import RolesView from './RolesView.vue' const rolesSource = readFileSync(resolve(process.cwd(), 'src/modules/iam/pages/RolesView.vue'), 'utf8') vi.mock('@/modules/iam/api/platformIam', () => ({ createPlatformRole: vi.fn(), listPlatformPermissions: vi.fn(), listPlatformRoles: vi.fn(), updatePlatformRole: vi.fn(), changePlatformRoleStatus: vi.fn(), deletePlatformRole: vi.fn(), replacePlatformRolePermissions: vi.fn(), })) const platformRole: PlatformRoleResource = { id: 4000002, tenantId: 0, appCode: 'PLATFORM', roleCode: 'CUSTOMER_REVIEWER', roleName: '企业审核员', roleType: 'SYSTEM', status: 'ACTIVE', description: '企业资料与入驻审核', memberCount: 3, } function mountView(pinia = createPinia()) { setActivePinia(pinia) const auth = useAuthStore() if (auth.permissions.length === 0) auth.permissions = ['platform:role:list', 'platform:role:create', 'platform:role:grant'] return shallowMount(RolesView, { global: { plugins: [pinia], directives: { loading: () => undefined }, stubs: { AdminPage: { template: '
' }, ListPageLayout: { template: '
' }, FormSection: { template: '
' }, AppDialog: { template: '
' }, LoadErrorState: { props: ['message'], template: '
{{ message }}
' }, DataTable: { props: ['data'], template: '
{{ data.length }}
' }, 'el-alert': { props: ['title'], template: '
{{ title }}
' }, 'el-input': true, 'el-option': true, 'el-select': true, 'el-button': true, 'el-table': true, 'el-table-column': true, 'el-tag': true, 'el-radio-group': true, 'el-radio-button': true, 'el-drawer': { props: ['modelValue'], template: '
', }, 'el-form': { template: '
' }, 'el-form-item': { template: '' }, 'el-tree': { props: { checkStrictly: Boolean }, template: '
', }, }, }, }) } describe('RolesView', () => { it('keeps the shared status badge prop defined during table slot initialization', () => { expect(rolesSource).toContain("(row as Role).status || '—'") }) it('uses bordered striped tables and semantic role operation buttons', () => { expect(rolesSource).toContain('appearance="integrated" :stripe="false"') expect(rolesSource).toContain('size="small" data-tone="neutral"') expect(rolesSource).toContain('size="small" :data-tone=') expect(rolesSource).toContain('size="small" data-tone="danger"') expect(rolesSource).toContain('>配置') expect(rolesSource).toContain(':data-tone=') expect(rolesSource).toContain('>删除') expect(rolesSource).toContain('@media (max-width: 720px)') }) beforeEach(() => { setActivePinia(createPinia()) vi.mocked(listPlatformRoles).mockReset() vi.mocked(listPlatformPermissions).mockReset() vi.mocked(listPlatformPermissions).mockResolvedValue([]) vi.mocked(createPlatformRole).mockReset() }) it('links menu groups and permissions so partial selections use the half-checked state', () => { const wrapper = mountView() expect(wrapper.get('[data-testid="permission-tree"]').attributes('data-check-strictly')).toBe('false') expect(rolesSource).not.toContain('check-strictly') expect(rolesSource).not.toContain("disabled: 'disabled'") }) it('restores saved permission ids after the dialog tree has mounted', () => { expect(rolesSource).toContain('@opened="initializePermissionSelection"') expect(rolesSource).toContain('treeRef.value?.setCheckedKeys([...form.permissionIds])') expect(rolesSource).not.toContain('{{ data.code }}') expect(rolesSource).not.toContain('nextTick(() =>') }) it('shows the role code in the default list layout', () => { expect(rolesSource).toContain("{ key: 'roleCode', prop: 'code', label: '角色编码', minWidth: 180, showOverflowTooltip: true }") expect(rolesSource).toContain('{{ (row as Role).code }}') expect(rolesSource).toContain('aria-label="平台角色列表"') }) it('uses the prototype-sized centered role editor with responsive split panes', () => { expect(rolesSource).toContain(' { const pinia = createPinia() setActivePinia(pinia) const auth = useAuthStore() auth.isServerSession = true auth.permissions = ['platform:role:list', 'platform:role:create'] vi.mocked(listPlatformRoles).mockResolvedValue([platformRole]) const wrapper = mountView(pinia) await flushPromises() expect(listPlatformRoles).toHaveBeenCalledTimes(1) expect(rolesSource).toContain('userCount: role.memberCount') expect(rolesSource).not.toContain('listPlatformInternalUsers') expect(wrapper.get('[data-testid="row-count"]').text()).toBe('1') expect(wrapper.text()).not.toContain('角色负责聚合菜单、操作和数据范围,内部用户可分配多个角色') expect(wrapper.find('[data-testid="permission-tree"]').exists()).toBe(false) }) it('keeps a failed IAM role list recoverable', async () => { const pinia = createPinia() setActivePinia(pinia) const auth = useAuthStore() auth.isServerSession = true auth.permissions = ['platform:role:list'] vi.mocked(listPlatformRoles).mockRejectedValue(new Error('IAM unavailable')) const wrapper = mountView(pinia) await flushPromises() const alertText = wrapper.findAll('[role="alert"]').map((item) => item.text()).join(' ') expect(alertText).toContain('平台角色列表加载失败,请稍后重试') expect(alertText).not.toContain('IAM unavailable') }) })