u
This commit is contained in:
1 parent
e7c84a82a2
commit
025d3232c4
65 files changed
+9838
-9111
No files matched your search
@@ -4,7 +4,7 @@
|
||||
* 这两块是保存编排里最容易出错、也最难靠肉眼验证的部分:
|
||||
* - 权限点行完全由菜单行派生(menu.{编码}),名称 / 顺序 / 启用态跟随菜单,
|
||||
* 目录不产生权限点;
|
||||
* - 删除菜单要连带清掉多语言资源、模块绑定与入口权限点,
|
||||
* - 删除菜单要连带清掉多语言资源与入口权限点,
|
||||
* 且权限点删除必须带 dependent_deletes(授权行随权限点删除)。
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import ModuleBindPanel from '@/views/module/menu-management/ModuleBindPanel.vue'
|
||||
import {
|
||||
buildMenuModuleRows,
|
||||
filterBindableModules,
|
||||
isBindableModule,
|
||||
moduleBindSignature,
|
||||
normalizeMenuModulesByType,
|
||||
} from '@/views/module/menu-management/utils'
|
||||
|
||||
/**
|
||||
* 菜单「绑定模块」(s_menu_module)的纯逻辑与面板行为。
|
||||
*
|
||||
* 关键口径:只有页面(page)菜单绑定模块;落库只登记 data / virtual ——
|
||||
* module 类型只是模块树上的分类节点,不参与绑定。
|
||||
*/
|
||||
|
||||
const MODULE_TREE = [
|
||||
{
|
||||
b_id: 'sales',
|
||||
b_name: '销售',
|
||||
children: [
|
||||
{ b_id: 'v_order', b_name: '订单' },
|
||||
{ b_id: 'v_customer', b_name: '客户' },
|
||||
],
|
||||
},
|
||||
{ b_id: 'v_other', b_name: '其他' },
|
||||
]
|
||||
|
||||
/** 编码 → 类型:sales 是分类,其余是可绑定的数据模块 */
|
||||
const TYPES: Record<string, string> = {
|
||||
sales: 'module',
|
||||
v_order: 'data',
|
||||
v_customer: 'virtual',
|
||||
v_other: 'data',
|
||||
}
|
||||
|
||||
/** 挂面板;menuType 决定渲染树还是给一句说明,model 是已绑模块编码 */
|
||||
function mountPanel(props: Record<string, unknown> = {}, model: string[] = []) {
|
||||
return mount(ModuleBindPanel, {
|
||||
props: {
|
||||
treeData: MODULE_TREE,
|
||||
moduleTypes: TYPES,
|
||||
menuType: 'page',
|
||||
menuId: 'm1',
|
||||
modelValue: model,
|
||||
'onUpdate:modelValue': () => {},
|
||||
...props,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('isBindableModule', () => {
|
||||
it('data / virtual 可绑定,module 不可', () => {
|
||||
expect(isBindableModule('data')).toBe(true)
|
||||
expect(isBindableModule('virtual')).toBe(true)
|
||||
expect(isBindableModule('module')).toBe(false)
|
||||
})
|
||||
|
||||
it('类型未知时按可绑定处理(避免误删历史绑定)', () => {
|
||||
// 模块清单没加载全时,宁可保留关系行也不当成脏数据删掉
|
||||
expect(isBindableModule(undefined)).toBe(true)
|
||||
expect(isBindableModule(null)).toBe(true)
|
||||
expect(isBindableModule('')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('filterBindableModules', () => {
|
||||
it('剔除分类节点,保留可绑定模块与原顺序', () => {
|
||||
expect(filterBindableModules(['v_order', 'sales', 'v_customer'], TYPES)).toEqual([
|
||||
'v_order',
|
||||
'v_customer',
|
||||
])
|
||||
})
|
||||
|
||||
it('勾父级带回的子树键里,只有可绑定的留下', () => {
|
||||
// 非严格模式点「销售」会把 sales / v_order / v_customer 一起回传
|
||||
expect(filterBindableModules(['sales', 'v_order', 'v_customer'], TYPES)).toEqual([
|
||||
'v_order',
|
||||
'v_customer',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('moduleBindSignature', () => {
|
||||
it('顺序无关:同集合不同顺序签名相同', () => {
|
||||
expect(moduleBindSignature(['a', 'b'])).toBe(moduleBindSignature(['b', 'a']))
|
||||
})
|
||||
|
||||
it('集合不同则签名不同', () => {
|
||||
expect(moduleBindSignature(['a'])).not.toBe(moduleBindSignature(['a', 'b']))
|
||||
})
|
||||
|
||||
it('数字与字符串编码视为同一个', () => {
|
||||
expect(moduleBindSignature([1, 2])).toBe(moduleBindSignature(['1', '2']))
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildMenuModuleRows', () => {
|
||||
it('按勾选顺序生成关系行,b_xh 从 10 起步', () => {
|
||||
expect(buildMenuModuleRows('m1', ['v_order', 'v_customer'])).toEqual([
|
||||
{ b_menu_id: 'm1', b_module_id: 'v_order', b_xh: 10, b_canuse: 1 },
|
||||
{ b_menu_id: 'm1', b_module_id: 'v_customer', b_xh: 20, b_canuse: 1 },
|
||||
])
|
||||
})
|
||||
|
||||
it('菜单编码为空时返回空数组(不产出悬空关系行)', () => {
|
||||
expect(buildMenuModuleRows('', ['v_order'])).toEqual([])
|
||||
})
|
||||
|
||||
it('空绑定返回空数组', () => {
|
||||
expect(buildMenuModuleRows('m1', [])).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeMenuModulesByType', () => {
|
||||
it('页面菜单保留绑定', () => {
|
||||
expect(normalizeMenuModulesByType('page', ['v_order'])).toEqual(['v_order'])
|
||||
})
|
||||
|
||||
it('目录 / 外链一律清空(非页面不绑定模块)', () => {
|
||||
expect(normalizeMenuModulesByType('directory', ['v_order'])).toEqual([])
|
||||
expect(normalizeMenuModulesByType('external', ['v_order'])).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('ModuleBindPanel', () => {
|
||||
it('页面菜单渲染可勾选的模块树', () => {
|
||||
const wrapper = mountPanel()
|
||||
expect(wrapper.find('.biz-tree').exists()).toBe(true)
|
||||
// 树默认收起(只渲染顶层节点),所以这里只断言顶层两个节点各有复选框
|
||||
const boxes = wrapper.findAllComponents({ name: 'G3Checkbox' })
|
||||
expect(boxes.length).toBe(2)
|
||||
expect(wrapper.text()).toContain('销售')
|
||||
expect(wrapper.text()).toContain('其他')
|
||||
})
|
||||
|
||||
it('目录 / 外链不渲染树,各给一句说明', () => {
|
||||
const dir = mountPanel({ menuType: 'directory' })
|
||||
expect(dir.find('.biz-tree').exists()).toBe(false)
|
||||
expect(dir.text()).toContain('目录不绑定模块')
|
||||
|
||||
const ext = mountPanel({ menuType: 'external' })
|
||||
expect(ext.find('.biz-tree').exists()).toBe(false)
|
||||
expect(ext.text()).toContain('外链不绑定模块')
|
||||
})
|
||||
|
||||
it('初始已绑模块反映在顶层复选框上', async () => {
|
||||
// 「其他」是顶层节点:默认收起的树里只有顶层可见,用它验证已绑态回填
|
||||
const wrapper = mountPanel({}, ['v_other'])
|
||||
await nextTick()
|
||||
|
||||
const boxes = wrapper.findAllComponents({ name: 'G3Checkbox' })
|
||||
// 顶层顺序与 treeData 一致:sales、v_other
|
||||
expect(boxes[0].props('modelValue')).toBe(false)
|
||||
expect(boxes[1].props('modelValue')).toBe(true)
|
||||
})
|
||||
|
||||
it('切换菜单清掉勾选草稿', async () => {
|
||||
const wrapper = mountPanel({}, ['v_other'])
|
||||
await nextTick()
|
||||
expect(wrapper.findAllComponents({ name: 'G3Checkbox' })[1].props('modelValue')).toBe(true)
|
||||
|
||||
// 切到另一个菜单:父级尚未回填新绑定时,草稿必须清空而不是留着上一个菜单的勾
|
||||
await wrapper.setProps({ menuId: 'm2', modelValue: [] })
|
||||
await nextTick()
|
||||
expect(wrapper.findAllComponents({ name: 'G3Checkbox' })[1].props('modelValue')).toBe(false)
|
||||
})
|
||||
|
||||
it('勾选后回写模型时剔除分类节点', async () => {
|
||||
const updates: string[][] = []
|
||||
const wrapper = mountPanel({
|
||||
'onUpdate:modelValue': (v: string[]) => updates.push(v),
|
||||
})
|
||||
await nextTick()
|
||||
|
||||
// 点「销售」分类 → 级联勾选整棵子树(含分类自身)
|
||||
const boxes = wrapper.findAllComponents({ name: 'G3Checkbox' })
|
||||
await boxes[0].vm.$emit('change', true)
|
||||
await nextTick()
|
||||
|
||||
const last = updates.at(-1) ?? []
|
||||
expect(last).not.toContain('sales')
|
||||
expect(last.toSorted()).toEqual(['v_customer', 'v_order'])
|
||||
})
|
||||
|
||||
it('数量标记只算可绑定的模块', async () => {
|
||||
const wrapper = mountPanel({}, ['v_order', 'v_customer'])
|
||||
await nextTick()
|
||||
expect(wrapper.find('.g3-tag').text()).toBe('2')
|
||||
})
|
||||
|
||||
it('未绑定时不显示数量标记', () => {
|
||||
const wrapper = mountPanel({}, [])
|
||||
expect(wrapper.find('.g3-tag').exists()).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user