tree-select.test.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. import { nextTick, reactive, ref } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { describe, expect, test, vi } from 'vitest'
  4. import TreeSelect from '../src/tree-select.vue'
  5. import type { RenderFunction } from 'vue'
  6. import type { VueWrapper } from '@vue/test-utils'
  7. import type ElSelect from '@element-plus/components/select'
  8. import type ElTree from '@element-plus/components/tree'
  9. const createComponent = ({
  10. slots = {},
  11. props = {},
  12. }: {
  13. slots?: Record<string, any>
  14. props?: typeof TreeSelect['props']
  15. } = {}) => {
  16. const wrapperRef = ref<InstanceType<typeof TreeSelect>>()
  17. const defaultData = ref([
  18. {
  19. value: 1,
  20. label: '一级 1',
  21. children: [
  22. {
  23. value: 11,
  24. label: '二级 1-1',
  25. children: [
  26. {
  27. value: 111,
  28. label: '三级 1-1',
  29. },
  30. ],
  31. },
  32. ],
  33. },
  34. ])
  35. const bindProps = reactive({
  36. modelValue: ref(''),
  37. data: defaultData,
  38. renderAfterExpand: false,
  39. ...props,
  40. })
  41. const wrapper = mount(
  42. {
  43. render() {
  44. return (
  45. <TreeSelect
  46. {...bindProps}
  47. onUpdate:modelValue={(val: string) => (bindProps.modelValue = val)}
  48. ref={(val: InstanceType<typeof TreeSelect>) =>
  49. (wrapperRef.value = val)
  50. }
  51. v-slots={slots}
  52. />
  53. )
  54. },
  55. },
  56. {
  57. attachTo: 'body',
  58. }
  59. )
  60. return {
  61. wrapper,
  62. getWrapperRef: () =>
  63. new Promise<InstanceType<typeof TreeSelect>>((resolve) =>
  64. nextTick(() => resolve(wrapperRef.value!))
  65. ),
  66. select: wrapper.findComponent({ name: 'ElSelect' }) as VueWrapper<
  67. InstanceType<typeof ElSelect>
  68. >,
  69. tree: wrapper.findComponent({ name: 'ElTree' }) as VueWrapper<
  70. InstanceType<typeof ElTree>
  71. >,
  72. }
  73. }
  74. describe('TreeSelect.vue', () => {
  75. test('render test', async () => {
  76. const { wrapper, tree } = createComponent({
  77. props: {
  78. defaultExpandAll: true,
  79. },
  80. })
  81. expect(wrapper.find('.el-tree')).toBeTruthy()
  82. expect(wrapper.find('.el-select')).toBeTruthy()
  83. expect(tree.findAll('.el-tree > .el-tree-node').length).toBe(1)
  84. expect(tree.findAll('.el-tree .el-tree-node').length).toBe(3)
  85. expect(tree.findAll('.el-tree .el-select-dropdown__item').length).toBe(3)
  86. wrapper.findComponent(TreeSelect).vm.data[0].children = []
  87. await nextTick()
  88. expect(tree.findAll('.el-tree .el-tree-node').length).toBe(1)
  89. })
  90. test('modelValue', async () => {
  91. const value = ref(1)
  92. const { getWrapperRef, select, tree } = createComponent({
  93. props: {
  94. modelValue: value,
  95. checkStrictly: true,
  96. showCheckbox: true,
  97. checkOnClickNode: true,
  98. },
  99. })
  100. const wrapperRef = await getWrapperRef()
  101. await nextTick()
  102. expect(select.vm.modelValue).toBe(1)
  103. expect(wrapperRef.getCheckedKeys()).toEqual([1])
  104. value.value = 11
  105. await nextTick(nextTick)
  106. expect(select.vm.modelValue).toBe(11)
  107. expect(wrapperRef.getCheckedKeys()).toEqual([11])
  108. await tree
  109. .findAll('.el-select-dropdown__item')
  110. .slice(-1)[0]
  111. .trigger('click')
  112. await nextTick()
  113. expect(select.vm.modelValue).toBe(111)
  114. expect(wrapperRef.getCheckedKeys()).toEqual([111])
  115. await tree.find('.el-tree-node__content').trigger('click')
  116. await nextTick()
  117. expect(select.vm.modelValue).toBe(1)
  118. expect(wrapperRef.getCheckedKeys()).toEqual([1])
  119. await tree.findAll('.el-checkbox__original')[1].trigger('click')
  120. await nextTick()
  121. expect(select.vm.modelValue).toBe(11)
  122. expect(wrapperRef.getCheckedKeys()).toEqual([11])
  123. })
  124. test('disabled', async () => {
  125. const { wrapper, tree } = createComponent({
  126. props: {
  127. data: [
  128. {
  129. value: '1',
  130. label: '1',
  131. children: [
  132. {
  133. value: '2',
  134. label: '2',
  135. disabled: true,
  136. },
  137. ],
  138. },
  139. ],
  140. showCheckbox: true,
  141. checkStrictly: true,
  142. defaultExpandAll: true,
  143. checkOnClickNode: true,
  144. },
  145. })
  146. await nextTick()
  147. await tree.find('.el-tree-node').trigger('click')
  148. await tree.find('.el-tree-node .el-checkbox.is-disabled').trigger('click')
  149. await tree
  150. .find('.el-tree-node .el-select-dropdown__item.is-disabled')
  151. .trigger('click')
  152. await nextTick()
  153. expect(wrapper.findComponent(TreeSelect).vm.modelValue).toBe('1')
  154. })
  155. test('multiple', async () => {
  156. const value = ref([1])
  157. const { getWrapperRef, select, tree } = createComponent({
  158. props: {
  159. modelValue: value,
  160. checkStrictly: true,
  161. showCheckbox: true,
  162. multiple: true,
  163. checkOnClickNode: true,
  164. },
  165. })
  166. const wrapperRef = await getWrapperRef()
  167. await nextTick()
  168. expect(select.vm.modelValue).toEqual([1])
  169. expect(wrapperRef.getCheckedKeys()).toEqual([1])
  170. value.value = [11]
  171. await nextTick(nextTick)
  172. expect(select.vm.modelValue).toEqual([11])
  173. expect(wrapperRef.getCheckedKeys()).toEqual([11])
  174. await tree
  175. .findAll('.el-select-dropdown__item')
  176. .slice(-1)[0]
  177. .trigger('click')
  178. await nextTick()
  179. expect(select.vm.modelValue).toEqual([11, 111])
  180. expect(wrapperRef.getCheckedKeys()).toEqual([11, 111])
  181. await tree.find('.el-tree-node__content').trigger('click')
  182. await nextTick()
  183. expect(select.vm.modelValue).toEqual([1, 11, 111])
  184. expect(wrapperRef.getCheckedKeys()).toEqual([1, 11, 111])
  185. await tree.findAll('.el-checkbox')[1].trigger('click')
  186. await nextTick()
  187. expect(select.vm.modelValue).toEqual([1, 111])
  188. expect(wrapperRef.getCheckedKeys()).toEqual([1, 111])
  189. })
  190. test('filter', async () => {
  191. const { tree } = createComponent({
  192. props: {
  193. filterable: true,
  194. },
  195. })
  196. tree.vm.filter('一级 1')
  197. await nextTick()
  198. expect(tree.findAll('.el-tree-node:not(.is-hidden)').length).toBe(1)
  199. })
  200. test('props', async () => {
  201. const { wrapper, select, tree } = createComponent({
  202. props: {
  203. data: [
  204. {
  205. id: '1',
  206. name: '1',
  207. childrens: [
  208. {
  209. id: '2',
  210. name: '2',
  211. },
  212. ],
  213. },
  214. ],
  215. props: {
  216. label: 'name',
  217. children: 'childrens',
  218. },
  219. valueKey: 'id',
  220. },
  221. })
  222. await nextTick()
  223. expect(tree.find('.el-select-dropdown__item').text()).toBe('1')
  224. await wrapper.setProps({ modelValue: '2' })
  225. expect(select.vm.selectedLabel).toBe('2')
  226. })
  227. test('slots', async () => {
  228. const { select, tree } = createComponent({
  229. slots: {
  230. default: ({ data }: { data: { label: string } }) => `123${data.label}`,
  231. prefix: () => 'prefix',
  232. },
  233. })
  234. await nextTick()
  235. expect(tree.find('.el-select-dropdown__item').text()).toBe('123一级 1')
  236. expect(select.find('.el-input__prefix-inner').text()).toBe('prefix')
  237. })
  238. test('renderContent', async () => {
  239. const { tree } = createComponent({
  240. props: {
  241. renderContent: (
  242. h: RenderFunction,
  243. { data }: { data: { label: string } }
  244. ) => {
  245. return `123${data.label}`
  246. },
  247. },
  248. })
  249. await nextTick()
  250. expect(tree.find('.el-select-dropdown__item').text()).toBe('123一级 1')
  251. })
  252. test('lazy', async () => {
  253. const { tree } = createComponent({
  254. props: {
  255. data: [
  256. {
  257. value: 1,
  258. label: 1,
  259. },
  260. ],
  261. lazy: true,
  262. load: (node: object, resolve: (p: any) => any[]) => {
  263. resolve([{ value: 2, label: 2, isLeaf: true }])
  264. },
  265. },
  266. })
  267. await nextTick()
  268. await tree.find('.el-tree-node').trigger('click')
  269. await nextTick()
  270. expect(tree.find('.el-tree-node .el-tree-node').text()).toBe('2')
  271. })
  272. test('events', async () => {
  273. const onNodeClick = vi.fn()
  274. const { tree } = createComponent({
  275. props: {
  276. onNodeClick,
  277. },
  278. })
  279. await nextTick()
  280. await tree.find('.el-tree-node').trigger('click')
  281. await nextTick()
  282. expect(onNodeClick).toBeCalled()
  283. })
  284. test('check-strictly showCheckbox click node', async () => {
  285. const { getWrapperRef, select, tree } = createComponent({
  286. props: {
  287. checkStrictly: true,
  288. showCheckbox: true,
  289. multiple: true,
  290. },
  291. })
  292. const wrapperRef = await getWrapperRef()
  293. await tree.findAll('.el-tree-node__content')[0].trigger('click')
  294. await nextTick()
  295. expect(select.vm.modelValue).toEqual([])
  296. expect(wrapperRef.getCheckedKeys()).toEqual([])
  297. await tree
  298. .findAll('.el-tree-node__content .el-checkbox')[0]
  299. .trigger('click')
  300. await nextTick()
  301. expect(select.vm.modelValue).toEqual([1])
  302. expect(wrapperRef.getCheckedKeys()).toEqual([1])
  303. })
  304. test('check-strictly showCheckbox checkOnClickNode click node', async () => {
  305. const { getWrapperRef, select, tree } = createComponent({
  306. props: {
  307. checkStrictly: true,
  308. showCheckbox: true,
  309. multiple: true,
  310. checkOnClickNode: true,
  311. },
  312. })
  313. const wrapperRef = await getWrapperRef()
  314. await tree.findAll('.el-tree-node__content')[0].trigger('click')
  315. await nextTick()
  316. expect(select.vm.modelValue).toEqual([1])
  317. expect(wrapperRef.getCheckedKeys()).toEqual([1])
  318. await tree
  319. .findAll('.el-tree-node__content .el-checkbox')[0]
  320. .trigger('click')
  321. await nextTick()
  322. expect(select.vm.modelValue).toEqual([])
  323. expect(wrapperRef.getCheckedKeys()).toEqual([])
  324. })
  325. test('only show checkbox', async () => {
  326. const { select, tree } = createComponent({
  327. props: {
  328. showCheckbox: true,
  329. },
  330. })
  331. // check child node when folder node checked,
  332. // value.value will be 111
  333. await tree
  334. .find('.el-tree-node__content .el-checkbox__original')
  335. .trigger('click')
  336. await nextTick()
  337. expect(select.vm.modelValue).equal(111)
  338. // unselect when has child checked
  339. await tree
  340. .find('.el-tree-node__content .el-checkbox__original')
  341. .trigger('click')
  342. await nextTick()
  343. expect(select.vm.modelValue).toBe(undefined)
  344. })
  345. test('show checkbox and check on click node', async () => {
  346. const { select, tree } = createComponent({
  347. props: {
  348. showCheckbox: true,
  349. checkOnClickNode: true,
  350. },
  351. })
  352. // check child node when folder node checked,
  353. // value.value will be 111
  354. await tree.findAll('.el-tree-node__content').slice(-1)[0].trigger('click')
  355. await nextTick()
  356. expect(select.vm.modelValue).equal(111)
  357. // unselect when has child checked
  358. await tree.findAll('.el-tree-node__content').slice(-1)[0].trigger('click')
  359. await nextTick()
  360. expect(select.vm.modelValue).toBe(undefined)
  361. })
  362. test('expand selected node`s parent in first time', async () => {
  363. const value = ref(111)
  364. const { tree } = createComponent({
  365. props: {
  366. modelValue: value,
  367. },
  368. })
  369. expect(tree.findAll('.is-expanded[data-key="1"]').length).toBe(1)
  370. expect(tree.findAll('.is-expanded[data-key="11"]').length).toBe(1)
  371. })
  372. test('expand-on-click-node', async () => {
  373. const { wrapper, tree } = createComponent({
  374. props: {
  375. expandOnClickNode: false,
  376. checkOnClickNode: true,
  377. },
  378. })
  379. await tree.findAll('.el-tree-node__content')[0].trigger('click')
  380. expect(
  381. tree.findAll('.el-tree-node__children')[0].attributes('style')
  382. ).toContain('display: none;')
  383. await wrapper.setProps({ expandOnClickNode: true })
  384. await tree.findAll('.el-tree-node__content')[0].trigger('click')
  385. expect(
  386. tree.findAll('.el-tree-node__children')[0].attributes('style')
  387. ).not.toContain('display: none;')
  388. })
  389. test('show correct label when child options are not rendered', async () => {
  390. const modelValue = ref<number>()
  391. const { select } = createComponent({
  392. props: {
  393. modelValue,
  394. renderAfterExpand: true,
  395. },
  396. })
  397. await nextTick()
  398. expect(select.vm.selectedLabel).toBe('')
  399. modelValue.value = 111
  400. await nextTick()
  401. expect(select.vm.selectedLabel).toBe('三级 1-1')
  402. })
  403. test('show correct label when lazy load', async () => {
  404. const modelValue = ref<number>(1)
  405. const cacheData = reactive([{ value: 3, label: '3-label' }])
  406. const { select } = createComponent({
  407. props: {
  408. data: [],
  409. modelValue,
  410. lazy: true,
  411. load: (node: object, resolve: (p: any) => any[]) => {
  412. resolve([{ value: 2, label: '2-label', isLeaf: true }])
  413. },
  414. cacheData,
  415. },
  416. })
  417. // no load & no cache will be default value
  418. await nextTick()
  419. expect(select.vm.selectedLabel).toBe(1)
  420. // no load & has cache will be correct label
  421. modelValue.value = 3
  422. await nextTick()
  423. expect(select.vm.selectedLabel).toBe('3-label')
  424. // no load & set cache lazy will be correct label
  425. modelValue.value = 1
  426. await nextTick()
  427. cacheData.push({
  428. value: 1,
  429. label: '1-label',
  430. })
  431. await nextTick()
  432. expect(select.vm.selectedLabel).toBe('1-label')
  433. })
  434. test('filter-method', async () => {
  435. const modelValue = ref(1)
  436. const data = ref([
  437. { value: 1, label: '1' },
  438. { value: 2, label: '2' },
  439. { value: 3, label: '3' },
  440. ])
  441. const filterMethod = vi.fn((val: string) => {
  442. data.value = [...data.value].filter((item) => item.label.includes(val))
  443. })
  444. const { select, tree } = createComponent({
  445. props: {
  446. modelValue,
  447. data,
  448. filterable: true,
  449. filterMethod,
  450. // trigger cache data
  451. renderAfterExpand: true,
  452. },
  453. })
  454. await nextTick()
  455. expect(tree.vm.data.length).toBe(3)
  456. expect(select.vm.selectedLabel).toBe('1')
  457. const input = select.find('input')
  458. input.element.focus()
  459. await nextTick()
  460. expect(select.vm.selectedLabel).toBe('')
  461. expect(filterMethod).toHaveBeenLastCalledWith('')
  462. select.vm.selectedLabel = '2'
  463. select.vm.debouncedOnInputChange()
  464. // await debounce
  465. await new Promise((resolve) => setTimeout(resolve))
  466. expect(select.vm.selectedLabel).toBe('2')
  467. expect(filterMethod).toHaveBeenLastCalledWith('2')
  468. expect(tree.text()).toBe('2')
  469. })
  470. })