/** * MenuTreePanel:行尾启用勾选。 * * 与模块树面板同构(勾选框不走 G3Tree 内置的 checkable,而是画在 #title 插槽行尾), * 因此同样要锁住三件事: * 1. 勾选上报 toggleEnabled,由父级落库; * 2. 点击不冒泡(行上挂了 click 选中 / mousedown 武装拖拽); * 3. 未保存草稿不可勾(库里没有这行),且面板必须开 block-node 才能贴到行尾。 */ import { describe, expect, it } from 'vitest' import { mount } from '@vue/test-utils' import type { VueWrapper } from '@vue/test-utils' import { G3Checkbox, G3Tree } from '@g3soft/ui' import MenuTreePanel from '@/views/module/menu-management/MenuTreePanel.vue' import type { MenuNode } from '@/views/module/menu-management/utils' const TREE = [ { b_id: 'group_base', b_name: '基础资料', b_menu_type: 'directory', b_canuse: 1 }, { b_id: 'base_contact', b_name: '联系人', b_menu_type: 'page', b_canuse: 0 }, { b_id: '-1', b_name: '未保存草稿', b_menu_type: 'page', b_canuse: 1 }, ] as unknown as MenuNode[] function mountPanel(unsavedIds = new Set()) { return mount(MenuTreePanel, { props: { treeData: TREE, unsavedIds }, }) } const rowOf = (wrapper: VueWrapper, label: string) => wrapper.findAll('[role="treeitem"]').find((row) => row.text().includes(label)) const checkboxOf = (wrapper: VueWrapper, label: string) => rowOf(wrapper, label)?.findComponent(G3Checkbox) describe('MenuTreePanel 行尾启用勾选', () => { it('每个节点行都带勾选框,勾选态反映 b_canuse', () => { const wrapper = mountPanel() expect(checkboxOf(wrapper, '基础资料')?.props('modelValue')).toBe(true) expect(checkboxOf(wrapper, '联系人')?.props('modelValue')).toBe(false) }) it('勾选上报 toggleEnabled(id + 目标启用态)', async () => { const wrapper = mountPanel() checkboxOf(wrapper, '联系人')?.vm.$emit('update:modelValue', true) await wrapper.vm.$nextTick() expect(wrapper.emitted('toggleEnabled')?.[0]?.[0]).toEqual({ id: 'base_contact', enabled: true, }) }) it('取消勾选同样上报(enabled: false)', async () => { const wrapper = mountPanel() checkboxOf(wrapper, '基础资料')?.vm.$emit('update:modelValue', false) await wrapper.vm.$nextTick() expect(wrapper.emitted('toggleEnabled')?.[0]?.[0]).toEqual({ id: 'group_base', enabled: false, }) }) it('点击勾选框不触发行的选中(事件不冒泡)', async () => { const wrapper = mountPanel() const row = rowOf(wrapper, '基础资料')! const box = checkboxOf(wrapper, '基础资料')!.find('input') await box.trigger('click') expect(wrapper.emitted('select')).toBeUndefined() // 行自身仍可正常选中,说明拦的是勾选框那一下、不是把整行点击弄坏了 await row.trigger('click') expect(wrapper.emitted('select')?.[0]?.[0]).toBe('group_base') }) it('未保存草稿的勾选框禁用且不上报', async () => { const wrapper = mountPanel(new Set(['-1'])) const box = checkboxOf(wrapper, '未保存草稿')! expect(box.props('disabled')).toBe(true) box.vm.$emit('update:modelValue', false) await wrapper.vm.$nextTick() expect(wrapper.emitted('toggleEnabled')).toBeUndefined() }) it('勾选框渲染在名称之后的行尾', () => { const wrapper = mountPanel() const row = rowOf(wrapper, '基础资料')! const nameEl = row.find('.mm-tree-node__name') const enableEl = row.find('.mm-tree-node__enable') expect(nameEl.exists()).toBe(true) expect(enableEl.exists()).toBe(true) const relation = nameEl.element.compareDocumentPosition(enableEl.element) expect(relation & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() }) it('面板必须开 block-node:否则内容层不撑满,勾选框贴不到行尾', () => { const wrapper = mountPanel() // 断言落在 G3Tree 上:BizTreePanel / BizTree 是 inheritAttrs:false, // kebab 的 'block-node' 会留在中间层的 $attrs 里(那些层取不到), // 只有到 G3Tree 才被归一成 blockNode prop。 expect(wrapper.findComponent(G3Tree).props('blockNode')).toBe(true) }) it('面板不启用 G3Tree 内置 checkable(避免行首出现多选勾选框)', () => { const wrapper = mountPanel() expect(wrapper.find('.g3-tree-node__checkbox').exists()).toBe(false) }) })