import { mount } from '@vue/test-utils' import { describe, expect, it, vi, beforeEach } from 'vitest' import Tree from '@/components/ui/tree/tree.vue' /** * 模拟 Chrome 的 DataTransfer 行为: * - dragover / dragenter 阶段:getData() 对所有类型返回空字符串(隐私保护),但 types / effectAllowed 可用 * - drop 阶段:getData() 正常返回 dragstart 中写入的数据 */ function makeDataTransfer({ types = [], get = () => '' } = {}) { return { effectAllowed: 'copy', dropEffect: 'none', types, getData: vi.fn((t) => get(t)), setData: vi.fn(), } } const treeData = [ { key: 'g:1', title: '分组1', isLeaf: false, children: [{ key: 'c:1', title: '列1', isLeaf: true }], }, ] describe('Tree 外部拖拽(字段库 → 分组)', () => { beforeEach(() => { // 让 calcDropPosition 能算出 'inside'(行高 40,鼠标落在中点) Element.prototype.getBoundingClientRect = () => ({ top: 0, bottom: 40, left: 0, right: 200, width: 200, height: 40, x: 0, y: 0, toJSON: () => ({}), }) }) it('dragover 阶段 getData 为空(Chrome 限制)时,基于 types 识别外部源并设置拖放指示', async () => { const externalDragKey = vi.fn((dt) => { const types = Array.from(dt?.types || []) if (!types.includes('application/fms-field')) return null const fid = dt?.getData?.('application/fms-field') return fid ? `external-field:${fid}` : `external-field:__pending__` }) const wrapper = mount(Tree, { props: { treeData, draggable: true, externalDragKey } }) const nodeRow = wrapper.find('.tree-node') // dragover:getData 读不到值(模拟 Chrome),仅 types 可用 const dt = makeDataTransfer({ types: ['application/fms-field'], get: () => '' }) await nodeRow.trigger('dragover', { dataTransfer: dt, clientY: 20 }) expect(externalDragKey).toHaveBeenCalled() // dragKey 已被解析 → 节点行出现放置指示(说明拖拽已被识别,可正常落放) expect(nodeRow.classes()).toContain('drop-inside') }) it('dragover 阶段仅用 getData 判断时(修复前实现),拖放指示不出现', async () => { // 旧实现:只靠 getData 判断拖拽源 → Chrome 下 dragover 读不到 → dragKey 不设置 const externalDragKey = vi.fn((dt) => { const fid = dt?.getData?.('application/fms-field') return fid ? `external-field:${String(fid)}` : null }) const wrapper = mount(Tree, { props: { treeData, draggable: true, externalDragKey } }) const nodeRow = wrapper.find('.tree-node') const dt = makeDataTransfer({ types: ['application/fms-field'], get: () => '' }) await nodeRow.trigger('dragover', { dataTransfer: dt, clientY: 20 }) // 没有任何 drop-* 指示类 expect(nodeRow.classes().some((c) => c.startsWith('drop-'))).toBe(false) }) it('drop 阶段 getData 可用时,发出正确的 external-field 拖拽键', async () => { const externalDragKey = (dt) => { const types = Array.from(dt?.types || []) if (!types.includes('application/fms-field')) return null const fid = dt?.getData?.('application/fms-field') return fid ? `external-field:${fid}` : `external-field:__pending__` } const wrapper = mount(Tree, { props: { treeData, draggable: true, externalDragKey } }) const nodeRow = wrapper.find('.tree-node') // dragover(读不到)→ drop(能读到 '42') const overDT = makeDataTransfer({ types: ['application/fms-field'], get: () => '' }) await nodeRow.trigger('dragover', { dataTransfer: overDT, clientY: 20 }) const dropDT = makeDataTransfer({ types: ['application/fms-field'], get: (t) => (t === 'application/fms-field' ? '42' : ''), }) await nodeRow.trigger('drop', { dataTransfer: dropDT }) const drops = wrapper.emitted('drop') expect(drops).toBeTruthy() expect(drops[0][0]).toMatchObject({ dragKey: 'external-field:42', dropKey: 'g:1' }) }) })