// @ts-nocheck
import { nextTick } from 'vue'
import { NOOP } from '@vue/shared'
import { describe, expect, test, vi } from 'vitest'
import { makeMountFunc } from '@element-plus/test-utils/make-mount'
import Tree from '../src/tree.vue'
import type {
FilterMethod,
TreeData,
TreeKey,
TreeNode,
TreeNodeData,
TreeOptionProps,
} from '../src/types'
let id = 1
const NODE_NUMBER = 5
const TREE_NODE_CLASS_NAME = '.el-tree-node'
const TREE_NODE_CONTENT_CLASS_NAME = '.el-tree-node__content'
const TREE_NODE_EXPAND_ICON_CLASS_NAME = '.el-tree-node__expand-icon'
const getUniqueId = () => {
return id++
}
const createData = (
maxDeep,
maxChildren,
minNodesNumber,
deep = 1,
disabled = false
) => {
return Array.from({ length: minNodesNumber })
.fill(deep)
.map(() => {
const id = getUniqueId()
const childrenNumber =
deep === maxDeep ? 0 : Math.round(Math.random() * maxChildren)
return {
id,
disabled: disabled ? Math.random() > 0.7 : false,
label: `node-${id}`,
children: childrenNumber
? createData(maxDeep, maxChildren, childrenNumber, deep + 1, disabled)
: [],
}
})
}
const data = createData(4, 30, NODE_NUMBER)
const _mount = makeMountFunc({
components: {
'el-tree': Tree,
},
})
interface TreeProps {
data?: TreeData
emptyText?: string
height?: number
props?: TreeOptionProps
highlightCurrent?: boolean
showCheckbox?: boolean
defaultCheckedKeys?: TreeKey[]
checkStrictly?: boolean
defaultExpandedKeys?: TreeKey[]
indent?: number
iconClass?: string
expandOnClickNode?: boolean
checkOnClickNode?: boolean
currentNodeKey?: TreeKey
filterMethod?: FilterMethod
}
interface TreeEvents {
onNodeClick?: (nodeData?: TreeNodeData, node?: TreeNode) => void
onNodeExpand?: (nodeData?: TreeNodeData, node?: TreeNode) => void
onNodeCheck?: (
nodeData?: TreeNodeData,
checked?: {
checkedKeys: TreeKey[]
checkedNodes: TreeNodeData[]
halfCheckedKeys: TreeKey[]
halfCheckedNodes: TreeNodeData[]
}
) => void
onCurrentChange?: (nodeData?: TreeNodeData, node?: TreeNode) => void
onNodeContextMenu?: (
e?: Event,
nodeData?: TreeNodeData,
node?: TreeNode
) => void
}
const createTree = (
options: {
data?: () => TreeProps
methods?: TreeEvents
slots?: {
default?: string
}
} = {}
) => {
const defaultSlot =
(options.slots &&
options.slots.default &&
`${options.slots.default}`) ||
''
const wrapper = _mount(
`
${defaultSlot}
`,
{
data() {
return {
data,
emptyText: undefined,
height: undefined,
props: {
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id',
},
highlightCurrent: false,
showCheckbox: false,
defaultCheckedKeys: undefined,
checkStrictly: false,
defaultExpandedKeys: undefined,
indent: 16,
iconClass: undefined,
expandOnClickNode: true,
checkOnClickNode: false,
currentNodeKey: undefined,
filterMethod: undefined,
...(options.data && options.data()),
}
},
methods: {
onNodeClick: NOOP,
onNodeExpand: NOOP,
onNodeCheck: NOOP,
onCurrentChange: NOOP,
onNodeContextMenu: NOOP,
...options.methods,
},
}
)
const treeWrapper = wrapper.findComponent(Tree)
const vm = wrapper.vm as any
return {
wrapper,
treeRef: vm.$refs.tree,
vm,
treeWrapper,
treeVm: treeWrapper.vm as any,
}
}
describe('Virtual Tree', () => {
test('create', async () => {
const { treeVm } = createTree()
await nextTick()
expect(treeVm.flattenTree.length).toEqual(NODE_NUMBER)
})
test('click node', async () => {
const onNodeClick = vi.fn()
const { wrapper, treeVm } = createTree({
methods: {
onNodeClick,
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
expect(onNodeClick).toBeCalled()
expect(treeVm.flattenTree.length).toBeGreaterThanOrEqual(NODE_NUMBER)
})
test('emptyText', async () => {
const emptyText = '暂无数据'
const { wrapper } = createTree({
data() {
return {
emptyText,
data: [],
}
},
})
await nextTick()
expect(wrapper.find('.el-tree__empty-text').text()).toBe(emptyText)
})
test('height', async () => {
const { wrapper } = createTree({
data() {
return {
height: 300,
}
},
})
await nextTick()
const el = wrapper.find('.el-tree-virtual-list').element as any
expect(el.style.height).toBe('300px')
})
test('props', async () => {
const { wrapper } = createTree({
data() {
return {
data: [
{
key: '1',
text: 'node-1',
readonly: false,
sub: [
{
key: '1-1',
text: 'node-1-1',
readonly: false,
},
],
},
{
key: '2',
text: 'node-2',
readonly: false,
sub: [
{
key: '2-1',
text: 'node-2-1',
},
{
key: '2-2',
text: 'node-2-2',
readonly: true,
},
],
},
],
props: {
value: 'key',
label: 'text',
disabled: 'readonly',
children: 'sub',
},
}
},
})
await nextTick()
let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
// test props.label
expect(nodes[0].text()).toBe('node-1')
expect(nodes[1].text()).toBe('node-2')
// expand node-2
await nodes[1].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
// test props.children
expect(nodes[2].text()).toBe('node-2-1')
expect(nodes[3].text()).toBe('node-2-2')
// test props.disabled
expect(nodes[3].classes()).not.toContain('is-focusable')
})
test('highlightCurrent', async () => {
const { wrapper } = createTree({
data() {
return {
highlightCurrent: true,
}
},
})
await nextTick()
expect(wrapper.classes()).toContain('el-tree--highlight-current')
})
test('showCheckbox', async () => {
const { wrapper } = createTree({
data() {
return {
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
showCheckbox: true,
}
},
})
await nextTick()
expect(wrapper.find('.el-checkbox').exists()).toBeTruthy()
// expand all nodes
let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[1].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[4].trigger('click')
nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes.length).toBe(8)
// When node-1 is checked, all child nodes should be checked
await nodes[0].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(7)
// When cancel node-1 checked, all child nodes should not be checked
await nodes[0].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
// When node-1-1 is checked, node-1-1-1 and node-1-1-2 should be checked
await nodes[1].find('.el-checkbox').trigger('click')
expect(
wrapper
.findAll(`${TREE_NODE_CLASS_NAME}.is-checked`)
.map((el) => el.text())
.toString()
).toBe(['node-1-1', 'node-1-1-1', 'node-1-1-2'].toString())
// When cancel node-1-1, node-1-1-1 and node-1-1-2 should not be checked
await nodes[1].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
// When node-1-1-1 is checked, node-1 and node-1-1 should be indeterminate
await nodes[2].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(1)
expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(2)
// When node-1-1-1 and node-1-1-2 are checked, node-1-1 should be checked, node-1 should be indeterminate
await nodes[3].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(3)
expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(1)
await nodes[3].find('.el-checkbox').trigger('click')
await nodes[2].find('.el-checkbox').trigger('click')
// test one leaf node
// When node-1-2-1 is checked, node-1-2 should be checked
await nodes[5].find('.el-checkbox').trigger('click')
expect(
wrapper
.findAll(`${TREE_NODE_CLASS_NAME}.is-checked`)
.map((el) => el.text())
.toString()
).toBe(['node-1-2', 'node-1-2-1'].toString())
// cancel node-1-2-1, node-1-2 should not be checked
await nodes[5].find('.el-checkbox').trigger('click')
expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(0)
})
test('defaultCheckedKeys', async () => {
const { treeRef } = createTree({
data() {
return {
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
defaultCheckedKeys: ['1-1-1', '1-1-2'],
showCheckbox: true,
}
},
})
await nextTick()
// node-1-1 should be checked
expect(treeRef.getCheckedKeys().length).toBe(3)
// node-1-1 should be indeterminate
expect(treeRef.getHalfCheckedKeys().length).toBe(1)
})
test('checkStrictly', async () => {
const { treeRef, wrapper } = createTree({
data() {
return {
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
defaultCheckedKeys: ['1-1-1', '1-1-2'],
showCheckbox: true,
checkStrictly: true,
}
},
})
await nextTick()
// node-1-1 should not be checked
expect(treeRef.getCheckedKeys().length).toBe(2)
// node-1-1 should not be indeterminate
expect(treeRef.getHalfCheckedKeys().length).toBe(0)
// manual
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].find('.el-checkbox').trigger('click')
expect(treeRef.getCheckedKeys().length).toBe(3)
})
test('defaultExpandedKeys', async () => {
const { wrapper } = createTree({
data() {
return {
height: 400,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
defaultExpandedKeys: ['1'],
}
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes.length).toBe(5)
})
test('indent', async () => {
const { wrapper } = createTree({
data() {
return {
indent: 20,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
defaultExpandedKeys: ['1'],
}
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
const node = nodes[1].element.querySelector(
TREE_NODE_CONTENT_CLASS_NAME
) as any
expect(node.style.paddingLeft).toBe('20px')
})
test('expandOnClickNode', async () => {
const onNodeExpand = vi.fn()
const { wrapper } = createTree({
data() {
return {
expandOnClickNode: false,
}
},
methods: {
onNodeExpand,
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
expect(onNodeExpand).not.toHaveBeenCalled()
await nodes[0].find(TREE_NODE_EXPAND_ICON_CLASS_NAME).trigger('click')
expect(onNodeExpand).toHaveBeenCalled()
})
test('checkOnClickNode', async () => {
const { wrapper, treeRef } = createTree({
data() {
return {
showCheckbox: true,
expandOnClickNode: false,
checkOnClickNode: true,
checkStrictly: true,
}
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[0].trigger('click')
expect(treeRef.getCheckedKeys().toString()).toBe([1].toString())
})
test('currentNodeKey', async () => {
const { wrapper } = createTree({
data() {
return {
currentNodeKey: '2',
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes[1].classes()).toContain('is-current')
})
test('custom node content', async () => {
const { wrapper } = createTree({
slots: {
default: `
cc {{node.label}}
`,
},
})
await nextTick()
expect(wrapper.find('.custom-tree-node-content').text()).toBe('cc node-1')
})
test('filter', async () => {
const { treeRef, wrapper } = createTree({
data() {
return {
currentNodeKey: '2',
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
filterMethod(query: string, node: TreeNodeData) {
return node.label.includes(query)
},
}
},
})
await nextTick()
treeRef.filter('node-1-1-1')
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
expect(nodes.map((node) => node.text()).toString()).toBe(
['node-1', 'node-1-1', 'node-1-1-1'].toString()
)
})
describe('events', () => {
test('current-change', async () => {
const onCurrentChange = vi.fn()
const { wrapper, vm, treeVm } = createTree({
methods: {
onCurrentChange,
},
})
await nextTick()
await wrapper.find(TREE_NODE_CLASS_NAME).trigger('click')
expect(onCurrentChange).toHaveBeenCalledTimes(1)
expect(onCurrentChange).toHaveBeenCalledWith(
vm.data[0],
treeVm.flattenTree[0]
)
})
test('check', async () => {
const onNodeCheck = vi.fn()
const { wrapper } = createTree({
data() {
return {
showCheckbox: true,
defaultExpandedKeys: ['1-1', '1'],
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
methods: {
onNodeCheck,
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[2].find('.el-checkbox').trigger('click')
expect(onNodeCheck).toHaveBeenCalledTimes(1)
expect(onNodeCheck).toHaveBeenCalledWith(
{ id: '1-1-1', label: 'node-1-1-1' },
{
checkedKeys: ['1-1-1'],
checkedNodes: [{ id: '1-1-1', label: 'node-1-1-1' }],
halfCheckedKeys: ['1-1', '1'],
halfCheckedNodes: [
{
children: [
{ id: '1-1-1', label: 'node-1-1-1' },
{ id: '1-1-2', label: 'node-1-1-2' },
],
id: '1-1',
label: 'node-1-1',
},
{
children: [
{
children: [
{ id: '1-1-1', label: 'node-1-1-1' },
{ id: '1-1-2', label: 'node-1-1-2' },
],
id: '1-1',
label: 'node-1-1',
},
{
children: [{ id: '1-2-1', label: 'node-1-2-1' }],
id: '1-2',
label: 'node-1-2',
},
{ id: '1-3', label: 'node-1-3' },
],
id: '1',
label: 'node-1',
},
],
}
)
})
test('context-menu', async () => {
const onNodeContextMenu = vi.fn()
const { wrapper } = createTree({
methods: {
onNodeContextMenu,
},
})
await nextTick()
await wrapper.find(TREE_NODE_CLASS_NAME).trigger('contextmenu')
expect(onNodeContextMenu).toHaveBeenCalledTimes(1)
})
})
describe('methods', () => {
test('getChecked', async () => {
const { treeRef } = createTree({
data() {
return {
showCheckbox: true,
defaultCheckedKeys: ['1-1-2'],
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
const checkedKeys = treeRef.getCheckedKeys()
const checkedNodes = treeRef.getCheckedNodes()
const halfCheckedKeys = treeRef.getHalfCheckedKeys()
const halfCheckedNodes = treeRef.getHalfCheckedNodes()
expect(checkedKeys.toString()).toBe(['1-1-2'].toString())
expect(checkedNodes.map((node) => node.id).toString()).toBe(
['1-1-2'].toString()
)
expect(halfCheckedKeys.toString()).toBe(['1-1', '1'].toString())
expect(halfCheckedNodes.map((node) => node.id).toString()).toBe(
['1-1', '1'].toString()
)
})
test('setCheckedKeys', async () => {
const { treeRef } = createTree({
data() {
return {
showCheckbox: true,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
treeRef.setCheckedKeys(['1-1'])
await nextTick()
const checkedKeys = treeRef.getCheckedKeys()
const halfCheckedKeys = treeRef.getHalfCheckedKeys()
expect(checkedKeys.toString()).toBe(['1-1', '1-1-1', '1-1-2'].toString())
expect(halfCheckedKeys.toString()).toBe(['1'].toString())
})
test('setChecked', async () => {
const { treeRef } = createTree({
data() {
return {
showCheckbox: true,
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
treeRef.setChecked('1-1', true)
const checkedKeys = treeRef.getCheckedKeys()
const halfCheckedKeys = treeRef.getHalfCheckedKeys()
expect(checkedKeys.toString()).toBe(['1-1', '1-1-1', '1-1-2'].toString())
expect(halfCheckedKeys.toString()).toBe(['1'].toString())
})
test('getCurrent', async () => {
const { treeRef, wrapper } = createTree({
data() {
return {
defaultExpandedKeys: ['1', '1-1'],
data: [
{
id: '1',
label: 'node-1',
children: [
{
id: '1-1',
label: 'node-1-1',
children: [
{
id: '1-1-1',
label: 'node-1-1-1',
},
{
id: '1-1-2',
label: 'node-1-1-2',
},
],
},
{
id: '1-2',
label: 'node-1-2',
children: [
{
id: '1-2-1',
label: 'node-1-2-1',
},
],
},
{
id: '1-3',
label: 'node-1-3',
},
],
},
{
id: '2',
label: 'node-2',
},
],
}
},
})
await nextTick()
const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
await nodes[2].trigger('click')
expect(treeRef.getCurrentNode()).toMatchObject({
id: '1-1-1',
label: 'node-1-1-1',
})
expect(treeRef.getCurrentKey()).toBe('1-1-1')
treeRef.setCurrentKey('1-1-2')
expect(treeRef.getCurrentNode()).toMatchObject({
id: '1-1-2',
label: 'node-1-1-2',
})
expect(treeRef.getCurrentKey()).toBe('1-1-2')
})
})
})