572 lines
21 KiB
JavaScript
572 lines
21 KiB
JavaScript
/**
|
||
* 绑定模块面板(ModuleBindPanel):菜单配置右侧的常驻面板。
|
||
*
|
||
* 回归点:
|
||
* 1) 树默认全部收起(不设 default-expanded-keys),子节点由用户按需逐层点开;
|
||
* 折叠态下子树不渲染(tree-node 用 v-if 而非 v-show),因此可直接数 treeitem。
|
||
* 2) 勾分类节点级联勾选其下全部模块,但回写给父级的只有 data / virtual 编码 ——
|
||
* module 类型只是模块树上的分类,不参与权限,写进 s_menu_module 会留下脏关系行。
|
||
* 3) 目录 / 外链菜单不绑定模块(不渲染可勾的树,只留一句说明)。
|
||
* 4) 外部值变化(切换菜单 / 重载)时,树上的勾选跟随重置。
|
||
* 5) 权限点:点模块名按 b_object_id 查 s_power 并用表格列出;分类节点与 virtual
|
||
* 模块不挂权限点,不发查询、只给说明;切换菜单时连草稿一起清空。
|
||
* 6) 就地增删(同「模块管理 → 权限」):新增先在草稿里加一行空动作行再进单元格编辑,
|
||
* 填完动作按共用口径(@/utils/powerCodes)派生编码 / 性质 / 资源键 / 默认名,
|
||
* 编码撞车时清掉编码并提示;启用与删除只改草稿(回写给父级),落库由菜单的保存统一提交。
|
||
*/
|
||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||
import { flushPromises, mount } from '@vue/test-utils'
|
||
import { nextTick } from 'vue'
|
||
import { registerFeature, useAreaSelection } from 'stk-table-vue'
|
||
import ModuleBindPanel from '@/views/module/menu-management/ModuleBindPanel.vue'
|
||
import { clearMessages } from '@/stores/i18n'
|
||
import { createTestPinia } from '../helpers/app.js'
|
||
|
||
// 与 main.js 一致:表格可编辑(单元格点击进编辑)依赖 stk-table 的 area-selection feature
|
||
registerFeature(useAreaSelection)
|
||
|
||
// 权限点 / 权限库都直接取数(面板自带的查询),mock 这一个接缝即可
|
||
vi.mock('@/services/api', () => ({ loadDataApi: vi.fn(), loadDataBySqlApi: vi.fn() }))
|
||
|
||
// 命令式 Message 在 jsdom 下会建 DOM 容器,按测试约定打桩
|
||
vi.mock('@/components/ui/message/message-manager.js', () => {
|
||
const m = { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn() }
|
||
return { Message: m, default: m }
|
||
})
|
||
|
||
import { buildPowerLibrarySql } from '@/utils/powerCodes'
|
||
import { loadDataApi, loadDataBySqlApi } from '@/services/api'
|
||
import { Message } from '@/components/ui/message/message-manager.js'
|
||
|
||
// 权限点表是 FmsTable(stk-table):jsdom 无布局,容器 clientWidth 为 0 时库会
|
||
// 启用虚拟列裁剪只渲染首列——patch 成宽容器,让全部列真实渲染(同 module-fields-panel 用例)
|
||
beforeAll(() => {
|
||
Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||
configurable: true,
|
||
get() {
|
||
return 5000
|
||
},
|
||
})
|
||
})
|
||
|
||
const TREE = [
|
||
{
|
||
b_id: 'base',
|
||
b_module_type: 'module',
|
||
b_name: '基础资料',
|
||
children: [
|
||
{
|
||
b_id: 'base_otherdata',
|
||
b_module_type: 'module',
|
||
b_name: '其他数据',
|
||
children: [{ b_id: 'b_port', b_module_type: 'data', b_name: '港口' }],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
b_id: 'system',
|
||
b_module_type: 'module',
|
||
b_name: '系统',
|
||
children: [{ b_id: 'b_user', b_module_type: 'data', b_name: '用户' }],
|
||
},
|
||
]
|
||
|
||
const MODULE_TYPES = {
|
||
base: 'module',
|
||
base_otherdata: 'module',
|
||
b_port: 'data',
|
||
system: 'module',
|
||
b_user: 'data',
|
||
}
|
||
|
||
/** 库中 b_port 的权限点:模块使用(启用)+ 审核(停用) */
|
||
const POWERS = [
|
||
{
|
||
b_id: 'module.b_port',
|
||
b_name: '模块使用',
|
||
b_i18n: 'module.b_port',
|
||
b_action: 'access',
|
||
b_type: 'module',
|
||
b_object_type: 'module',
|
||
b_object_id: 'b_port',
|
||
b_canuse: 1,
|
||
b_xh: 10,
|
||
},
|
||
{
|
||
b_id: 'action.b_port.audit',
|
||
b_name: '审核',
|
||
b_i18n: 'action.audit',
|
||
b_action: 'audit',
|
||
b_type: 'action',
|
||
b_object_type: 'module',
|
||
b_object_id: 'b_port',
|
||
b_canuse: 0,
|
||
b_xh: 20,
|
||
},
|
||
]
|
||
|
||
/** 权限库里的动作(跨模块去重后只留动作与一份名称) */
|
||
const LIBRARY = [
|
||
{ b_action: 'audit', b_name: '审核' },
|
||
{ b_action: 'print', b_name: '打印' },
|
||
]
|
||
|
||
function mountPanel(props = {}) {
|
||
// attachTo:FmsTable(stk-table)要真实 DOM 尺寸,脱离文档流时量不到宽高
|
||
const wrapper = mount(ModuleBindPanel, {
|
||
props: {
|
||
treeData: TREE,
|
||
moduleTypes: MODULE_TYPES,
|
||
menuType: 'page',
|
||
modelValue: [],
|
||
// 权限点草稿走 v-model:power-drafts:回写值要真的回到 prop,面板才继续拿得到草稿
|
||
powerDrafts: {},
|
||
'onUpdate:powerDrafts': (value) => wrapper.setProps({ powerDrafts: value }),
|
||
...props,
|
||
},
|
||
attachTo: document.body,
|
||
})
|
||
return wrapper
|
||
}
|
||
|
||
/** 按文案取按钮(工具栏按钮没有更稳定的属性可用) */
|
||
function buttonByText(wrapper, text) {
|
||
return wrapper.findAll('button').find((btn) => btn.text().includes(text))
|
||
}
|
||
|
||
/**
|
||
* 左权限点表的启用勾选框(树上的勾选框是 ui/Checkbox,类名不同)。
|
||
* 按区块取:右栏权限库的选择列(含表头那枚)也是 .fms-checkbox。
|
||
*/
|
||
function powerCheckboxes(wrapper) {
|
||
return wrapper.findAll('.mm-bind-panel__powers .fms-checkbox input')
|
||
}
|
||
|
||
/** 取最后一次回写给父级的权限点草稿 */
|
||
function lastDrafts(wrapper) {
|
||
const events = wrapper.emitted('update:powerDrafts')
|
||
return events[events.length - 1][0]
|
||
}
|
||
|
||
/** 驱动 ui/Checkbox 内部的原生 input:勾上 / 取消 */
|
||
function setCheck(wrapper, index, checked = true) {
|
||
return wrapper.findAll('input.checkbox-input')[index].setValue(checked)
|
||
}
|
||
|
||
/** 可勾选行的勾选态(顺序与 [role="treeitem"] 一致) */
|
||
function checkedStates(wrapper) {
|
||
return wrapper.findAll('input.checkbox-input').map((box) => box.element.checked)
|
||
}
|
||
|
||
/** 回写给父级的模块编码(取最后一次 emit) */
|
||
function lastEmitted(wrapper) {
|
||
const events = wrapper.emitted('update:modelValue')
|
||
return events[events.length - 1][0]
|
||
}
|
||
|
||
/** 权限库区块的行文本(左权限点表也有表格,按区块取) */
|
||
function libraryRowTexts(wrapper) {
|
||
return wrapper
|
||
.findAll('.mm-bind-panel__powers-lib .stk-tbody-main tr')
|
||
.map((row) => row.text())
|
||
}
|
||
|
||
/** 权限库区块的行复选框(选择列的表头也有一枚,取 tbody 里的才是行) */
|
||
function libraryCheckboxes(wrapper) {
|
||
return wrapper.findAll('.mm-bind-panel__powers-lib .stk-tbody-main .fms-checkbox input')
|
||
}
|
||
|
||
/** 按节点名取树行([role="treeitem"]) */
|
||
function nodeRow(wrapper, name) {
|
||
return wrapper.findAll('[role="treeitem"]').find((row) => row.text().includes(name))
|
||
}
|
||
|
||
/** 展开非叶子节点(箭头是行内唯一的 role="button") */
|
||
async function expandNode(wrapper, name) {
|
||
await nodeRow(wrapper, name).find('[role="button"]').trigger('click')
|
||
await nextTick()
|
||
}
|
||
|
||
/** 点节点标题:选中即列出该模块的权限点 */
|
||
async function clickNode(wrapper, name) {
|
||
await nodeRow(wrapper, name).find('.tree-node-content').trigger('click')
|
||
await nextTick()
|
||
}
|
||
|
||
beforeEach(() => {
|
||
localStorage.clear()
|
||
sessionStorage.clear()
|
||
clearMessages()
|
||
// FmsTable 内取 appStore(深浅色主题);用 createTestPinia 保证 persist 插件真实注册
|
||
createTestPinia()
|
||
loadDataApi.mockReset()
|
||
loadDataApi.mockResolvedValue({ data: [] })
|
||
loadDataBySqlApi.mockReset()
|
||
loadDataBySqlApi.mockResolvedValue({ data: [] })
|
||
Message.success.mockClear()
|
||
Message.info.mockClear()
|
||
Message.warning.mockClear()
|
||
})
|
||
|
||
describe('绑定模块面板:树默认收起', () => {
|
||
it('初始只渲染根节点,没有任何节点处于展开态', () => {
|
||
const wrapper = mountPanel()
|
||
|
||
const items = wrapper.findAll('[role="treeitem"]')
|
||
expect(items).toHaveLength(TREE.length)
|
||
expect(items.map((item) => item.attributes('aria-expanded'))).toEqual(['false', 'false'])
|
||
})
|
||
|
||
it('折叠态下子节点不渲染', () => {
|
||
const wrapper = mountPanel()
|
||
|
||
expect(wrapper.text()).toContain('基础资料')
|
||
expect(wrapper.text()).not.toContain('其他数据')
|
||
expect(wrapper.text()).not.toContain('港口')
|
||
})
|
||
})
|
||
|
||
describe('绑定模块面板:勾选与回写', () => {
|
||
it('勾分类节点级联整棵子树,回写只保留 data 模块编码', async () => {
|
||
const wrapper = mountPanel()
|
||
|
||
await setCheck(wrapper, 0)
|
||
await nextTick()
|
||
|
||
// 分类节点(base / base_otherdata)不落库,只回写 data 模块
|
||
expect(lastEmitted(wrapper)).toEqual(['b_port'])
|
||
})
|
||
|
||
it('子模块全勾时分类节点呈全选态,点它取消整棵子树并回写空数组', async () => {
|
||
const wrapper = mountPanel({ modelValue: ['b_port'] })
|
||
|
||
await setCheck(wrapper, 0, false)
|
||
await nextTick()
|
||
expect(lastEmitted(wrapper)).toEqual([])
|
||
|
||
// 再点回来:级联勾选整棵子树,回写仍只有 data 模块
|
||
await setCheck(wrapper, 0)
|
||
await nextTick()
|
||
expect(lastEmitted(wrapper)).toEqual(['b_port'])
|
||
})
|
||
|
||
it('外部值变化(切换菜单)时树上的勾选跟随重置', async () => {
|
||
const wrapper = mountPanel()
|
||
// 展开「系统」露出子节点「用户」:此时只有未勾选的根节点
|
||
await expandNode(wrapper, '系统')
|
||
expect(checkedStates(wrapper)).toEqual([false, false, false])
|
||
|
||
await wrapper.setProps({ modelValue: ['b_user'] })
|
||
await nextTick()
|
||
expect(checkedStates(wrapper)).toEqual([false, true, true])
|
||
})
|
||
})
|
||
|
||
/** 展开到「港口」这一层(b_port 是唯一的 data 叶子) */
|
||
async function openPort(wrapper) {
|
||
await expandNode(wrapper, '基础资料')
|
||
await expandNode(wrapper, '其他数据')
|
||
await clickNode(wrapper, '港口')
|
||
await flushPromises()
|
||
}
|
||
|
||
/** 点「新增权限」:草稿里多一行空动作行 */
|
||
async function addPowerRow(wrapper) {
|
||
await buttonByText(wrapper, '新增权限').trigger('click')
|
||
await nextTick()
|
||
}
|
||
|
||
/**
|
||
* 驱动单元格变更:jsdom 不实现布局,单元格编辑器挂载与命中不可靠,
|
||
* 直接触发组件真实监听的 change(row, col, value)——同 module-fields-panel 用例的做法。
|
||
*/
|
||
function changeCell(wrapper, row, dataIndex, value) {
|
||
wrapper.findComponent({ name: 'FmsTable' }).vm.$emit('change', row, { dataIndex }, value)
|
||
}
|
||
|
||
describe('绑定模块面板:权限点(点模块名查看)', () => {
|
||
it('按模块编码查权限点,用表格列出(启停态反映到勾选框)', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
expect(loadDataApi).toHaveBeenCalledWith(
|
||
's_power',
|
||
"b_object_type = 'module' AND b_object_id = N'b_port'",
|
||
'b_xh ASC, b_id ASC',
|
||
)
|
||
expect(wrapper.findAll('.stk-tbody-main tr')).toHaveLength(2)
|
||
expect(wrapper.text()).toContain('权限名称')
|
||
expect(wrapper.text()).toContain('模块使用')
|
||
expect(wrapper.text()).toContain('action.b_port.audit')
|
||
expect(powerCheckboxes(wrapper).map((el) => el.element.checked)).toEqual([true, false])
|
||
})
|
||
|
||
it('点分类节点:不发查询,只提示分类节点没有权限点', async () => {
|
||
const wrapper = mountPanel()
|
||
await clickNode(wrapper, '基础资料')
|
||
|
||
expect(loadDataApi).not.toHaveBeenCalled()
|
||
expect(wrapper.text()).toContain('分类节点没有权限点')
|
||
})
|
||
|
||
it('同一模块来回点只查一次(草稿即缓存)', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
await clickNode(wrapper, '基础资料')
|
||
await clickNode(wrapper, '港口')
|
||
await flushPromises()
|
||
|
||
expect(loadDataApi).toHaveBeenCalledTimes(1)
|
||
expect(wrapper.text()).toContain('module.b_port')
|
||
})
|
||
|
||
it('切换菜单:清掉选中的模块与权限点草稿', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel({ menuId: 'otherdata' })
|
||
await openPort(wrapper)
|
||
expect(wrapper.text()).toContain('module.b_port')
|
||
|
||
await wrapper.setProps({ menuId: 'base' })
|
||
await nextTick()
|
||
|
||
expect(wrapper.text()).not.toContain('module.b_port')
|
||
expect(wrapper.text()).toContain('点击左侧模块')
|
||
expect(lastDrafts(wrapper)).toEqual({})
|
||
})
|
||
})
|
||
|
||
describe('绑定模块面板:权限点就地增删', () => {
|
||
it('模块没有权限点时也能新增:先加一行空动作行,等填动作', async () => {
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
expect(wrapper.findAll('.fms-action-btn')).toHaveLength(0)
|
||
|
||
await addPowerRow(wrapper)
|
||
|
||
const rows = lastDrafts(wrapper).b_port.rows
|
||
expect(rows).toHaveLength(1)
|
||
expect(rows[0]).toMatchObject({
|
||
b_id: '',
|
||
b_action: '',
|
||
b_type: 'action',
|
||
b_object_type: 'module',
|
||
b_object_id: 'b_port',
|
||
b_canuse: 1,
|
||
})
|
||
expect(rows[0]._rowKey).toBeTruthy()
|
||
expect(wrapper.findAll('.stk-tbody-main tr')).toHaveLength(1)
|
||
// 未保存标记:改动只在草稿里,由菜单的「保存」统一落库
|
||
expect(wrapper.text()).toContain('未保存')
|
||
})
|
||
|
||
it('填动作:按共用口径派生编码 / 性质 / 资源键 / 默认名', async () => {
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
await addPowerRow(wrapper)
|
||
|
||
changeCell(wrapper, lastDrafts(wrapper).b_port.rows[0], 'b_action', 'audit')
|
||
await nextTick()
|
||
|
||
expect(lastDrafts(wrapper).b_port.rows[0]).toMatchObject({
|
||
b_id: 'action.b_port.audit',
|
||
b_name: '审核',
|
||
b_i18n: 'action.audit',
|
||
b_action: 'audit',
|
||
b_type: 'action',
|
||
b_object_id: 'b_port',
|
||
})
|
||
expect(wrapper.text()).toContain('action.b_port.audit')
|
||
})
|
||
|
||
it('动作编码撞车:清掉该行编码(不落库)并提示', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
await addPowerRow(wrapper)
|
||
|
||
// 新增行(最后一行)填一个库里已存在的动作 → 编码撞车
|
||
changeCell(wrapper, lastDrafts(wrapper).b_port.rows.at(-1), 'b_action', 'audit')
|
||
await nextTick()
|
||
|
||
expect(Message.warning).toHaveBeenCalledWith('该动作已存在,重复行不会保存')
|
||
expect(lastDrafts(wrapper).b_port.rows.at(-1)).toMatchObject({ b_action: 'audit', b_id: '' })
|
||
})
|
||
|
||
it('启用勾选框:按 _rowKey 把勾选结果写回草稿(不就地改显示副本)', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
await powerCheckboxes(wrapper)[1].setValue(true)
|
||
await nextTick()
|
||
|
||
const rows = lastDrafts(wrapper).b_port.rows
|
||
expect(rows.find((row) => row.b_id === 'action.b_port.audit').b_canuse).toBe(1)
|
||
expect(rows.find((row) => row.b_id === 'module.b_port').b_canuse).toBe(1)
|
||
expect(wrapper.text()).toContain('未保存')
|
||
})
|
||
|
||
it('删除:文字按钮只从草稿里去掉,不回库', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
// 操作列是文字按钮;删掉第二行(审核),第一行「模块使用」保留
|
||
await wrapper.findAll('.fms-action-btn')[1].trigger('click')
|
||
await nextTick()
|
||
|
||
expect(lastDrafts(wrapper).b_port.rows.map((row) => row.b_id)).toEqual(['module.b_port'])
|
||
expect(wrapper.findAll('.stk-tbody-main tr')).toHaveLength(1)
|
||
expect(wrapper.text()).toContain('未保存')
|
||
})
|
||
})
|
||
|
||
describe('绑定模块面板:权限区表头与表宽', () => {
|
||
it('不再渲染「权限点 / 模块名 / 条数」表头行', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
expect(wrapper.find('.mm-bind-panel__powers-head').exists()).toBe(false)
|
||
// 权限区只剩工具栏(新增权限)+ 表格:模块名由左侧树写明了,这里不重复
|
||
expect(wrapper.find('.mm-bind-panel__powers').text()).not.toContain('港口')
|
||
expect(wrapper.find('.mm-bind-panel__powers').text()).toContain('新增权限')
|
||
})
|
||
|
||
it('权限点表按列宽定宽:列宽收紧到 <500,且允许拖拽回写(自适应)', async () => {
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
const totalWidth = wrapper.vm.powerColumns.reduce((sum, col) => sum + (col.width || 0), 0)
|
||
expect(totalWidth).toBeLessThan(500)
|
||
|
||
// colResizable + v-model:columns:表格按列宽定宽(不自铺满整栏),拖过的列宽可回写
|
||
const powerTable = wrapper.findAllComponents({ name: 'FmsTable' })[0]
|
||
expect(powerTable.props('colResizable')).toMatchObject({ disabled: expect.any(Function) })
|
||
})
|
||
})
|
||
|
||
describe('绑定模块面板:权限库(历史动作导入)', () => {
|
||
it('选中 data 模块后按去重口径取库,列出名称与动作', async () => {
|
||
loadDataBySqlApi.mockResolvedValue({ data: LIBRARY })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
expect(loadDataBySqlApi).toHaveBeenCalledWith(buildPowerLibrarySql())
|
||
// 名称固定一档窄宽,动作(最后一列)不指定宽度:由它自适应吃剩余
|
||
const [nameCol, actionCol] = wrapper.vm.libraryColumns
|
||
expect(nameCol.width).toBe(88)
|
||
expect(actionCol.width).toBeUndefined()
|
||
|
||
const rows = libraryRowTexts(wrapper)
|
||
expect(rows).toHaveLength(2)
|
||
expect(rows[0]).toContain('审核')
|
||
expect(rows[0]).toContain('audit')
|
||
expect(rows[1]).toContain('打印')
|
||
expect(rows[1]).toContain('print')
|
||
})
|
||
|
||
it('没选模块 / 选中分类节点时不出权限库,也不发查询', async () => {
|
||
const wrapper = mountPanel()
|
||
|
||
// 未选模块:权限点区只有一句说明,右栏整块不渲染
|
||
expect(wrapper.find('.mm-bind-panel__powers-lib').exists()).toBe(false)
|
||
|
||
await clickNode(wrapper, '基础资料')
|
||
expect(wrapper.find('.mm-bind-panel__powers-lib').exists()).toBe(false)
|
||
expect(loadDataBySqlApi).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('已在本模块的动作置灰,未用过的可勾选', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
loadDataBySqlApi.mockResolvedValue({ data: LIBRARY })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
// audit 已在 b_port 的权限点里(POWERS),print 没有
|
||
expect(libraryCheckboxes(wrapper).map((box) => box.element.disabled)).toEqual([true, false])
|
||
})
|
||
|
||
it('导入选中动作:按共用口径派生行追进草稿,清空选中并提示', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
loadDataBySqlApi.mockResolvedValue({ data: LIBRARY })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
|
||
await libraryCheckboxes(wrapper)[1].setValue(true)
|
||
await nextTick()
|
||
expect(wrapper.vm.librarySelectedKeys).toEqual(['action:print'])
|
||
|
||
wrapper.vm.importSelectedActions()
|
||
await nextTick()
|
||
|
||
const rows = lastDrafts(wrapper).b_port.rows
|
||
expect(rows).toHaveLength(3)
|
||
expect(rows[2]).toMatchObject({
|
||
b_id: 'action.b_port.print',
|
||
b_name: '打印',
|
||
b_i18n: 'action.print',
|
||
b_action: 'print',
|
||
b_type: 'action',
|
||
b_object_type: 'module',
|
||
b_object_id: 'b_port',
|
||
b_canuse: 1,
|
||
// 顺序号接着草稿末尾排(原有 10 / 20)
|
||
b_xh: 30,
|
||
})
|
||
expect(wrapper.vm.librarySelectedKeys).toEqual([])
|
||
expect(Message.success).toHaveBeenCalledWith('已导入 1 个权限点,随菜单保存一起落库')
|
||
})
|
||
|
||
it('选中的动作本模块都已存在:不写回草稿,只提示一句', async () => {
|
||
loadDataApi.mockResolvedValue({ data: POWERS })
|
||
loadDataBySqlApi.mockResolvedValue({ data: [LIBRARY[0]] })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
const drafts = wrapper.emitted('update:powerDrafts') || []
|
||
|
||
// 库里该行是禁用态(勾不上),直接驱动内部状态覆盖兜底分支
|
||
wrapper.vm.librarySelectedKeys = ['action:audit']
|
||
wrapper.vm.importSelectedActions()
|
||
await nextTick()
|
||
|
||
expect(wrapper.emitted('update:powerDrafts') || []).toHaveLength(drafts.length)
|
||
expect(Message.info).toHaveBeenCalledWith('选中的动作本模块都已存在')
|
||
expect(Message.success).not.toHaveBeenCalled()
|
||
})
|
||
|
||
it('换模块:库重取一次,选中清空', async () => {
|
||
loadDataBySqlApi.mockResolvedValue({ data: LIBRARY })
|
||
const wrapper = mountPanel()
|
||
await openPort(wrapper)
|
||
await libraryCheckboxes(wrapper)[1].setValue(true)
|
||
await nextTick()
|
||
expect(wrapper.vm.librarySelectedKeys).toEqual(['action:print'])
|
||
|
||
// 换到另一个 data 模块(系统 / 用户)
|
||
await expandNode(wrapper, '系统')
|
||
await clickNode(wrapper, '用户')
|
||
|
||
expect(loadDataBySqlApi).toHaveBeenCalledTimes(2)
|
||
expect(wrapper.vm.librarySelectedKeys).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('绑定模块面板:非页面菜单', () => {
|
||
it('目录菜单不渲染可勾的树,只给一句说明', () => {
|
||
const wrapper = mountPanel({ menuType: 'directory' })
|
||
|
||
expect(wrapper.findAll('[role="treeitem"]')).toHaveLength(0)
|
||
expect(wrapper.text()).toContain('目录不绑定模块')
|
||
})
|
||
|
||
it('外链菜单不渲染可勾的树,只给一句说明', () => {
|
||
const wrapper = mountPanel({ menuType: 'external' })
|
||
|
||
expect(wrapper.findAll('[role="treeitem"]')).toHaveLength(0)
|
||
expect(wrapper.text()).toContain('外链不绑定模块')
|
||
})
|
||
})
|