slider.test.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. import { h, nextTick, onMounted, ref } from 'vue'
  2. import { mount } from '@vue/test-utils'
  3. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
  4. import { EVENT_CODE } from '@element-plus/constants'
  5. import { ElFormItem } from '@element-plus/components/form'
  6. import Slider from '../src/slider.vue'
  7. import type { SliderProps } from '../src/slider'
  8. vi.mock('lodash-unified', async () => {
  9. return {
  10. ...((await vi.importActual('lodash-unified')) as Record<string, any>),
  11. debounce: vi.fn((fn) => {
  12. fn.cancel = vi.fn()
  13. fn.flush = vi.fn()
  14. return fn
  15. }),
  16. }
  17. })
  18. describe('Slider', () => {
  19. beforeEach(() => {
  20. vi.useFakeTimers()
  21. })
  22. afterEach(() => {
  23. vi.useRealTimers()
  24. })
  25. it('create', () => {
  26. const wrapper = mount(Slider)
  27. expect(wrapper.props().modelValue).toBe(0)
  28. })
  29. it('should not exceed min and max', async () => {
  30. const value = ref(60)
  31. mount(() => <Slider v-model={value.value} min={50} />)
  32. await nextTick()
  33. value.value = 40
  34. await nextTick()
  35. expect(value.value).toBe(50)
  36. value.value = 120
  37. await nextTick()
  38. expect(value.value).toBe(100)
  39. })
  40. it('sizes', () => {
  41. const value = ref(0)
  42. const wrapper = mount(() => <Slider v-model={value.value} size="small" />)
  43. expect(wrapper.find('.el-slider--small').exists()).toBe(true)
  44. })
  45. it('show tooltip', () => {
  46. const value = ref(0)
  47. const wrapper = mount(() => <Slider v-model={value.value} />)
  48. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  49. slider.vm.handleMouseEnter()
  50. expect(slider.vm.tooltipVisible).toBeTruthy()
  51. slider.vm.handleMouseLeave()
  52. expect(slider.vm.tooltipVisible).toBeFalsy()
  53. })
  54. it('hide tooltip', () => {
  55. const value = ref(0)
  56. const wrapper = mount({
  57. setup() {
  58. return () => (
  59. <Slider ref="slider" v-model={value.value} show-tooltip={false} />
  60. )
  61. },
  62. })
  63. const tooltip = wrapper.findComponent({ name: 'ElTooltip' }).vm
  64. expect(tooltip.disabled).toBe(true)
  65. })
  66. it('format tooltip', async () => {
  67. const value = ref(0)
  68. const formatTooltip = (val: number) => `$${val}`
  69. const wrapper = mount(() => (
  70. <Slider v-model={value.value} format-tooltip={formatTooltip} />
  71. ))
  72. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  73. await nextTick()
  74. expect(slider.vm.formatValue).toBe('$0')
  75. })
  76. it('placement', async () => {
  77. const TOOLTIP_CLASS = 'custom_tooltip'
  78. const PLACEMENT = 'left'
  79. mount(() => <Slider tooltip-class={TOOLTIP_CLASS} placement={PLACEMENT} />)
  80. await nextTick()
  81. await nextTick() // here
  82. expect(
  83. (document.querySelector(`.${TOOLTIP_CLASS}`) as HTMLElement).dataset
  84. .popperPlacement
  85. ).toBe(PLACEMENT)
  86. })
  87. describe('drag', () => {
  88. it('horizontal', async () => {
  89. vi.useRealTimers()
  90. const value = ref(0)
  91. const wrapper = mount(
  92. () => (
  93. <div style="width: 200px;">
  94. <Slider v-model={value.value} vertical={false} />
  95. </div>
  96. ),
  97. {
  98. attachTo: document.body,
  99. }
  100. )
  101. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  102. vi.spyOn(
  103. wrapper.find('.el-slider__runway').element,
  104. 'clientWidth',
  105. 'get'
  106. ).mockImplementation(() => 200)
  107. slider.trigger('mousedown', { clientX: 0 })
  108. const mousemove = new MouseEvent('mousemove', {
  109. screenX: 100,
  110. screenY: 0,
  111. clientX: 100,
  112. clientY: 0,
  113. })
  114. window.dispatchEvent(mousemove)
  115. const mouseup = new MouseEvent('mouseup', {
  116. screenX: 100,
  117. screenY: 0,
  118. clientX: 100,
  119. clientY: 0,
  120. })
  121. window.dispatchEvent(mouseup)
  122. await nextTick()
  123. expect(value.value === 50).toBeTruthy()
  124. })
  125. it('vertical', async () => {
  126. vi.useRealTimers()
  127. const value = ref(0)
  128. const wrapper = mount(
  129. () => (
  130. <div style="width: 200px;">
  131. <Slider v-model={value.value} vertical={true} />
  132. </div>
  133. ),
  134. {
  135. attachTo: document.body,
  136. }
  137. )
  138. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  139. vi.spyOn(
  140. wrapper.find('.el-slider__runway').element,
  141. 'clientHeight',
  142. 'get'
  143. ).mockImplementation(() => 200)
  144. slider.trigger('mousedown', { clientY: 0 })
  145. const mousemove = new MouseEvent('mousemove', {
  146. screenX: 0,
  147. screenY: -100,
  148. clientX: 0,
  149. clientY: -100,
  150. })
  151. window.dispatchEvent(mousemove)
  152. const mouseup = new MouseEvent('mouseup', {
  153. screenX: 0,
  154. screenY: -100,
  155. clientX: 0,
  156. clientY: -100,
  157. })
  158. window.dispatchEvent(mouseup)
  159. await nextTick()
  160. expect(value.value).toBe(50)
  161. })
  162. })
  163. describe('accessibility', () => {
  164. it('left/right arrows', async () => {
  165. const value = ref(0)
  166. const wrapper = mount(() => <Slider v-model={value.value} />)
  167. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  168. slider.vm.onKeyDown(
  169. new KeyboardEvent('keydown', { key: EVENT_CODE.right })
  170. )
  171. await nextTick()
  172. expect(value.value).toBe(1)
  173. slider.vm.onKeyDown(
  174. new KeyboardEvent('keydown', { key: EVENT_CODE.left })
  175. )
  176. await nextTick()
  177. expect(value.value).toBe(0)
  178. })
  179. it('up/down arrows', async () => {
  180. const value = ref(0.1)
  181. const wrapper = mount(() => <Slider v-model={value.value} />)
  182. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  183. slider.vm.onKeyDown(new KeyboardEvent('keydown', { key: EVENT_CODE.up }))
  184. await nextTick()
  185. expect(value.value).toBe(1)
  186. slider.vm.onKeyDown(
  187. new KeyboardEvent('keydown', { key: EVENT_CODE.down })
  188. )
  189. await nextTick()
  190. expect(value.value).toBe(0)
  191. })
  192. it('page up/down keys', async () => {
  193. const value = ref(-1)
  194. const wrapper = mount(() => (
  195. <Slider v-model={value.value} min={-5} max={10} />
  196. ))
  197. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  198. slider.vm.onKeyDown(
  199. new KeyboardEvent('keydown', { key: EVENT_CODE.pageUp })
  200. )
  201. await nextTick()
  202. expect(value.value).toBe(3)
  203. slider.vm.onKeyDown(
  204. new KeyboardEvent('keydown', { key: EVENT_CODE.pageDown })
  205. )
  206. await nextTick()
  207. expect(value.value).toBe(-1)
  208. })
  209. it('home/end keys', async () => {
  210. const value = ref(0)
  211. const wrapper = mount(() => (
  212. <Slider v-model={value.value} min={-5} max={10} />
  213. ))
  214. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  215. slider.vm.onKeyDown(
  216. new KeyboardEvent('keydown', { key: EVENT_CODE.home })
  217. )
  218. await nextTick()
  219. expect(value.value).toBe(-5)
  220. slider.vm.onKeyDown(new KeyboardEvent('keydown', { key: EVENT_CODE.end }))
  221. await nextTick()
  222. expect(value.value).toBe(10)
  223. })
  224. })
  225. it('step', async () => {
  226. vi.useRealTimers()
  227. const value = ref(0)
  228. const wrapper = mount(
  229. () => (
  230. <div style="width: 200px;">
  231. <Slider v-model={value.value} min={0} max={1} step={0.1} />
  232. </div>
  233. ),
  234. {
  235. attachTo: document.body,
  236. }
  237. )
  238. const mockClientWidth = vi
  239. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  240. .mockImplementation(() => 200)
  241. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  242. await nextTick()
  243. slider.trigger('mousedown', { clientX: 0 })
  244. const mousemove = new MouseEvent('mousemove', {
  245. screenX: 100,
  246. screenY: 0,
  247. clientX: 100,
  248. clientY: 0,
  249. })
  250. window.dispatchEvent(mousemove)
  251. const mouseup = new MouseEvent('mouseup', {
  252. screenX: 100,
  253. screenY: 0,
  254. clientX: 100,
  255. clientY: 0,
  256. })
  257. await nextTick()
  258. window.dispatchEvent(mouseup)
  259. await nextTick()
  260. expect(value.value === 0.5).toBeTruthy()
  261. mockClientWidth.mockRestore()
  262. })
  263. it('click', async () => {
  264. vi.useRealTimers()
  265. const value = ref(0)
  266. const wrapper = mount(() => <Slider v-model={value.value} />)
  267. const mockClientWidth = vi
  268. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  269. .mockImplementation(() => 200)
  270. const slider = wrapper.findComponent({ name: 'ElSlider' })
  271. slider.vm.onSliderClick(new MouseEvent('mousedown', { clientX: 100 }))
  272. await nextTick()
  273. expect(value.value > 0).toBeTruthy()
  274. mockClientWidth.mockRestore()
  275. })
  276. it('change event', async () => {
  277. vi.useRealTimers()
  278. const value = ref(0)
  279. const data = ref<SliderProps['modelValue']>(0)
  280. const onChange = (val: SliderProps['modelValue']) => (data.value = val)
  281. const wrapper = mount(() => (
  282. <div style="width: 200px">
  283. <Slider v-model={value.value} onChange={onChange} />
  284. </div>
  285. ))
  286. const slider = wrapper.findComponent({ name: 'ElSlider' })
  287. const mockRectLeft = vi
  288. .spyOn(
  289. wrapper.find('.el-slider__runway').element,
  290. 'getBoundingClientRect'
  291. )
  292. .mockImplementation(() => {
  293. return {
  294. left: 0,
  295. } as DOMRect
  296. })
  297. const mockClientWidth = vi
  298. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  299. .mockImplementation(() => 200)
  300. expect(data.value).toBe(0)
  301. slider.vm.onSliderClick(new MouseEvent('mousedown', { clientX: 100 }))
  302. await nextTick()
  303. expect(data.value === 50).toBeTruthy()
  304. mockRectLeft.mockRestore()
  305. mockClientWidth.mockRestore()
  306. })
  307. it('input event', async () => {
  308. vi.useRealTimers()
  309. const value = ref(0)
  310. const data = ref<SliderProps['modelValue']>(0)
  311. const onInput = (val: SliderProps['modelValue']) => (data.value = val)
  312. const wrapper = mount(() => (
  313. <div style="width: 200px">
  314. <Slider v-model={value.value} onInput={onInput} />
  315. </div>
  316. ))
  317. const slider = wrapper.findComponent({ name: 'ElSlider' })
  318. const mockRectLeft = vi
  319. .spyOn(
  320. wrapper.find('.el-slider__runway').element,
  321. 'getBoundingClientRect'
  322. )
  323. .mockImplementation(() => {
  324. return {
  325. left: 0,
  326. } as DOMRect
  327. })
  328. const mockClientWidth = vi
  329. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  330. .mockImplementation(() => 200)
  331. await nextTick()
  332. expect(data.value).toBe(0)
  333. slider.vm.onSliderClick(new MouseEvent('mousedown', { clientX: 100 }))
  334. await nextTick()
  335. expect(data.value === 50).toBeTruthy()
  336. mockRectLeft.mockRestore()
  337. mockClientWidth.mockRestore()
  338. })
  339. it('disabled', async () => {
  340. vi.useRealTimers()
  341. const value = ref(0)
  342. const wrapper = mount(() => <Slider v-model={value.value} disabled />)
  343. const mockClientWidth = vi
  344. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  345. .mockImplementation(() => 200)
  346. const slider = wrapper.findComponent({ name: 'ElSliderButton' })
  347. slider.vm.onButtonDown({ clientX: 0 })
  348. const mousemove = new MouseEvent('mousemove', {
  349. screenX: 50,
  350. screenY: 0,
  351. clientX: 50,
  352. clientY: 0,
  353. })
  354. window.dispatchEvent(mousemove)
  355. const mouseup = new MouseEvent('mouseup', {
  356. screenX: 50,
  357. screenY: 0,
  358. clientX: 50,
  359. clientY: 0,
  360. })
  361. window.dispatchEvent(mouseup)
  362. await nextTick()
  363. expect(value.value).toBe(0)
  364. mockClientWidth.mockRestore()
  365. })
  366. it('show input', async () => {
  367. const value = ref(0)
  368. const wrapper = mount(() => <Slider v-model={value.value} show-input />)
  369. const increaseButton = wrapper.find('.el-input-number__increase')
  370. await increaseButton.trigger('mousedown')
  371. vi.advanceTimersByTime(200)
  372. expect(value.value > 0).toBeTruthy()
  373. })
  374. it('show stops', () => {
  375. const wrapper = mount(() => <Slider step={10} show-stops />)
  376. const stops = wrapper.findAll('.el-slider__stop')
  377. expect(stops.length).toBe(9)
  378. })
  379. it('vertical mode', async () => {
  380. vi.useRealTimers()
  381. const value = ref(0)
  382. const wrapper = mount(
  383. () => <Slider height="200px" v-model={value.value} vertical />,
  384. {
  385. attachTo: document.body,
  386. }
  387. )
  388. const mockRectBottom = vi
  389. .spyOn(
  390. wrapper.find('.el-slider__runway').element,
  391. 'getBoundingClientRect'
  392. )
  393. .mockImplementation(() => {
  394. return {
  395. bottom: 200,
  396. } as DOMRect
  397. })
  398. const mockClientHeight = vi
  399. .spyOn(wrapper.find('.el-slider__runway').element, 'clientHeight', 'get')
  400. .mockImplementation(() => 200)
  401. const slider = wrapper.getComponent({ name: 'ElSlider' })
  402. slider.vm.onSliderClick(new MouseEvent('mousedown', { clientX: 100 }))
  403. await nextTick()
  404. expect(value.value > 0).toBeTruthy()
  405. mockRectBottom.mockRestore()
  406. mockClientHeight.mockRestore()
  407. })
  408. it('rerender with min and show-input', async () => {
  409. const value = ref(30)
  410. const show = ref(false)
  411. mount(
  412. {
  413. setup() {
  414. onMounted(() => (show.value = true))
  415. return () =>
  416. show.value && <Slider v-model={value.value} min={10} show-input />
  417. },
  418. },
  419. {
  420. attachTo: document.body,
  421. }
  422. )
  423. await nextTick()
  424. expect(value.value).toEqual(30)
  425. })
  426. describe('range', () => {
  427. it('basic ranged slider', () => {
  428. const value = ref([10, 20])
  429. const wrapper = mount(() => <Slider v-model={value.value} range />)
  430. const sliders = wrapper.findAllComponents({ name: 'ElSliderButton' })
  431. expect(sliders.length).toBe(2)
  432. })
  433. it('should not exceed min and max', async () => {
  434. const value = ref([50, 60])
  435. mount(() => <Slider v-model={value.value} min={50} range />)
  436. await nextTick()
  437. value.value = [40, 60]
  438. await nextTick()
  439. expect(value.value).toStrictEqual([50, 60])
  440. value.value = [50, 120]
  441. await nextTick()
  442. expect(value.value).toStrictEqual([50, 100])
  443. })
  444. it('click', async () => {
  445. vi.useRealTimers()
  446. const value = ref([0, 100])
  447. const wrapper = mount(
  448. () => (
  449. <div style="width: 200px;">
  450. <Slider v-model={value.value} range />
  451. </div>
  452. ),
  453. {
  454. attachTo: document.body,
  455. }
  456. )
  457. const mockRectLeft = vi
  458. .spyOn(
  459. wrapper.find('.el-slider__runway').element,
  460. 'getBoundingClientRect'
  461. )
  462. .mockImplementation(() => {
  463. return {
  464. left: 0,
  465. } as DOMRect
  466. })
  467. const mockClientWidth = vi
  468. .spyOn(wrapper.find('.el-slider__runway').element, 'clientWidth', 'get')
  469. .mockImplementation(() => 200)
  470. const slider = wrapper.getComponent({ name: 'ElSlider' })
  471. slider.vm.onSliderClick(new MouseEvent('mousedown', { clientX: 100 }))
  472. await nextTick()
  473. // Because mock the clientWidth, so the targetValue is 50.
  474. // The behavior of the setPosition method in the useSlider.ts file should be that the value of the second button is 50
  475. expect(value.value[0] === 0).toBeTruthy()
  476. expect(value.value[1] === 50).toBeTruthy()
  477. mockRectLeft.mockRestore()
  478. mockClientWidth.mockRestore()
  479. })
  480. it('responsive to dynamic min and max', async () => {
  481. const min = ref(0)
  482. const max = ref(100)
  483. const value = ref([50, 80])
  484. mount(() => (
  485. <Slider v-model={value.value} min={min.value} max={max.value} range />
  486. ))
  487. await nextTick()
  488. min.value = 60
  489. await nextTick()
  490. expect(value.value).toStrictEqual([60, 80])
  491. min.value = 30
  492. max.value = 40
  493. await nextTick()
  494. expect(value.value).toStrictEqual([40, 40])
  495. })
  496. it('show stops', async () => {
  497. const value = ref([30, 60])
  498. const wrapper = mount(() => (
  499. <Slider v-model={value.value} step={10} range show-stops />
  500. ))
  501. await nextTick()
  502. const stops = wrapper.findAll('.el-slider__stop')
  503. expect(stops.length).toBe(5)
  504. })
  505. it('marks', async () => {
  506. const value = ref([30, 60])
  507. const marksValue = ref({
  508. 0: '0°C',
  509. 8: '8°C',
  510. 37: '37°C',
  511. 50: {
  512. style: {
  513. color: '#f50',
  514. },
  515. label: h('strong', '50°C'),
  516. },
  517. })
  518. const wrapper = mount(() => (
  519. <Slider
  520. v-model={value.value}
  521. min={20}
  522. step={10}
  523. marks={marksValue.value}
  524. range
  525. show-stops
  526. />
  527. ))
  528. await nextTick()
  529. const stops = wrapper.findAll('.el-slider__marks-stop.el-slider__stop')
  530. const marks = wrapper.findAll('.el-slider__marks .el-slider__marks-text')
  531. expect(marks.length).toBe(2)
  532. expect(stops.length).toBe(2)
  533. expect(getComputedStyle(marks[marks.length - 1].element).color).toBe(
  534. 'rgb(255, 85, 0)'
  535. )
  536. })
  537. })
  538. describe('form item accessibility integration', () => {
  539. it('automatic id attachment', async () => {
  540. const wrapper = mount(() => (
  541. <ElFormItem label="Foobar" data-test-ref="item">
  542. <Slider />
  543. </ElFormItem>
  544. ))
  545. await nextTick()
  546. const formItem = wrapper.find('[data-test-ref="item"]')
  547. const formItemLabel = formItem.find('.el-form-item__label')
  548. const sliderButton = wrapper.find('.el-slider__button-wrapper')
  549. expect(formItem.attributes().role).toBeFalsy()
  550. expect(formItemLabel.attributes().for).toBe(sliderButton.attributes().id)
  551. })
  552. it('range with automatic id attachment', async () => {
  553. const wrapper = mount(() => (
  554. <ElFormItem label="Foobar" data-test-ref="item">
  555. <Slider range />
  556. </ElFormItem>
  557. ))
  558. await nextTick()
  559. const formItem = wrapper.find('[data-test-ref="item"]')
  560. const formItemLabel = formItem.find('.el-form-item__label')
  561. const sliderWrapper = wrapper.find('.el-slider')
  562. expect(formItem.attributes().role).toBeFalsy()
  563. expect(formItemLabel.attributes().for).toBe(sliderWrapper.attributes().id)
  564. })
  565. it('specified id attachment', async () => {
  566. const wrapper = mount(() => (
  567. <ElFormItem label="Foobar" data-test-ref="item">
  568. <Slider id="foobar" />
  569. </ElFormItem>
  570. ))
  571. await nextTick()
  572. const formItem = wrapper.find('[data-test-ref="item"]')
  573. const formItemLabel = formItem.find('.el-form-item__label')
  574. const sliderButton = wrapper.find('.el-slider__button-wrapper')
  575. expect(formItem.attributes().role).toBeFalsy()
  576. expect(sliderButton.attributes().id).toBe('foobar')
  577. expect(formItemLabel.attributes().for).toBe(sliderButton.attributes().id)
  578. })
  579. it('range with specified id attachment', async () => {
  580. const wrapper = mount(() => (
  581. <ElFormItem label="Foobar" data-test-ref="item">
  582. <Slider id="foobar" range />
  583. </ElFormItem>
  584. ))
  585. await nextTick()
  586. const formItem = wrapper.find('[data-test-ref="item"]')
  587. const formItemLabel = formItem.find('.el-form-item__label')
  588. const sliderWrapper = wrapper.find('.el-slider')
  589. expect(formItem.attributes().role).toBeFalsy()
  590. expect(sliderWrapper.attributes().id).toBe('foobar')
  591. expect(formItemLabel.attributes().for).toBe(sliderWrapper.attributes().id)
  592. })
  593. it('form item role is group when multiple inputs', async () => {
  594. const wrapper = mount(() => (
  595. <ElFormItem label="Foobar" data-test-ref="item">
  596. <Slider />
  597. <Slider />
  598. </ElFormItem>
  599. ))
  600. await nextTick()
  601. const formItem = wrapper.find('[data-test-ref="item"]')
  602. expect(formItem.attributes().role).toBe('group')
  603. })
  604. })
  605. })