20260915221013

This commit is contained in:
oneao committed 2026-09-15 22:10:13 +08:00
1 parent 1f56cc3efb
commit dce29f250e
54 files changed
+3732 -574

No files matched your search

+89 -12
View File
@@ -1,4 +1,5 @@
import { describe, it, expect, vi } from 'vitest'
import { describe, it, expect, vi, afterEach } from 'vitest'
import { nextTick } from 'vue'
import { mount } from '@vue/test-utils'
import Tree from '@/components/ui/tree/tree.vue'
@@ -91,27 +92,103 @@ describe('Tree 右键菜单', () => {
describe('Tree 行内元素顺序', () => {
/**
* 回归点:箭头槽(非叶为真箭头、叶子为等宽占位)必须排在拖拽手柄之前。
* 放到手柄之后时,叶子行的手柄与标题之间会空出一整个箭头宽,读起来像多余空隙;
* 排在前面则这段空白归入左侧缩进带,手柄紧跟标题(见 tree-node.vue 模板注释)。
* 回归点:行内只有「箭头槽 → 标题」,不再有拖拽手柄列 ——
* 拖拽改由长按整行发起,行与行之间不存在「有手柄 / 无手柄」的宽度差异。
* 箭头槽是全行唯一的固定槽:非叶为真箭头、叶子为等宽占位,
* 保证同层标题左边界一致(去掉叶子占位会让叶子标题左移一格)。
*/
const rowClasses = (row) => [...row.element.children].map((el) => el.className)
it('拖拽开启时:箭头槽在拖拽手柄之前', () => {
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'])
expect(rowClasses(parentRow)).toEqual(['tree-node-switcher', 'tree-node-content'])
})
it('叶子行的占位与手柄顺序一致,保证标题横坐标与非叶兄弟相同', () => {
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',
])
expect(rowClasses(childRow)).toEqual(['tree-node-switcher is-placeholder', 'tree-node-content'])
})
it('不可拖拽的节点与可拖拽节点行内结构一致', () => {
const wrapper = mount(Tree, {
props: {
fieldNames,
draggable: true,
defaultExpandedKeys: ['1'],
treeData: [
{
b_id: '1',
b_name: '全部分类',
draggable: false,
children: [{ b_id: '2', b_name: '子节点' }],
},
],
},
})
const rows = wrapper.findAll('.tree-node')
expect(rowClasses(rows[0])).toEqual(['tree-node-switcher', 'tree-node-content'])
expect(rowClasses(rows[1])).toEqual(['tree-node-switcher is-placeholder', 'tree-node-content'])
})
})
describe('Tree 长按拖拽', () => {
const rowOf = (wrapper, index = 0) => wrapper.findAll('.tree-node')[index]
const press = (row) => row.trigger('mousedown', { button: 0, clientX: 0, clientY: 0 })
afterEach(() => vi.useRealTimers())
it('长按到位后才置为可拖拽,短按保持普通行', async () => {
vi.useFakeTimers()
const wrapper = mount(Tree, { props: { treeData, fieldNames, draggable: true } })
const row = rowOf(wrapper)
await press(row)
// 未到长按时长:仍是普通行,短按/双击/多选点击的语义不受影响
expect(row.attributes('draggable')).toBe('false')
vi.advanceTimersByTime(400)
await nextTick()
expect(row.attributes('draggable')).toBe('true')
wrapper.unmount()
})
it('长按期间位移超过余量则放弃本次拖拽', async () => {
vi.useFakeTimers()
const wrapper = mount(Tree, { props: { treeData, fieldNames, draggable: true } })
const row = rowOf(wrapper)
await press(row)
document.dispatchEvent(new MouseEvent('mousemove', { clientX: 40, clientY: 0 }))
vi.advanceTimersByTime(400)
await nextTick()
expect(row.attributes('draggable')).toBe('false')
wrapper.unmount()
})
it('未开启 draggable 或节点标记 draggable:false 时不进入拖拽', async () => {
vi.useFakeTimers()
const plain = mount(Tree, { props: { treeData, fieldNames } })
await press(rowOf(plain))
vi.advanceTimersByTime(500)
await nextTick()
expect(rowOf(plain).attributes('draggable')).toBe('false')
plain.unmount()
const locked = mount(Tree, {
props: {
fieldNames,
draggable: true,
treeData: [{ b_id: '1', b_name: '根节点', draggable: false, children: [] }],
},
})
await press(rowOf(locked))
vi.advanceTimersByTime(500)
await nextTick()
expect(rowOf(locked).attributes('draggable')).toBe('false')
locked.unmount()
})
})