tree.test.ts 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180
  1. // @ts-nocheck
  2. import { nextTick } from 'vue'
  3. import { NOOP } from '@vue/shared'
  4. import { describe, expect, test, vi } from 'vitest'
  5. import { makeMountFunc } from '@element-plus/test-utils/make-mount'
  6. import Tree from '../src/tree.vue'
  7. import type {
  8. FilterMethod,
  9. TreeData,
  10. TreeKey,
  11. TreeNode,
  12. TreeNodeData,
  13. TreeOptionProps,
  14. } from '../src/types'
  15. let id = 1
  16. const NODE_NUMBER = 5
  17. const TREE_NODE_CLASS_NAME = '.el-tree-node'
  18. const TREE_NODE_CONTENT_CLASS_NAME = '.el-tree-node__content'
  19. const TREE_NODE_EXPAND_ICON_CLASS_NAME = '.el-tree-node__expand-icon'
  20. const getUniqueId = () => {
  21. return id++
  22. }
  23. const createData = (
  24. maxDeep,
  25. maxChildren,
  26. minNodesNumber,
  27. deep = 1,
  28. disabled = false
  29. ) => {
  30. return Array.from({ length: minNodesNumber })
  31. .fill(deep)
  32. .map(() => {
  33. const id = getUniqueId()
  34. const childrenNumber =
  35. deep === maxDeep ? 0 : Math.round(Math.random() * maxChildren)
  36. return {
  37. id,
  38. disabled: disabled ? Math.random() > 0.7 : false,
  39. label: `node-${id}`,
  40. children: childrenNumber
  41. ? createData(maxDeep, maxChildren, childrenNumber, deep + 1, disabled)
  42. : [],
  43. }
  44. })
  45. }
  46. const data = createData(4, 30, NODE_NUMBER)
  47. const _mount = makeMountFunc({
  48. components: {
  49. 'el-tree': Tree,
  50. },
  51. })
  52. interface TreeProps {
  53. data?: TreeData
  54. emptyText?: string
  55. height?: number
  56. props?: TreeOptionProps
  57. highlightCurrent?: boolean
  58. showCheckbox?: boolean
  59. defaultCheckedKeys?: TreeKey[]
  60. checkStrictly?: boolean
  61. defaultExpandedKeys?: TreeKey[]
  62. indent?: number
  63. iconClass?: string
  64. expandOnClickNode?: boolean
  65. checkOnClickNode?: boolean
  66. currentNodeKey?: TreeKey
  67. filterMethod?: FilterMethod
  68. }
  69. interface TreeEvents {
  70. onNodeClick?: (nodeData?: TreeNodeData, node?: TreeNode) => void
  71. onNodeExpand?: (nodeData?: TreeNodeData, node?: TreeNode) => void
  72. onNodeCheck?: (
  73. nodeData?: TreeNodeData,
  74. checked?: {
  75. checkedKeys: TreeKey[]
  76. checkedNodes: TreeNodeData[]
  77. halfCheckedKeys: TreeKey[]
  78. halfCheckedNodes: TreeNodeData[]
  79. }
  80. ) => void
  81. onCurrentChange?: (nodeData?: TreeNodeData, node?: TreeNode) => void
  82. onNodeContextMenu?: (
  83. e?: Event,
  84. nodeData?: TreeNodeData,
  85. node?: TreeNode
  86. ) => void
  87. }
  88. const createTree = (
  89. options: {
  90. data?: () => TreeProps
  91. methods?: TreeEvents
  92. slots?: {
  93. default?: string
  94. }
  95. } = {}
  96. ) => {
  97. const defaultSlot =
  98. (options.slots &&
  99. options.slots.default &&
  100. `<template #default="{node}">${options.slots.default}</template>`) ||
  101. ''
  102. const wrapper = _mount(
  103. `
  104. <el-tree
  105. ref="tree"
  106. :data="data"
  107. :empty-text="emptyText"
  108. :height="height"
  109. :props="props"
  110. :highlight-current="highlightCurrent"
  111. :show-checkbox="showCheckbox"
  112. :default-checked-keys="defaultCheckedKeys"
  113. :check-strictly="checkStrictly"
  114. :default-expanded-keys="defaultExpandedKeys"
  115. :indent="indent"
  116. :item-size="itemSize"
  117. :icon-class="iconClass"
  118. :expand-on-click-node="expandOnClickNode"
  119. :check-on-click-node="checkOnClickNode"
  120. :current-node-key="currentNodeKey"
  121. :filter-method="filterMethod"
  122. @node-click="onNodeClick"
  123. @node-expand="onNodeExpand"
  124. @check="onNodeCheck"
  125. @current-change="onCurrentChange"
  126. @node-contextmenu="onNodeContextMenu"
  127. >${defaultSlot}</el-tree>
  128. `,
  129. {
  130. data() {
  131. return {
  132. data,
  133. emptyText: undefined,
  134. height: undefined,
  135. props: {
  136. children: 'children',
  137. label: 'label',
  138. disabled: 'disabled',
  139. value: 'id',
  140. },
  141. highlightCurrent: false,
  142. showCheckbox: false,
  143. defaultCheckedKeys: undefined,
  144. checkStrictly: false,
  145. defaultExpandedKeys: undefined,
  146. indent: 16,
  147. itemSize: 26,
  148. iconClass: undefined,
  149. expandOnClickNode: true,
  150. checkOnClickNode: false,
  151. currentNodeKey: undefined,
  152. filterMethod: undefined,
  153. ...(options.data && options.data()),
  154. }
  155. },
  156. methods: {
  157. onNodeClick: NOOP,
  158. onNodeExpand: NOOP,
  159. onNodeCheck: NOOP,
  160. onCurrentChange: NOOP,
  161. onNodeContextMenu: NOOP,
  162. ...options.methods,
  163. },
  164. }
  165. )
  166. const treeWrapper = wrapper.findComponent(Tree)
  167. const vm = wrapper.vm as any
  168. return {
  169. wrapper,
  170. treeRef: vm.$refs.tree,
  171. vm,
  172. treeWrapper,
  173. treeVm: treeWrapper.vm as any,
  174. }
  175. }
  176. describe('Virtual Tree', () => {
  177. test('create', async () => {
  178. const { wrapper, treeVm } = createTree()
  179. await nextTick()
  180. expect(treeVm.flattenTree.length).toEqual(NODE_NUMBER)
  181. const iconWrapper = wrapper.find(TREE_NODE_EXPAND_ICON_CLASS_NAME)
  182. expect(iconWrapper.find('svg').exists()).toBeTruthy()
  183. })
  184. test('click node', async () => {
  185. const onNodeClick = vi.fn()
  186. const { wrapper, treeVm } = createTree({
  187. methods: {
  188. onNodeClick,
  189. },
  190. })
  191. await nextTick()
  192. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  193. await nodes[0].trigger('click')
  194. expect(onNodeClick).toBeCalled()
  195. expect(treeVm.flattenTree.length).toBeGreaterThanOrEqual(NODE_NUMBER)
  196. })
  197. test('emptyText', async () => {
  198. const emptyText = '暂无数据'
  199. const { wrapper } = createTree({
  200. data() {
  201. return {
  202. emptyText,
  203. data: [],
  204. }
  205. },
  206. })
  207. await nextTick()
  208. expect(wrapper.find('.el-tree__empty-text').text()).toBe(emptyText)
  209. })
  210. test('height', async () => {
  211. const { wrapper } = createTree({
  212. data() {
  213. return {
  214. height: 300,
  215. }
  216. },
  217. })
  218. await nextTick()
  219. const el = wrapper.find('.el-tree-virtual-list').element as any
  220. expect(el.style.height).toBe('300px')
  221. })
  222. test('item-size', async () => {
  223. const { wrapper } = createTree({
  224. data() {
  225. return {
  226. itemSize: 40,
  227. }
  228. },
  229. })
  230. await nextTick()
  231. const node = wrapper.find('.el-tree-node').element
  232. const content = wrapper.find('.el-tree-node__content').element
  233. expect(node.style.height).toBe('40px')
  234. expect(content.style.height).toBe('40px')
  235. })
  236. test('props', async () => {
  237. const { wrapper } = createTree({
  238. data() {
  239. return {
  240. data: [
  241. {
  242. key: '1',
  243. text: 'node-1',
  244. readonly: false,
  245. sub: [
  246. {
  247. key: '1-1',
  248. text: 'node-1-1',
  249. readonly: false,
  250. },
  251. ],
  252. },
  253. {
  254. key: '2',
  255. text: 'node-2',
  256. readonly: false,
  257. sub: [
  258. {
  259. key: '2-1',
  260. text: 'node-2-1',
  261. },
  262. {
  263. key: '2-2',
  264. text: 'node-2-2',
  265. readonly: true,
  266. },
  267. ],
  268. },
  269. ],
  270. props: {
  271. value: 'key',
  272. label: 'text',
  273. disabled: 'readonly',
  274. children: 'sub',
  275. },
  276. }
  277. },
  278. })
  279. await nextTick()
  280. let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  281. // test props.label
  282. expect(nodes[0].text()).toBe('node-1')
  283. expect(nodes[1].text()).toBe('node-2')
  284. // expand node-2
  285. await nodes[1].trigger('click')
  286. nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  287. // test props.children
  288. expect(nodes[2].text()).toBe('node-2-1')
  289. expect(nodes[3].text()).toBe('node-2-2')
  290. // test props.disabled
  291. expect(nodes[3].classes()).not.toContain('is-focusable')
  292. })
  293. test('highlightCurrent', async () => {
  294. const { wrapper } = createTree({
  295. data() {
  296. return {
  297. highlightCurrent: true,
  298. }
  299. },
  300. })
  301. await nextTick()
  302. expect(wrapper.classes()).toContain('el-tree--highlight-current')
  303. })
  304. test('showCheckbox', async () => {
  305. const { wrapper } = createTree({
  306. data() {
  307. return {
  308. height: 400,
  309. data: [
  310. {
  311. id: '1',
  312. label: 'node-1',
  313. children: [
  314. {
  315. id: '1-1',
  316. label: 'node-1-1',
  317. children: [
  318. {
  319. id: '1-1-1',
  320. label: 'node-1-1-1',
  321. },
  322. {
  323. id: '1-1-2',
  324. label: 'node-1-1-2',
  325. },
  326. ],
  327. },
  328. {
  329. id: '1-2',
  330. label: 'node-1-2',
  331. children: [
  332. {
  333. id: '1-2-1',
  334. label: 'node-1-2-1',
  335. },
  336. ],
  337. },
  338. {
  339. id: '1-3',
  340. label: 'node-1-3',
  341. },
  342. ],
  343. },
  344. {
  345. id: '2',
  346. label: 'node-2',
  347. },
  348. ],
  349. showCheckbox: true,
  350. }
  351. },
  352. })
  353. await nextTick()
  354. expect(wrapper.find('.el-checkbox').exists()).toBeTruthy()
  355. // expand all nodes
  356. let nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  357. await nodes[0].trigger('click')
  358. nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  359. await nodes[1].trigger('click')
  360. nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  361. await nodes[4].trigger('click')
  362. nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  363. expect(nodes.length).toBe(8)
  364. // When node-1 is checked, all child nodes should be checked
  365. await nodes[0].find('.el-checkbox').trigger('click')
  366. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(7)
  367. // When cancel node-1 checked, all child nodes should not be checked
  368. await nodes[0].find('.el-checkbox').trigger('click')
  369. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
  370. // When node-1-1 is checked, node-1-1-1 and node-1-1-2 should be checked
  371. await nodes[1].find('.el-checkbox').trigger('click')
  372. expect(
  373. wrapper
  374. .findAll(`${TREE_NODE_CLASS_NAME}.is-checked`)
  375. .map((el) => el.text())
  376. .toString()
  377. ).toBe(['node-1-1', 'node-1-1-1', 'node-1-1-2'].toString())
  378. // When cancel node-1-1, node-1-1-1 and node-1-1-2 should not be checked
  379. await nodes[1].find('.el-checkbox').trigger('click')
  380. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
  381. // When node-1-1-1 is checked, node-1 and node-1-1 should be indeterminate
  382. await nodes[2].find('.el-checkbox').trigger('click')
  383. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(1)
  384. expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(2)
  385. // When node-1-1-1 and node-1-1-2 are checked, node-1-1 should be checked, node-1 should be indeterminate
  386. await nodes[3].find('.el-checkbox').trigger('click')
  387. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(3)
  388. expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(1)
  389. await nodes[3].find('.el-checkbox').trigger('click')
  390. await nodes[2].find('.el-checkbox').trigger('click')
  391. // test one leaf node
  392. // When node-1-2-1 is checked, node-1-2 should be checked
  393. await nodes[5].find('.el-checkbox').trigger('click')
  394. expect(
  395. wrapper
  396. .findAll(`${TREE_NODE_CLASS_NAME}.is-checked`)
  397. .map((el) => el.text())
  398. .toString()
  399. ).toBe(['node-1-2', 'node-1-2-1'].toString())
  400. // cancel node-1-2-1, node-1-2 should not be checked
  401. await nodes[5].find('.el-checkbox').trigger('click')
  402. expect(wrapper.findAll('.el-checkbox.is-checked').length).toBe(0)
  403. expect(wrapper.findAll('.el-checkbox .is-indeterminate').length).toBe(0)
  404. })
  405. test('defaultCheckedKeys', async () => {
  406. const { treeRef } = createTree({
  407. data() {
  408. return {
  409. height: 400,
  410. data: [
  411. {
  412. id: '1',
  413. label: 'node-1',
  414. children: [
  415. {
  416. id: '1-1',
  417. label: 'node-1-1',
  418. children: [
  419. {
  420. id: '1-1-1',
  421. label: 'node-1-1-1',
  422. },
  423. {
  424. id: '1-1-2',
  425. label: 'node-1-1-2',
  426. },
  427. ],
  428. },
  429. {
  430. id: '1-2',
  431. label: 'node-1-2',
  432. children: [
  433. {
  434. id: '1-2-1',
  435. label: 'node-1-2-1',
  436. },
  437. ],
  438. },
  439. {
  440. id: '1-3',
  441. label: 'node-1-3',
  442. },
  443. ],
  444. },
  445. {
  446. id: '2',
  447. label: 'node-2',
  448. },
  449. ],
  450. defaultCheckedKeys: ['1-1-1', '1-1-2'],
  451. showCheckbox: true,
  452. }
  453. },
  454. })
  455. await nextTick()
  456. // node-1-1 should be checked
  457. expect(treeRef.getCheckedKeys().length).toBe(3)
  458. // node-1-1 should be indeterminate
  459. expect(treeRef.getHalfCheckedKeys().length).toBe(1)
  460. })
  461. test('checkStrictly', async () => {
  462. const { treeRef, wrapper } = createTree({
  463. data() {
  464. return {
  465. height: 400,
  466. data: [
  467. {
  468. id: '1',
  469. label: 'node-1',
  470. children: [
  471. {
  472. id: '1-1',
  473. label: 'node-1-1',
  474. children: [
  475. {
  476. id: '1-1-1',
  477. label: 'node-1-1-1',
  478. },
  479. {
  480. id: '1-1-2',
  481. label: 'node-1-1-2',
  482. },
  483. ],
  484. },
  485. {
  486. id: '1-2',
  487. label: 'node-1-2',
  488. children: [
  489. {
  490. id: '1-2-1',
  491. label: 'node-1-2-1',
  492. },
  493. ],
  494. },
  495. {
  496. id: '1-3',
  497. label: 'node-1-3',
  498. },
  499. ],
  500. },
  501. {
  502. id: '2',
  503. label: 'node-2',
  504. },
  505. ],
  506. defaultCheckedKeys: ['1-1-1', '1-1-2'],
  507. showCheckbox: true,
  508. checkStrictly: true,
  509. }
  510. },
  511. })
  512. await nextTick()
  513. // node-1-1 should not be checked
  514. expect(treeRef.getCheckedKeys().length).toBe(2)
  515. // node-1-1 should not be indeterminate
  516. expect(treeRef.getHalfCheckedKeys().length).toBe(0)
  517. // manual
  518. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  519. await nodes[0].find('.el-checkbox').trigger('click')
  520. expect(treeRef.getCheckedKeys().length).toBe(3)
  521. })
  522. test('defaultExpandedKeys', async () => {
  523. const { wrapper } = createTree({
  524. data() {
  525. return {
  526. height: 400,
  527. data: [
  528. {
  529. id: '1',
  530. label: 'node-1',
  531. children: [
  532. {
  533. id: '1-1',
  534. label: 'node-1-1',
  535. children: [
  536. {
  537. id: '1-1-1',
  538. label: 'node-1-1-1',
  539. },
  540. {
  541. id: '1-1-2',
  542. label: 'node-1-1-2',
  543. },
  544. ],
  545. },
  546. {
  547. id: '1-2',
  548. label: 'node-1-2',
  549. children: [
  550. {
  551. id: '1-2-1',
  552. label: 'node-1-2-1',
  553. },
  554. ],
  555. },
  556. {
  557. id: '1-3',
  558. label: 'node-1-3',
  559. },
  560. ],
  561. },
  562. {
  563. id: '2',
  564. label: 'node-2',
  565. },
  566. ],
  567. defaultExpandedKeys: ['1'],
  568. }
  569. },
  570. })
  571. await nextTick()
  572. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  573. expect(nodes.length).toBe(5)
  574. })
  575. test('indent', async () => {
  576. const { wrapper } = createTree({
  577. data() {
  578. return {
  579. indent: 20,
  580. data: [
  581. {
  582. id: '1',
  583. label: 'node-1',
  584. children: [
  585. {
  586. id: '1-1',
  587. label: 'node-1-1',
  588. children: [
  589. {
  590. id: '1-1-1',
  591. label: 'node-1-1-1',
  592. },
  593. {
  594. id: '1-1-2',
  595. label: 'node-1-1-2',
  596. },
  597. ],
  598. },
  599. {
  600. id: '1-2',
  601. label: 'node-1-2',
  602. children: [
  603. {
  604. id: '1-2-1',
  605. label: 'node-1-2-1',
  606. },
  607. ],
  608. },
  609. {
  610. id: '1-3',
  611. label: 'node-1-3',
  612. },
  613. ],
  614. },
  615. {
  616. id: '2',
  617. label: 'node-2',
  618. },
  619. ],
  620. defaultExpandedKeys: ['1'],
  621. }
  622. },
  623. })
  624. await nextTick()
  625. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  626. const node = nodes[1].element.querySelector(
  627. TREE_NODE_CONTENT_CLASS_NAME
  628. ) as any
  629. expect(node.style.paddingLeft).toBe('20px')
  630. })
  631. test('expandOnClickNode', async () => {
  632. const onNodeExpand = vi.fn()
  633. const { wrapper } = createTree({
  634. data() {
  635. return {
  636. expandOnClickNode: false,
  637. }
  638. },
  639. methods: {
  640. onNodeExpand,
  641. },
  642. })
  643. await nextTick()
  644. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  645. await nodes[0].trigger('click')
  646. expect(onNodeExpand).not.toHaveBeenCalled()
  647. await nodes[0].find(TREE_NODE_EXPAND_ICON_CLASS_NAME).trigger('click')
  648. expect(onNodeExpand).toHaveBeenCalled()
  649. })
  650. test('checkOnClickNode', async () => {
  651. const { wrapper, treeRef } = createTree({
  652. data() {
  653. return {
  654. showCheckbox: true,
  655. expandOnClickNode: false,
  656. checkOnClickNode: true,
  657. checkStrictly: true,
  658. }
  659. },
  660. })
  661. await nextTick()
  662. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  663. await nodes[0].trigger('click')
  664. expect(treeRef.getCheckedKeys().toString()).toBe([1].toString())
  665. })
  666. test('currentNodeKey', async () => {
  667. const { wrapper } = createTree({
  668. data() {
  669. return {
  670. currentNodeKey: '2',
  671. data: [
  672. {
  673. id: '1',
  674. label: 'node-1',
  675. children: [
  676. {
  677. id: '1-1',
  678. label: 'node-1-1',
  679. children: [
  680. {
  681. id: '1-1-1',
  682. label: 'node-1-1-1',
  683. },
  684. {
  685. id: '1-1-2',
  686. label: 'node-1-1-2',
  687. },
  688. ],
  689. },
  690. {
  691. id: '1-2',
  692. label: 'node-1-2',
  693. children: [
  694. {
  695. id: '1-2-1',
  696. label: 'node-1-2-1',
  697. },
  698. ],
  699. },
  700. {
  701. id: '1-3',
  702. label: 'node-1-3',
  703. },
  704. ],
  705. },
  706. {
  707. id: '2',
  708. label: 'node-2',
  709. },
  710. ],
  711. }
  712. },
  713. })
  714. await nextTick()
  715. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  716. expect(nodes[1].classes()).toContain('is-current')
  717. })
  718. test('custom node content', async () => {
  719. const { wrapper } = createTree({
  720. slots: {
  721. default: `<div class="custom-tree-node-content">cc {{node.label}}</div>`,
  722. },
  723. })
  724. await nextTick()
  725. expect(wrapper.find('.custom-tree-node-content').text()).toBe('cc node-1')
  726. })
  727. test('filter', async () => {
  728. const { treeRef, wrapper } = createTree({
  729. data() {
  730. return {
  731. currentNodeKey: '2',
  732. data: [
  733. {
  734. id: '1',
  735. label: 'node-1',
  736. children: [
  737. {
  738. id: '1-1',
  739. label: 'node-1-1',
  740. children: [
  741. {
  742. id: '1-1-1',
  743. label: 'node-1-1-1',
  744. },
  745. {
  746. id: '1-1-2',
  747. label: 'node-1-1-2',
  748. },
  749. ],
  750. },
  751. {
  752. id: '1-2',
  753. label: 'node-1-2',
  754. children: [
  755. {
  756. id: '1-2-1',
  757. label: 'node-1-2-1',
  758. },
  759. ],
  760. },
  761. {
  762. id: '1-3',
  763. label: 'node-1-3',
  764. },
  765. ],
  766. },
  767. {
  768. id: '2',
  769. label: 'node-2',
  770. },
  771. ],
  772. filterMethod(query: string, node: TreeNodeData) {
  773. return node.label.includes(query)
  774. },
  775. }
  776. },
  777. })
  778. await nextTick()
  779. treeRef.filter('node-1-1-1')
  780. await nextTick()
  781. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  782. expect(nodes.map((node) => node.text()).toString()).toBe(
  783. ['node-1', 'node-1-1', 'node-1-1-1'].toString()
  784. )
  785. })
  786. describe('events', () => {
  787. test('current-change', async () => {
  788. const onCurrentChange = vi.fn()
  789. const { wrapper, vm, treeVm } = createTree({
  790. methods: {
  791. onCurrentChange,
  792. },
  793. })
  794. await nextTick()
  795. await wrapper.find(TREE_NODE_CLASS_NAME).trigger('click')
  796. expect(onCurrentChange).toHaveBeenCalledTimes(1)
  797. expect(onCurrentChange).toHaveBeenCalledWith(
  798. vm.data[0],
  799. treeVm.flattenTree[0]
  800. )
  801. })
  802. test('check', async () => {
  803. const onNodeCheck = vi.fn()
  804. const { wrapper } = createTree({
  805. data() {
  806. return {
  807. showCheckbox: true,
  808. defaultExpandedKeys: ['1-1', '1'],
  809. data: [
  810. {
  811. id: '1',
  812. label: 'node-1',
  813. children: [
  814. {
  815. id: '1-1',
  816. label: 'node-1-1',
  817. children: [
  818. {
  819. id: '1-1-1',
  820. label: 'node-1-1-1',
  821. },
  822. {
  823. id: '1-1-2',
  824. label: 'node-1-1-2',
  825. },
  826. ],
  827. },
  828. {
  829. id: '1-2',
  830. label: 'node-1-2',
  831. children: [
  832. {
  833. id: '1-2-1',
  834. label: 'node-1-2-1',
  835. },
  836. ],
  837. },
  838. {
  839. id: '1-3',
  840. label: 'node-1-3',
  841. },
  842. ],
  843. },
  844. {
  845. id: '2',
  846. label: 'node-2',
  847. },
  848. ],
  849. }
  850. },
  851. methods: {
  852. onNodeCheck,
  853. },
  854. })
  855. await nextTick()
  856. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  857. await nodes[2].find('.el-checkbox').trigger('click')
  858. expect(onNodeCheck).toHaveBeenCalledTimes(1)
  859. expect(onNodeCheck).toHaveBeenCalledWith(
  860. { id: '1-1-1', label: 'node-1-1-1' },
  861. {
  862. checkedKeys: ['1-1-1'],
  863. checkedNodes: [{ id: '1-1-1', label: 'node-1-1-1' }],
  864. halfCheckedKeys: ['1-1', '1'],
  865. halfCheckedNodes: [
  866. {
  867. children: [
  868. { id: '1-1-1', label: 'node-1-1-1' },
  869. { id: '1-1-2', label: 'node-1-1-2' },
  870. ],
  871. id: '1-1',
  872. label: 'node-1-1',
  873. },
  874. {
  875. children: [
  876. {
  877. children: [
  878. { id: '1-1-1', label: 'node-1-1-1' },
  879. { id: '1-1-2', label: 'node-1-1-2' },
  880. ],
  881. id: '1-1',
  882. label: 'node-1-1',
  883. },
  884. {
  885. children: [{ id: '1-2-1', label: 'node-1-2-1' }],
  886. id: '1-2',
  887. label: 'node-1-2',
  888. },
  889. { id: '1-3', label: 'node-1-3' },
  890. ],
  891. id: '1',
  892. label: 'node-1',
  893. },
  894. ],
  895. }
  896. )
  897. })
  898. test('context-menu', async () => {
  899. const onNodeContextMenu = vi.fn()
  900. const { wrapper } = createTree({
  901. methods: {
  902. onNodeContextMenu,
  903. },
  904. })
  905. await nextTick()
  906. await wrapper.find(TREE_NODE_CLASS_NAME).trigger('contextmenu')
  907. expect(onNodeContextMenu).toHaveBeenCalledTimes(1)
  908. })
  909. })
  910. describe('methods', () => {
  911. test('getChecked', async () => {
  912. const { treeRef } = createTree({
  913. data() {
  914. return {
  915. showCheckbox: true,
  916. defaultCheckedKeys: ['1-1-2'],
  917. data: [
  918. {
  919. id: '1',
  920. label: 'node-1',
  921. children: [
  922. {
  923. id: '1-1',
  924. label: 'node-1-1',
  925. children: [
  926. {
  927. id: '1-1-1',
  928. label: 'node-1-1-1',
  929. },
  930. {
  931. id: '1-1-2',
  932. label: 'node-1-1-2',
  933. },
  934. ],
  935. },
  936. {
  937. id: '1-2',
  938. label: 'node-1-2',
  939. children: [
  940. {
  941. id: '1-2-1',
  942. label: 'node-1-2-1',
  943. },
  944. ],
  945. },
  946. {
  947. id: '1-3',
  948. label: 'node-1-3',
  949. },
  950. ],
  951. },
  952. {
  953. id: '2',
  954. label: 'node-2',
  955. },
  956. ],
  957. }
  958. },
  959. })
  960. await nextTick()
  961. const checkedKeys = treeRef.getCheckedKeys()
  962. const checkedNodes = treeRef.getCheckedNodes()
  963. const halfCheckedKeys = treeRef.getHalfCheckedKeys()
  964. const halfCheckedNodes = treeRef.getHalfCheckedNodes()
  965. expect(checkedKeys.toString()).toBe(['1-1-2'].toString())
  966. expect(checkedNodes.map((node) => node.id).toString()).toBe(
  967. ['1-1-2'].toString()
  968. )
  969. expect(halfCheckedKeys.toString()).toBe(['1-1', '1'].toString())
  970. expect(halfCheckedNodes.map((node) => node.id).toString()).toBe(
  971. ['1-1', '1'].toString()
  972. )
  973. })
  974. test('setCheckedKeys', async () => {
  975. const { treeRef } = createTree({
  976. data() {
  977. return {
  978. showCheckbox: true,
  979. data: [
  980. {
  981. id: '1',
  982. label: 'node-1',
  983. children: [
  984. {
  985. id: '1-1',
  986. label: 'node-1-1',
  987. children: [
  988. {
  989. id: '1-1-1',
  990. label: 'node-1-1-1',
  991. },
  992. {
  993. id: '1-1-2',
  994. label: 'node-1-1-2',
  995. },
  996. ],
  997. },
  998. {
  999. id: '1-2',
  1000. label: 'node-1-2',
  1001. children: [
  1002. {
  1003. id: '1-2-1',
  1004. label: 'node-1-2-1',
  1005. },
  1006. ],
  1007. },
  1008. {
  1009. id: '1-3',
  1010. label: 'node-1-3',
  1011. },
  1012. ],
  1013. },
  1014. {
  1015. id: '2',
  1016. label: 'node-2',
  1017. },
  1018. ],
  1019. }
  1020. },
  1021. })
  1022. await nextTick()
  1023. treeRef.setCheckedKeys(['1-1'])
  1024. await nextTick()
  1025. const checkedKeys = treeRef.getCheckedKeys()
  1026. const halfCheckedKeys = treeRef.getHalfCheckedKeys()
  1027. expect(checkedKeys.toString()).toBe(['1-1', '1-1-1', '1-1-2'].toString())
  1028. expect(halfCheckedKeys.toString()).toBe(['1'].toString())
  1029. })
  1030. test('setChecked', async () => {
  1031. const { treeRef } = createTree({
  1032. data() {
  1033. return {
  1034. showCheckbox: true,
  1035. data: [
  1036. {
  1037. id: '1',
  1038. label: 'node-1',
  1039. children: [
  1040. {
  1041. id: '1-1',
  1042. label: 'node-1-1',
  1043. children: [
  1044. {
  1045. id: '1-1-1',
  1046. label: 'node-1-1-1',
  1047. },
  1048. {
  1049. id: '1-1-2',
  1050. label: 'node-1-1-2',
  1051. },
  1052. ],
  1053. },
  1054. {
  1055. id: '1-2',
  1056. label: 'node-1-2',
  1057. children: [
  1058. {
  1059. id: '1-2-1',
  1060. label: 'node-1-2-1',
  1061. },
  1062. ],
  1063. },
  1064. {
  1065. id: '1-3',
  1066. label: 'node-1-3',
  1067. },
  1068. ],
  1069. },
  1070. {
  1071. id: '2',
  1072. label: 'node-2',
  1073. },
  1074. ],
  1075. }
  1076. },
  1077. })
  1078. await nextTick()
  1079. treeRef.setChecked('1-1', true)
  1080. const checkedKeys = treeRef.getCheckedKeys()
  1081. const halfCheckedKeys = treeRef.getHalfCheckedKeys()
  1082. expect(checkedKeys.toString()).toBe(['1-1', '1-1-1', '1-1-2'].toString())
  1083. expect(halfCheckedKeys.toString()).toBe(['1'].toString())
  1084. })
  1085. test('getCurrent', async () => {
  1086. const { treeRef, wrapper } = createTree({
  1087. data() {
  1088. return {
  1089. defaultExpandedKeys: ['1', '1-1'],
  1090. data: [
  1091. {
  1092. id: '1',
  1093. label: 'node-1',
  1094. children: [
  1095. {
  1096. id: '1-1',
  1097. label: 'node-1-1',
  1098. children: [
  1099. {
  1100. id: '1-1-1',
  1101. label: 'node-1-1-1',
  1102. },
  1103. {
  1104. id: '1-1-2',
  1105. label: 'node-1-1-2',
  1106. },
  1107. ],
  1108. },
  1109. {
  1110. id: '1-2',
  1111. label: 'node-1-2',
  1112. children: [
  1113. {
  1114. id: '1-2-1',
  1115. label: 'node-1-2-1',
  1116. },
  1117. ],
  1118. },
  1119. {
  1120. id: '1-3',
  1121. label: 'node-1-3',
  1122. },
  1123. ],
  1124. },
  1125. {
  1126. id: '2',
  1127. label: 'node-2',
  1128. },
  1129. ],
  1130. }
  1131. },
  1132. })
  1133. await nextTick()
  1134. const nodes = wrapper.findAll(TREE_NODE_CLASS_NAME)
  1135. await nodes[2].trigger('click')
  1136. expect(treeRef.getCurrentNode()).toMatchObject({
  1137. id: '1-1-1',
  1138. label: 'node-1-1-1',
  1139. })
  1140. expect(treeRef.getCurrentKey()).toBe('1-1-1')
  1141. treeRef.setCurrentKey('1-1-2')
  1142. expect(treeRef.getCurrentNode()).toMatchObject({
  1143. id: '1-1-2',
  1144. label: 'node-1-1-2',
  1145. })
  1146. expect(treeRef.getCurrentKey()).toBe('1-1-2')
  1147. })
  1148. })
  1149. })