Files
workspace/code/fms/fms-vue/tests/components/tree.spec.js
T
2026-09-15 17:00:29 +08:00

118 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import Tree from '@/components/ui/tree/tree.vue'
const treeData = [{ b_id: '1', b_name: '根节点', children: [{ b_id: '2', b_name: '子节点' }] }]
const fieldNames = { key: 'b_id', title: 'b_name', children: 'children' }
describe('Tree 右键菜单', () => {
it('节点右键发出 contextmenu 事件(携带 node 与 key)', async () => {
const wrapper = mount(Tree, { props: { treeData, fieldNames } })
await wrapper.find('.tree-node-content').trigger('contextmenu')
const emitted = wrapper.emitted('contextmenu')
expect(emitted).toHaveLength(1)
expect(emitted[0][0].node.b_id).toBe('1')
expect(emitted[0][0].key).toBe('1')
expect(emitted[0][0].event).toBeTruthy()
})
it('节点右键不会重复触发树空白处右键逻辑', async () => {
const wrapper = mount(Tree, { props: { treeData, fieldNames } })
await wrapper.find('.tree-node-content').trigger('contextmenu')
expect(wrapper.emitted('contextmenu')).toHaveLength(1)
})
it('空白区域右键发出 contextmenu 事件(node 为 null)', async () => {
const wrapper = mount(Tree, { props: { treeData, fieldNames } })
await wrapper.find('.tree').trigger('contextmenu')
const emitted = wrapper.emitted('contextmenu')
expect(emitted).toHaveLength(1)
expect(emitted[0][0].node).toBeNull()
expect(emitted[0][0].key).toBeNull()
})
it('传入 contextMenu 时按节点渲染菜单(Teleport 到 body)并发出 contextMenuClick', async () => {
const contextMenu = vi.fn((node) =>
node
? [
{ key: 'add', label: '新增子项' },
{ divider: true },
{ key: 'delete', label: '删除', danger: true },
]
: [{ key: 'add-root', label: '新增顶级' }],
)
const wrapper = mount(Tree, { props: { treeData, fieldNames, contextMenu } })
// 节点右键:渲染节点菜单
await wrapper.find('.tree-node-content').trigger('contextmenu')
expect(contextMenu).toHaveBeenCalled()
expect(contextMenu.mock.calls[0][0].b_id).toBe('1')
let menu = document.body.querySelector('.tree-context-menu')
expect(menu).toBeTruthy()
expect(menu.textContent).toContain('新增子项')
const divider = menu.querySelector('.tree-context-menu__divider')
expect(divider).toBeTruthy()
menu.querySelector('.tree-context-menu__item').click()
expect(wrapper.emitted('contextMenuClick')).toHaveLength(1)
expect(wrapper.emitted('contextMenuClick')[0][0].item.key).toBe('add')
expect(wrapper.emitted('contextMenuClick')[0][0].node.b_id).toBe('1')
// 点击后菜单关闭(等待离开动画结束)
await new Promise((r) => setTimeout(r, 150))
expect(document.body.querySelector('.tree-context-menu')).toBeNull()
// 空白右键:渲染顶级菜单
await wrapper.find('.tree').trigger('contextmenu')
menu = document.body.querySelector('.tree-context-menu')
expect(menu.textContent).toContain('新增顶级')
})
it('disabled 菜单项点击不发出 contextMenuClick', async () => {
const contextMenu = () => [{ key: 'x', label: '禁用项', disabled: true }]
const wrapper = mount(Tree, { props: { treeData, fieldNames, contextMenu } })
await wrapper.find('.tree-node-content').trigger('contextmenu')
const menu = document.body.querySelector('.tree-context-menu')
menu.querySelector('.tree-context-menu__item').click()
expect(wrapper.emitted('contextMenuClick')).toBeUndefined()
})
it('点击菜单外部关闭菜单', async () => {
const contextMenu = () => [{ key: 'x', label: '项目' }]
const wrapper = mount(Tree, { props: { treeData, fieldNames, contextMenu } })
await wrapper.find('.tree-node-content').trigger('contextmenu')
expect(document.body.querySelector('.tree-context-menu')).toBeTruthy()
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }))
await new Promise((r) => setTimeout(r, 0))
expect(document.body.querySelector('.tree-context-menu')).toBeNull()
wrapper.unmount()
})
})
describe('Tree 行内元素顺序', () => {
/**
* 回归点:箭头槽(非叶为真箭头、叶子为等宽占位)必须排在拖拽手柄之前。
* 放到手柄之后时,叶子行的手柄与标题之间会空出一整个箭头宽,读起来像多余空隙;
* 排在前面则这段空白归入左侧缩进带,手柄紧跟标题(见 tree-node.vue 模板注释)。
*/
const rowClasses = (row) => [...row.element.children].map((el) => el.className)
it('拖拽开启时:箭头槽在拖拽手柄之前', () => {
const wrapper = mount(Tree, { props: { treeData, fieldNames, draggable: true } })
const parentRow = wrapper.findAll('.tree-node')[0]
expect(rowClasses(parentRow)).toEqual(['tree-node-switcher', 'tree-node-grip', 'tree-node-content'])
})
it('叶子行的占位与手柄顺序一致,保证标题横坐标与非叶兄弟相同', () => {
const wrapper = mount(Tree, {
props: { treeData, fieldNames, draggable: true, defaultExpandedKeys: ['1'] },
})
const childRow = wrapper.findAll('.tree-node')[1]
expect(rowClasses(childRow)).toEqual([
'tree-node-switcher is-placeholder',
'tree-node-grip',
'tree-node-content',
])
})
})