time-picker.test.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. // @ts-nocheck
  2. import { computed, nextTick, ref } from 'vue'
  3. import { mount } from '@vue/test-utils'
  4. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
  5. import dayjs from 'dayjs'
  6. import triggerEvent from '@element-plus/test-utils/trigger-event'
  7. import { rAF } from '@element-plus/test-utils/tick'
  8. import { ElFormItem } from '@element-plus/components/form'
  9. import sleep from '@element-plus/test-utils/sleep'
  10. import TimePicker from '../src/time-picker'
  11. import Picker from '../src/common/picker.vue'
  12. const makeRange = (start, end) => {
  13. const result = []
  14. for (let i = start; i <= end; i++) {
  15. result.push(i)
  16. }
  17. return result
  18. }
  19. const getSpinnerTextAsArray = (dom, selector) => {
  20. return Array.prototype.slice
  21. .call(dom.querySelectorAll(selector))
  22. .map((node) => Number(node.textContent))
  23. }
  24. afterEach(() => {
  25. document.body.innerHTML = ''
  26. })
  27. describe('TimePicker', () => {
  28. it('create & custom class & style', async () => {
  29. const placeholder = ref('test_')
  30. const readonly = ref(true)
  31. const wrapper = mount(() => (
  32. <TimePicker
  33. placeholder={placeholder.value}
  34. readonly={readonly.value}
  35. style="color:red"
  36. class="customClass"
  37. />
  38. ))
  39. const input = wrapper.find('input')
  40. expect(input.attributes('placeholder')).toBe('test_')
  41. expect(input.attributes('readonly')).not.toBeUndefined()
  42. const outterInput = wrapper.find('.el-input')
  43. expect(outterInput.classes()).toContain('customClass')
  44. expect(outterInput.attributes().style).toBeDefined()
  45. })
  46. it('set format && default value && set AM/PM spinner && no $attr to panel', async () => {
  47. const format = ref('hh-mm:ss A')
  48. const value = ref(new Date(2016, 9, 10, 18, 40))
  49. const wrapper = mount(() => (
  50. <TimePicker format={format.value} v-model={value.value} />
  51. ))
  52. await nextTick()
  53. const input = wrapper.find('input')
  54. expect(input.element.value).toBe('06-40:00 PM') // format
  55. input.trigger('blur')
  56. input.trigger('focus')
  57. await nextTick()
  58. const list = document.querySelectorAll('.el-time-spinner__list')
  59. const hoursEl = list[0]
  60. const items = hoursEl.querySelectorAll('.el-time-spinner__item')
  61. expect(items[0].textContent).toBe('12 AM') // am pm
  62. expect(items[1].textContent).toBe('01 AM')
  63. expect(items[12].textContent).toBe('12 PM')
  64. expect(items[15].textContent).toBe('03 PM')
  65. const times = document.querySelectorAll('.el-time-spinner__list .is-active')
  66. expect(times[0].textContent).toBe('06 PM')
  67. expect(times[1].textContent).toBe('40') // default value
  68. expect(times[2].textContent).toBe('00')
  69. const panel = document.querySelector('.el-time-panel') as any
  70. expect(panel.classList.contains('customClass')).toBeFalsy()
  71. })
  72. it('select time', async () => {
  73. const value = ref('')
  74. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  75. const input = wrapper.find('input')
  76. input.trigger('blur')
  77. input.trigger('focus')
  78. await nextTick()
  79. const list = document.querySelectorAll('.el-time-spinner__list')
  80. const hoursEl = list[0]
  81. const minutesEl = list[1]
  82. const secondsEl = list[2]
  83. const hourEl = hoursEl.querySelectorAll('.el-time-spinner__item')[4] as any
  84. const minuteEl = minutesEl.querySelectorAll(
  85. '.el-time-spinner__item'
  86. )[36] as any
  87. const secondEl = secondsEl.querySelectorAll(
  88. '.el-time-spinner__item'
  89. )[20] as any
  90. // click hour, minute, second one at a time.
  91. hourEl.click()
  92. await nextTick()
  93. minuteEl.click()
  94. await nextTick()
  95. secondEl.click()
  96. await nextTick()
  97. const date = value.value
  98. expect(hourEl.classList.contains('is-active')).toBeTruthy()
  99. expect(minuteEl.classList.contains('is-active')).toBeTruthy()
  100. expect(secondEl.classList.contains('is-active')).toBeTruthy()
  101. expect(date.getHours()).toBe(4)
  102. expect(date.getMinutes()).toBe(36)
  103. expect(date.getSeconds()).toBe(20)
  104. })
  105. it('click confirm / cancel button', async () => {
  106. const value = ref('')
  107. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  108. const input = wrapper.find('input')
  109. input.trigger('blur')
  110. input.trigger('focus')
  111. await nextTick()
  112. ;(document.querySelector('.el-time-panel__btn.cancel') as any).click()
  113. expect(value.value).toBe('')
  114. input.trigger('blur')
  115. input.trigger('focus')
  116. await nextTick()
  117. ;(document.querySelector('.el-time-panel__btn.confirm') as any).click()
  118. expect(value.value).toBeInstanceOf(Date)
  119. })
  120. it('should update oldValue when visible change', async () => {
  121. const value = ref(new Date(2016, 9, 10, 18, 40))
  122. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  123. // show picker panel
  124. const input = wrapper.find('input')
  125. input.trigger('blur')
  126. input.trigger('focus')
  127. await nextTick()
  128. // select time
  129. const list = document.querySelectorAll('.el-time-spinner__list')
  130. const hoursEl = list[0]
  131. const minutesEl = list[1]
  132. const secondsEl = list[2]
  133. const hourEl = hoursEl.querySelectorAll('.el-time-spinner__item')[4] as any
  134. const minuteEl = minutesEl.querySelectorAll(
  135. '.el-time-spinner__item'
  136. )[36] as any
  137. const secondEl = secondsEl.querySelectorAll(
  138. '.el-time-spinner__item'
  139. )[20] as any
  140. hourEl.click()
  141. await nextTick()
  142. minuteEl.click()
  143. await nextTick()
  144. secondEl.click()
  145. await nextTick()
  146. // click confirm button
  147. ;(document.querySelector('.el-time-panel__btn.confirm') as any).click()
  148. const date = value.value
  149. expect(date.getHours()).toBe(4)
  150. expect(date.getMinutes()).toBe(36)
  151. expect(date.getSeconds()).toBe(20)
  152. // show picker panel and click cancel button
  153. input.trigger('blur')
  154. input.trigger('focus')
  155. await nextTick()
  156. ;(document.querySelector('.el-time-panel__btn.cancel') as any).click()
  157. expect(date.getHours()).toBe(4)
  158. expect(date.getMinutes()).toBe(36)
  159. expect(date.getSeconds()).toBe(20)
  160. })
  161. it('set format', async () => {
  162. const value = ref('')
  163. const wrapper = mount(() => (
  164. <TimePicker v-model={value.value} format="HH:mm" />
  165. ))
  166. const input = wrapper.find('input')
  167. input.trigger('blur')
  168. input.trigger('focus')
  169. await nextTick()
  170. const spinnerDom = document.querySelectorAll('.el-time-spinner__wrapper')
  171. const minutesDom = spinnerDom[1]
  172. const secondsDom = spinnerDom[2]
  173. expect(minutesDom).not.toBeUndefined()
  174. expect(secondsDom).toBeUndefined()
  175. })
  176. it('event change, focus, blur, keydown', async () => {
  177. const changeHandler = vi.fn()
  178. const focusHandler = vi.fn()
  179. const blurHandler = vi.fn()
  180. const keydownHandler = vi.fn()
  181. const value = ref(new Date(2016, 9, 10, 18, 40))
  182. const wrapper = mount(() => (
  183. <TimePicker
  184. v-model={value.value}
  185. onChange={changeHandler}
  186. onFocus={focusHandler}
  187. onBlur={blurHandler}
  188. onKeydown={keydownHandler}
  189. />
  190. ))
  191. const input = wrapper.find('input')
  192. input.trigger('focus')
  193. await nextTick()
  194. await rAF() // Set selection range causes focus to be retained
  195. input.element.blur()
  196. input.trigger('blur')
  197. await nextTick()
  198. await rAF() // Blur is delayed to ensure focus was not moved to popper
  199. input.trigger('keydown')
  200. await nextTick()
  201. await rAF()
  202. expect(focusHandler).toHaveBeenCalledTimes(1)
  203. expect(blurHandler).toHaveBeenCalledTimes(1)
  204. expect(keydownHandler).toHaveBeenCalledTimes(1)
  205. input.trigger('focus')
  206. await nextTick()
  207. await rAF()
  208. const list = document.querySelectorAll('.el-time-spinner__list')
  209. const hoursEl = list[0]
  210. const hourEl = hoursEl.querySelectorAll('.el-time-spinner__item')[4] as any
  211. hourEl.click()
  212. await nextTick()
  213. expect(changeHandler).toHaveBeenCalledTimes(0)
  214. ;(document.querySelector('.el-time-panel__btn.confirm') as any).click()
  215. await nextTick()
  216. await nextTick() // onchange is triggered by props.modelValue update
  217. expect(changeHandler).toHaveBeenCalledTimes(1)
  218. })
  219. it('selectableRange ', async () => {
  220. // ['17:30:00 - 18:30:00', '18:50:00 - 20:30:00', '21:00:00 - 22:00:00']
  221. const disabledHoursArr = [
  222. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 23,
  223. ]
  224. const disabledHoursData = () => {
  225. return disabledHoursArr
  226. }
  227. const disabledMinutesData = (hour) => {
  228. // ['17:30:00 - 18:30:00', '18:50:00 - 20:30:00', '21:00:00 - 22:00:00']
  229. if (hour === 17) {
  230. return makeRange(0, 29)
  231. }
  232. if (hour === 18) {
  233. return makeRange(31, 49)
  234. }
  235. if (hour === 20) {
  236. return makeRange(31, 59)
  237. }
  238. if (hour === 22) {
  239. return makeRange(1, 59)
  240. }
  241. }
  242. const disabledSeconds = (hour, minute) => {
  243. if (hour === 18 && minute === 30) {
  244. return makeRange(1, 59)
  245. }
  246. if (hour === 20 && minute === 30) {
  247. return makeRange(1, 59)
  248. }
  249. if (hour === 22 && minute === 0) {
  250. return makeRange(1, 59)
  251. }
  252. }
  253. const value = ref('')
  254. const wrapper = mount(() => (
  255. <TimePicker
  256. v-model={value.value}
  257. disabled-hours={disabledHoursData}
  258. disabled-minutes={disabledMinutesData}
  259. disabled-seconds={disabledSeconds}
  260. />
  261. ))
  262. const input = wrapper.find('input')
  263. input.trigger('focus')
  264. await nextTick()
  265. const list = document.querySelectorAll('.el-time-spinner__list')
  266. const hoursEl = list[0]
  267. const minutesEl = list[1]
  268. const secondsEl = list[2]
  269. const disabledHours = getSpinnerTextAsArray(hoursEl, '.is-disabled')
  270. expect(disabledHours).toEqual(disabledHoursArr)
  271. const hourSpinners = hoursEl.querySelectorAll('.el-time-spinner__item')
  272. ;(hourSpinners[18] as any).click()
  273. await nextTick()
  274. const disabledMinutes = getSpinnerTextAsArray(minutesEl, '.is-disabled')
  275. expect(disabledMinutes.every((t) => t > 30 && t < 50)).toBeTruthy()
  276. expect(disabledMinutes.length).toEqual(19)
  277. ;(hourSpinners[22] as any).click()
  278. await nextTick()
  279. const enabledMinutes = getSpinnerTextAsArray(
  280. minutesEl,
  281. ':not(.is-disabled)'
  282. )
  283. const enabledSeconds = getSpinnerTextAsArray(
  284. secondsEl,
  285. ':not(.is-disabled)'
  286. )
  287. expect(enabledMinutes).toEqual([0])
  288. expect(enabledSeconds).toEqual([0])
  289. })
  290. it('ref focus', async () => {
  291. const value = ref(new Date(2016, 9, 10, 18, 40))
  292. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  293. await nextTick()
  294. wrapper.findComponent(TimePicker).vm.$.exposed.focus()
  295. // This one allows mounted to take effect
  296. await nextTick()
  297. // These following two allows popper to gets rendered.
  298. await rAF()
  299. const popperEl = document.querySelector('.el-picker__popper')
  300. const attr = popperEl.getAttribute('aria-hidden')
  301. expect(attr).toEqual('false')
  302. })
  303. it('ref blur', async () => {
  304. const value = ref(new Date(2016, 9, 10, 18, 40))
  305. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  306. const timePickerExposed = wrapper.findComponent(TimePicker).vm.$.exposed
  307. await nextTick()
  308. timePickerExposed.focus()
  309. await nextTick()
  310. timePickerExposed.blur()
  311. await nextTick()
  312. const popperEl = document.querySelector('.el-picker__popper')
  313. const attr = popperEl.getAttribute('aria-hidden')
  314. expect(attr).toEqual('false')
  315. })
  316. it('ref handleOpen', async () => {
  317. const value = ref(new Date(2016, 9, 10, 18, 40))
  318. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  319. const timePickerExposed = wrapper.findComponent(TimePicker).vm.$.exposed
  320. await nextTick()
  321. timePickerExposed.handleOpen()
  322. await nextTick()
  323. const popperEl = document.querySelector('.el-picker__popper')
  324. const attr = popperEl.getAttribute('aria-hidden')
  325. expect(attr).toEqual('false')
  326. })
  327. it('ref handleClose', async () => {
  328. vi.useFakeTimers()
  329. const value = ref(new Date(2016, 9, 10, 18, 40))
  330. const wrapper = mount(() => <TimePicker v-model={value.value} />)
  331. const timePickerExposed = wrapper.findComponent(TimePicker).vm.$.exposed
  332. await nextTick()
  333. timePickerExposed.handleOpen()
  334. await nextTick()
  335. timePickerExposed.handleClose()
  336. await nextTick()
  337. const popperEl = document.querySelector('.el-picker__popper')
  338. const attr = popperEl.getAttribute('aria-hidden')
  339. expect(attr).toEqual('true')
  340. vi.useRealTimers()
  341. })
  342. it('model value should sync when disabled-hours was updated', async () => {
  343. const value = ref('2000-01-01 00:00:00')
  344. const minHour = ref('8')
  345. const disabledHours = computed(() => () => {
  346. return Array.from({ length: 24 })
  347. .fill(null)
  348. .map((_, i) => i)
  349. .filter((h) => h < Number.parseInt(minHour.value, 10))
  350. })
  351. mount(() => (
  352. <TimePicker
  353. v-model={value.value}
  354. disabled-hours={disabledHours.value}
  355. value-format="YYYY-MM-DD HH:mm:ss"
  356. />
  357. ))
  358. await nextTick()
  359. expect(value.value).toEqual('2000-01-01 08:00:00')
  360. minHour.value = '9'
  361. await nextTick()
  362. expect(value.value).toEqual('2000-01-01 09:00:00')
  363. minHour.value = '8'
  364. await nextTick()
  365. expect(value.value).toEqual('2000-01-01 09:00:00')
  366. })
  367. it('picker-panel should not pop up when readonly', async () => {
  368. const wrapper = mount(() => <TimePicker readonly />)
  369. const input = wrapper.find('input')
  370. await input.trigger('mousedown')
  371. await nextTick()
  372. expect((wrapper.findComponent(Picker).vm as any).pickerVisible).toEqual(
  373. false
  374. )
  375. })
  376. it('picker-panel should not pop up when disabled', async () => {
  377. const wrapper = mount(() => <TimePicker readonly />)
  378. const input = wrapper.find('input')
  379. await input.trigger('mousedown')
  380. await nextTick()
  381. expect((wrapper.findComponent(Picker).vm as any).pickerVisible).toEqual(
  382. false
  383. )
  384. })
  385. it('can auto skip when disabled', async () => {
  386. const disabledHours = () => [
  387. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 23,
  388. ]
  389. const value = ref(new Date(2016, 9, 20, 18, 30))
  390. const wrapper = mount(
  391. () => (
  392. <TimePicker
  393. v-model={value.value}
  394. disabled-hours={disabledHours}
  395. arrow-control
  396. />
  397. ),
  398. {
  399. attachTo: document.body,
  400. }
  401. )
  402. const input = wrapper.find('input')
  403. input.trigger('focus')
  404. await nextTick()
  405. const list = document.querySelectorAll('.el-time-spinner__list')
  406. const hoursEl = list[0]
  407. let activeHours = getSpinnerTextAsArray(hoursEl, '.is-active')[0]
  408. expect(activeHours).toEqual(20)
  409. const hoursElWrapperList = document.querySelectorAll(
  410. '.el-time-spinner__wrapper'
  411. )
  412. const hoursElWrapper = hoursElWrapperList[0]
  413. const hoursElArrowDown: Element | null =
  414. hoursElWrapper.querySelector('.arrow-down')
  415. expect(hoursElArrowDown).toBeTruthy()
  416. const mousedownEvt = new MouseEvent('mousedown')
  417. const mouseupEvt = new MouseEvent('mouseup')
  418. const testTime = 130
  419. hoursElArrowDown.dispatchEvent(mousedownEvt)
  420. hoursElArrowDown.dispatchEvent(mouseupEvt)
  421. await sleep(testTime)
  422. activeHours = getSpinnerTextAsArray(hoursEl, '.is-active')[0]
  423. expect(activeHours).toEqual(21)
  424. hoursElArrowDown.dispatchEvent(mousedownEvt)
  425. hoursElArrowDown.dispatchEvent(mouseupEvt)
  426. await sleep(testTime)
  427. activeHours = getSpinnerTextAsArray(hoursEl, '.is-active')[0]
  428. expect(activeHours).toEqual(22)
  429. hoursElArrowDown.dispatchEvent(new MouseEvent('mousedown'))
  430. hoursElArrowDown.dispatchEvent(new MouseEvent('mouseup'))
  431. await sleep(testTime)
  432. activeHours = getSpinnerTextAsArray(hoursEl, '.is-active')[0]
  433. expect(activeHours).toEqual(20)
  434. })
  435. })
  436. describe('TimePicker(range)', () => {
  437. it('create', async () => {
  438. const value = ref([
  439. new Date(2016, 9, 10, 18, 40),
  440. new Date(2016, 9, 10, 19, 40),
  441. ])
  442. const wrapper = mount(
  443. () => <TimePicker v-model={value.value} size="small" is-range={true} />,
  444. {
  445. attachTo: document.body,
  446. }
  447. )
  448. expect(wrapper.find('.el-range-editor--small').exists()).toBeTruthy()
  449. const input = wrapper.find('input')
  450. input.trigger('blur')
  451. input.trigger('focus')
  452. await nextTick()
  453. // For skipping Transition animation
  454. await rAF()
  455. const list = document.querySelectorAll(
  456. '.el-time-spinner__list .el-time-spinner__item.is-active'
  457. )
  458. ;['18', '40', '00', '19', '40', '00'].forEach((_, i) => {
  459. expect(list[i].textContent).toBe(_)
  460. })
  461. })
  462. it('default value', async () => {
  463. const value = ref('')
  464. const defaultValue = ref([
  465. new Date(2000, 9, 1, 10, 20, 0),
  466. new Date(2000, 9, 1, 11, 10, 0),
  467. ])
  468. const wrapper = mount(
  469. () => (
  470. <TimePicker
  471. v-model={value.value}
  472. default-value={defaultValue.value}
  473. is-range={true}
  474. />
  475. ),
  476. {
  477. attachTo: document.body,
  478. }
  479. )
  480. const input = wrapper.find('input')
  481. input.trigger('blur')
  482. input.trigger('focus')
  483. await nextTick()
  484. // For skipping Transition animation
  485. await rAF()
  486. const list = document.querySelectorAll(
  487. '.el-time-spinner__list .el-time-spinner__item.is-active'
  488. )
  489. ;['10', '20', '00', '11', '10', '00'].forEach((_, i) => {
  490. expect(list[i].textContent).toBe(_)
  491. })
  492. })
  493. it('cancel button', async () => {
  494. const cancelDates = [
  495. new Date(2016, 9, 10, 9, 40),
  496. new Date(2016, 9, 10, 15, 40),
  497. ]
  498. const value = ref(cancelDates)
  499. const wrapper = mount(() => <TimePicker v-model={value.value} is-range />, {
  500. attachTo: document.body,
  501. })
  502. const input = wrapper.find('input')
  503. input.trigger('blur')
  504. await nextTick()
  505. input.trigger('focus')
  506. await nextTick()
  507. // For skipping Transition animation
  508. await rAF()
  509. ;(document.querySelector('.el-time-panel__btn.cancel') as any).click()
  510. await rAF()
  511. expect(value.value).toEqual(cancelDates)
  512. expect((wrapper.findComponent(Picker).vm as any).pickerVisible).toEqual(
  513. false
  514. )
  515. expect(document.querySelector('.el-picker-panel')).toBeNull()
  516. input.trigger('blur')
  517. input.trigger('focus')
  518. await nextTick()
  519. ;(document.querySelector('.el-time-panel__btn.confirm') as any).click()
  520. expect(Array.isArray(value.value)).toBeTruthy()
  521. value.value.forEach((v: unknown) => {
  522. expect(v).toBeInstanceOf(Date)
  523. })
  524. })
  525. it('clear button', async () => {
  526. const value = ref([
  527. new Date(2016, 9, 10, 9, 40),
  528. new Date(2016, 9, 10, 15, 40),
  529. ])
  530. const wrapper = mount(() => <TimePicker v-model={value.value} is-range />)
  531. const findInputWrapper = () => wrapper.find('.el-date-editor')
  532. const findClear = () => wrapper.find('.el-range__close-icon')
  533. await nextTick()
  534. const inputWrapper = findInputWrapper()
  535. await inputWrapper.trigger('mouseenter')
  536. await rAF()
  537. const clearIcon = findClear()
  538. await clearIcon.trigger('click')
  539. await nextTick()
  540. expect(value.value).toEqual(null)
  541. })
  542. it('selectableRange ', async () => {
  543. // left ['08:00:00 - 12:59:59'] right ['11:00:00 - 16:59:59']
  544. const value = ref([
  545. new Date(2016, 9, 10, 9, 40),
  546. new Date(2016, 9, 10, 15, 40),
  547. ])
  548. const disabledHours = (role) => {
  549. if (role === 'start') {
  550. return makeRange(0, 7).concat(makeRange(13, 23))
  551. }
  552. return makeRange(0, 10).concat(makeRange(17, 23))
  553. }
  554. const wrapper = mount(() => (
  555. <TimePicker
  556. v-model={value.value}
  557. is-range
  558. disabled-hours={disabledHours}
  559. />
  560. ))
  561. const input = wrapper.find('input')
  562. input.trigger('focus')
  563. await nextTick()
  564. // For skipping Transition animation
  565. await rAF()
  566. const list = document.querySelectorAll('.el-time-spinner__list')
  567. const leftHoursEl = list[0]
  568. const leftEndbledHours = getSpinnerTextAsArray(
  569. leftHoursEl,
  570. ':not(.is-disabled)'
  571. )
  572. expect(leftEndbledHours).toEqual([8, 9, 10, 11, 12])
  573. const rightHoursEl = list[3]
  574. const rightEndbledHours = getSpinnerTextAsArray(
  575. rightHoursEl,
  576. ':not(.is-disabled)'
  577. )
  578. expect(rightEndbledHours).toEqual([11, 12, 13, 14, 15, 16])
  579. ;(leftHoursEl.querySelectorAll('.el-time-spinner__item')[12] as any).click()
  580. await nextTick()
  581. const NextRightEndbledHours = getSpinnerTextAsArray(
  582. rightHoursEl,
  583. ':not(.is-disabled)'
  584. )
  585. expect(NextRightEndbledHours).toEqual([12, 13, 14, 15, 16])
  586. })
  587. it('arrow key', async () => {
  588. const value = ref(new Date(2016, 9, 10, 18, 40))
  589. const wrapper = mount(() => (
  590. <TimePicker v-model={value.value} format="YYYY-MM-DD HH:mm:ss" />
  591. ))
  592. const input = wrapper.find('input')
  593. input.trigger('blur')
  594. input.trigger('focus')
  595. await nextTick()
  596. const initValue = input.element.value
  597. triggerEvent(input.element, 'keydown', 'ArrowDown')
  598. await nextTick()
  599. const addOneHour = input.element.value
  600. triggerEvent(input.element, 'keydown', 'ArrowRight')
  601. await nextTick()
  602. triggerEvent(input.element, 'keydown', 'ArrowDown')
  603. await nextTick()
  604. const addOneHourOneMinute = input.element.value
  605. expect(dayjs(initValue).diff(addOneHour, 'minute')).toEqual(-60)
  606. expect(dayjs(initValue).diff(addOneHourOneMinute, 'minute')).toEqual(-61)
  607. })
  608. it('should be able to inherit options from parent injection', async () => {
  609. const ElPopperOptions = {
  610. strategy: 'fixed',
  611. }
  612. const value = ref(new Date(2016, 9, 10, 18, 40))
  613. const options = ref(ElPopperOptions)
  614. const wrapper = mount(
  615. () => (
  616. <TimePicker
  617. v-model={value.value}
  618. format="YYYY-MM-DD HH:mm:ss"
  619. popper-options={options.value}
  620. />
  621. ),
  622. {
  623. global: {
  624. provide() {
  625. return {
  626. ElPopperOptions,
  627. }
  628. },
  629. },
  630. }
  631. )
  632. await nextTick()
  633. expect((wrapper.findComponent(Picker).vm as any).elPopperOptions).toEqual(
  634. ElPopperOptions
  635. )
  636. })
  637. it('am/pm mode avoid render redundant content', async () => {
  638. const timeRange = ref([])
  639. const wrapper = mount(
  640. () => (
  641. <TimePicker
  642. v-model={timeRange.value}
  643. is-range
  644. range-separator="To"
  645. start-placeholder="Start time"
  646. end-placeholder="End time"
  647. arrow-control
  648. format="hh:mm:ss a"
  649. />
  650. ),
  651. {
  652. attachTo: document.body,
  653. }
  654. )
  655. const input = wrapper.find('input')
  656. input.trigger('blur')
  657. input.trigger('focus')
  658. await nextTick()
  659. // For skipping Transition animation
  660. await rAF()
  661. const list = document.querySelectorAll('.el-time-spinner__list')
  662. expect(
  663. list[0]
  664. .querySelector('.el-time-spinner__item.is-active')
  665. .innerHTML.split(' ').length
  666. ).toBe(2)
  667. expect(
  668. list[1]
  669. .querySelector('.el-time-spinner__item.is-active')
  670. .innerHTML.split(' ').length
  671. ).toBe(1)
  672. expect(
  673. list[2]
  674. .querySelector('.el-time-spinner__item.is-active')
  675. .innerHTML.split(' ').length
  676. ).toBe(1)
  677. })
  678. describe('form item accessibility integration', () => {
  679. it('automatic id attachment', async () => {
  680. const wrapper = mount(() => (
  681. <ElFormItem label="Foobar" data-test-ref="item">
  682. <TimePicker />
  683. </ElFormItem>
  684. ))
  685. await nextTick()
  686. const formItem = wrapper.find('[data-test-ref="item"]')
  687. const formItemLabel = formItem.find('.el-form-item__label')
  688. const timePickerInput = wrapper.find('.el-input__inner')
  689. expect(formItem.attributes().role).toBeFalsy()
  690. expect(formItemLabel.attributes().for).toBe(
  691. timePickerInput.attributes().id
  692. )
  693. })
  694. it('specified id attachment', async () => {
  695. const wrapper = mount(() => (
  696. <ElFormItem label="Foobar" data-test-ref="item">
  697. <TimePicker id="foobar" />
  698. </ElFormItem>
  699. ))
  700. await nextTick()
  701. const formItem = wrapper.find('[data-test-ref="item"]')
  702. const formItemLabel = formItem.find('.el-form-item__label')
  703. const timePickerInput = wrapper.find('.el-input__inner')
  704. expect(formItem.attributes().role).toBeFalsy()
  705. expect(timePickerInput.attributes().id).toBe('foobar')
  706. expect(formItemLabel.attributes().for).toBe(
  707. timePickerInput.attributes().id
  708. )
  709. })
  710. it('form item role is group when multiple inputs', async () => {
  711. const wrapper = mount(() => (
  712. <ElFormItem label="Foobar" data-test-ref="item">
  713. <TimePicker />
  714. <TimePicker />
  715. </ElFormItem>
  716. ))
  717. await nextTick()
  718. const formItem = wrapper.find('[data-test-ref="item"]')
  719. expect(formItem.attributes().role).toBe('group')
  720. })
  721. })
  722. describe('dismiss events restore picker', () => {
  723. let wrapper: ReturnType<typeof mount>
  724. const findInput = () =>
  725. wrapper.findComponent({
  726. name: 'ElInput',
  727. })
  728. const findClear = () => wrapper.find('.clear-icon')
  729. const findPicker = () =>
  730. wrapper.findComponent({
  731. name: 'Picker',
  732. })
  733. beforeEach(() => {
  734. const value = ref(new Date(2016, 9, 10, 18, 40))
  735. wrapper = mount(() => <TimePicker v-model={value.value} ref="input" />, {
  736. attachTo: document.body,
  737. })
  738. })
  739. afterEach(() => {
  740. wrapper.unmount()
  741. })
  742. it('should be able to focus back and callout picker after clear', async () => {
  743. await nextTick()
  744. const input = findInput()
  745. await input.trigger('mouseenter')
  746. await rAF()
  747. const clearIcon = findClear()
  748. await clearIcon.trigger('click')
  749. await rAF()
  750. expect(document.activeElement).toBe(wrapper.find('input').element)
  751. expect(document.querySelector('.el-time-panel')).toBeFalsy()
  752. await input.vm.$emit('input', 'a')
  753. await rAF()
  754. expect(document.querySelector('.el-time-panel')).toBeTruthy()
  755. })
  756. it('should be able to focus back and callout picker after pick', async () => {
  757. await nextTick()
  758. const picker = findPicker()
  759. const input = findInput()
  760. input.vm.$emit('input', 'a')
  761. await rAF()
  762. expect(document.querySelector('.el-time-panel')).toBeTruthy()
  763. picker.vm.onPick('', false)
  764. await rAF() // Picker triggers popup close, event propagation
  765. await rAF() // Focus trap recognizes focusout event, and propagation
  766. expect(document.activeElement).toBe(wrapper.find('input').element)
  767. expect(document.querySelector('.el-time-panel')).toBeFalsy()
  768. input.vm.$emit('input', 'a')
  769. await rAF()
  770. expect(document.querySelector('.el-time-panel')).toBeTruthy()
  771. })
  772. })
  773. it('display value', async () => {
  774. const value = ref([undefined, undefined])
  775. const wrapper = mount(() => <TimePicker v-model={value.value} is-range />)
  776. await nextTick()
  777. const [startInput, endInput] = wrapper.findAll('input')
  778. expect(startInput.element.value).toBe('')
  779. expect(endInput.element.value).toBe('')
  780. })
  781. })