Files
admin-web/src/modules/iam/pages/RolesView.test.ts
T

161 lines
7.0 KiB
TypeScript
Raw Normal View History

2026-07-30 17:59:43 +08:00
import { createPinia, setActivePinia } from 'pinia'
2026-08-12 14:08:33 +08:00
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
2026-07-30 17:59:43 +08:00
import { flushPromises, shallowMount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
2026-08-17 19:52:04 +08:00
import { createPlatformRole, listPlatformPermissions, listPlatformRoles, type PlatformRoleResource } from '@/modules/iam/api/platformIam'
2026-08-03 18:49:18 +08:00
import { useAuthStore } from '@/core/auth/auth'
2026-07-30 17:59:43 +08:00
import RolesView from './RolesView.vue'
2026-08-12 14:08:33 +08:00
const rolesSource = readFileSync(resolve(process.cwd(), 'src/modules/iam/pages/RolesView.vue'), 'utf8')
2026-08-03 18:49:18 +08:00
vi.mock('@/modules/iam/api/platformIam', () => ({
2026-07-30 17:59:43 +08:00
createPlatformRole: vi.fn(),
2026-07-31 13:43:16 +08:00
listPlatformPermissions: vi.fn(),
2026-07-30 17:59:43 +08:00
listPlatformRoles: vi.fn(),
2026-07-31 13:43:16 +08:00
updatePlatformRole: vi.fn(),
changePlatformRoleStatus: vi.fn(),
deletePlatformRole: vi.fn(),
replacePlatformRolePermissions: vi.fn(),
2026-07-30 17:59:43 +08:00
}))
const platformRole: PlatformRoleResource = {
id: 4000002,
tenantId: 0,
appCode: 'PLATFORM',
roleCode: 'CUSTOMER_REVIEWER',
roleName: '企业审核员',
roleType: 'SYSTEM',
status: 'ACTIVE',
description: '企业资料与入驻审核',
2026-08-17 19:52:04 +08:00
memberCount: 3,
2026-07-30 17:59:43 +08:00
}
function mountView(pinia = createPinia()) {
setActivePinia(pinia)
2026-07-31 15:34:35 +08:00
const auth = useAuthStore()
if (auth.permissions.length === 0) auth.permissions = ['platform:role:list', 'platform:role:create', 'platform:role:grant']
2026-07-30 17:59:43 +08:00
return shallowMount(RolesView, {
global: {
plugins: [pinia],
directives: { loading: () => undefined },
stubs: {
2026-08-10 15:36:53 +08:00
AdminPage: { template: '<main><slot /></main>' },
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
FormSection: { template: '<section><slot /></section>' },
2026-08-12 16:58:52 +08:00
AppDialog: { template: '<section><slot /><slot name="footer" /></section>' },
2026-08-10 15:36:53 +08:00
LoadErrorState: { props: ['message'], template: '<div role="alert">{{ message }}<slot name="details" /></div>' },
2026-08-10 16:24:54 +08:00
DataTable: { props: ['data'], template: '<section><slot name="toolbar-actions" /><span data-testid="row-count">{{ data.length }}</span></section>' },
2026-07-30 17:59:43 +08:00
'el-alert': { props: ['title'], template: '<div role="alert">{{ title }}<slot name="title" /></div>' },
'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: '<section><slot /><slot name="footer" /></section>',
},
'el-form': { template: '<form><slot /></form>' },
'el-form-item': { template: '<label><slot /></label>' },
'el-tree': {
props: { checkStrictly: Boolean },
template: '<div data-testid="permission-tree" :data-check-strictly="String(checkStrictly)" />',
},
},
},
})
}
describe('RolesView', () => {
2026-09-03 20:30:58 +08:00
it('keeps the shared status badge prop defined during table slot initialization', () => {
expect(rolesSource).toContain("(row as Role).status || '—'")
})
2026-08-12 18:50:03 +08:00
it('uses bordered striped tables and semantic role operation buttons', () => {
expect(rolesSource).toContain('border stripe')
2026-09-15 09:58:15 +08:00
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('>配置</el-button>')
expect(rolesSource).toContain(':data-tone=')
expect(rolesSource).toContain('>删除</el-button>')
2026-08-12 18:50:03 +08:00
expect(rolesSource).toContain('@media (max-width: 720px)')
})
2026-07-30 17:59:43 +08:00
beforeEach(() => {
setActivePinia(createPinia())
vi.mocked(listPlatformRoles).mockReset()
2026-07-31 13:43:16 +08:00
vi.mocked(listPlatformPermissions).mockReset()
vi.mocked(listPlatformPermissions).mockResolvedValue([])
2026-07-30 17:59:43 +08:00
vi.mocked(createPlatformRole).mockReset()
})
2026-09-11 11:15:04 +08:00
it('links menu groups and permissions so partial selections use the half-checked state', () => {
2026-07-30 17:59:43 +08:00
const wrapper = mountView()
2026-09-11 11:15:04 +08:00
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'")
2026-07-30 17:59:43 +08:00
})
2026-09-11 11:19:45 +08:00
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('nextTick(() =>')
})
2026-08-17 19:52:04 +08:00
it('shows the role code in the default list layout', () => {
expect(rolesSource).toContain("{ key: 'roleCode', prop: 'code', label: '角色编码', minWidth: 180, showOverflowTooltip: true }")
2026-08-12 14:08:33 +08:00
expect(rolesSource).toContain('<el-form-item label="角色编码"')
expect(rolesSource).not.toContain('<small>{{ (row as Role).code }}</small>')
expect(rolesSource).toContain('aria-label="平台角色列表"')
})
2026-08-12 16:58:52 +08:00
it('uses the prototype-sized centered role editor with responsive split panes', () => {
expect(rolesSource).toContain('<AppDialog v-model="editorOpen"')
expect(rolesSource).toContain('width="min(860px, calc(100vw - 28px))"')
expect(rolesSource).not.toContain('<AppDrawer v-model="editorOpen"')
expect(rolesSource).toContain('grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.3fr)')
expect(rolesSource).toContain('.role-editor-layout { grid-template-columns: minmax(0, 1fr); }')
expect(rolesSource).toContain(':global(.role-editor-dialog .el-dialog__body) { min-height: 0; overflow-y: auto; }')
})
2026-07-30 17:59:43 +08:00
it('loads platform roles from IAM for a server session', async () => {
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)
2026-08-17 19:52:04 +08:00
expect(rolesSource).toContain('userCount: role.memberCount')
expect(rolesSource).not.toContain('listPlatformInternalUsers')
2026-08-10 16:24:54 +08:00
expect(wrapper.get('[data-testid="row-count"]').text()).toBe('1')
expect(wrapper.text()).not.toContain('角色负责聚合菜单、操作和数据范围,内部用户可分配多个角色')
2026-07-30 17:59:43 +08:00
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()
2026-08-12 12:35:32 +08:00
const alertText = wrapper.findAll('[role="alert"]').map((item) => item.text()).join(' ')
expect(alertText).toContain('平台角色列表加载失败,请稍后重试')
expect(alertText).not.toContain('IAM unavailable')
2026-07-30 17:59:43 +08:00
})
})