datePicker.test.js 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067
  1. import React from 'react';
  2. import DatePicker from '../index';
  3. import BaseDatePicker from '../datePicker';
  4. import ConfigProvider from '../../configProvider';
  5. import * as _ from 'lodash';
  6. import { clear } from 'jest-date-mock';
  7. import { addDays, startOfWeek, endOfWeek, add, format, addWeeks, set } from 'date-fns';
  8. import { zhCN, enUS } from "date-fns/locale";
  9. import { zonedTimeToUtc } from 'date-fns-tz';
  10. import { strings } from '../../../semi-foundation/datePicker/constants';
  11. import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
  12. import en_US from '../../locale/source/en_US';
  13. import LocaleProvider from '../../locale/localeProvider';
  14. import { genBeforeEach, genAfterEach, mount, sleep as baseSleep } from '../../_test_/utils';
  15. import { utcToZonedTime, toIANA } from '../../../semi-foundation/utils/date-fns-extra';
  16. const animationMs = 200;
  17. const baseYear = 2019;
  18. const baseDay = 8;
  19. const baseMon = 8;
  20. const baseDate = new Date(baseYear, baseMon, baseDay, 8, 8, 8, 8);
  21. const popupSelector = `.${BASE_CLASS_PREFIX}-popover .${BASE_CLASS_PREFIX}-datepicker`;
  22. const sleep = (ms = 200) => baseSleep(ms);
  23. const getRandomIANATimeZone = function () {
  24. const TIME_ZONE = Array.from({ length: 26 }).map((_, index) => index - 11);
  25. const offset = Math.floor(Math.random() * TIME_ZONE.length);
  26. const timeZone = toIANA(TIME_ZONE[offset]);
  27. return [timeZone, offset];
  28. };
  29. describe(`DatePicker`, () => {
  30. beforeEach(() => {
  31. clear();
  32. genBeforeEach()();
  33. });
  34. afterEach(genAfterEach());
  35. it(`test appearance`, () => {
  36. const defaultValue = new Date();
  37. /**
  38. * with default value
  39. */
  40. const elem = mount(<DatePicker defaultValue={defaultValue} />);
  41. expect(elem.find(`.${BASE_CLASS_PREFIX}-datepicker`).length).toBe(1);
  42. expect(elem.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input-wrapper-clearable`).length).toBe(1);
  43. });
  44. it(`test defaultOpen`, async () => {
  45. const defaultValue = new Date();
  46. const open = true;
  47. const motion = false;
  48. const elem = mount(<DatePicker motion={motion} defaultOpen={open} defaultValue={defaultValue} />);
  49. await sleep();
  50. expect(document.querySelectorAll(popupSelector).length).toBe(1);
  51. // document.body.click();
  52. document.dispatchEvent(new Event('mousedown', { bubbles: true }));
  53. await sleep();
  54. expect(document.querySelectorAll(popupSelector).length).toBe(0);
  55. });
  56. it(`test open`, async () => {
  57. const defaultValue = new Date();
  58. const open = true;
  59. const motion = false;
  60. const elem = mount(<DatePicker motion={motion} open={open} defaultValue={defaultValue} />);
  61. expect(document.querySelectorAll(popupSelector).length).toBe(1);
  62. /**
  63. * click body without reset open
  64. */
  65. document.body.click();
  66. await sleep();
  67. expect(document.querySelectorAll(popupSelector).length).toBe(1);
  68. /**
  69. * click body and set open
  70. */
  71. elem.setProps({ open: false });
  72. document.body.click();
  73. await sleep();
  74. expect(document.querySelectorAll(popupSelector).length).toBe(0);
  75. });
  76. it(`test presets`, async () => {
  77. const dayOffset = 1;
  78. const presets = [
  79. {
  80. text: 'Today',
  81. start: new Date(baseDate),
  82. end: new Date(baseDate),
  83. },
  84. {
  85. text: 'Next Day',
  86. start: addDays(new Date(baseDate), dayOffset),
  87. end: addDays(new Date(baseDate), dayOffset),
  88. },
  89. ];
  90. const defaultValue = new Date(addDays(new Date(baseDate), -dayOffset));
  91. const open = true;
  92. const motion = false;
  93. const demo = mount(<DatePicker presets={presets} motion={motion} open={open} defaultValue={defaultValue} />);
  94. const elem = demo.find(BaseDatePicker);
  95. const btns = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-quick-control button`);
  96. /**
  97. * click next day
  98. */
  99. btns[1].click();
  100. let value = elem.state('value');
  101. expect(value[0].getDate() - defaultValue.getDate()).toEqual(dayOffset * 2);
  102. /**
  103. * click current day
  104. */
  105. btns[0].click();
  106. value = elem.state('value');
  107. expect(value[0].getDate()).toEqual(defaultValue.getDate() + 1);
  108. });
  109. it(`test value`, async () => {
  110. const currentValue = new Date(baseDate);
  111. const open = true;
  112. const motion = false;
  113. const dayOffset = 3;
  114. const onChange = sinon.spy(async (date, str) => {
  115. expect(date.getDate()).toBe(currentValue.getDate() + dayOffset);
  116. elem.setProps({ value: date });
  117. await sleep();
  118. expect(_.first(datePickerElem.state('value')).getDate() - baseDate.getDate()).toBe(dayOffset);
  119. });
  120. const elem = mount(<DatePicker motion={motion} open={open} value={currentValue} onChange={onChange} />);
  121. const datePickerElem = elem.find(BaseDatePicker);
  122. const popup = document.querySelector(`.${BASE_CLASS_PREFIX}-popover .${BASE_CLASS_PREFIX}-datepicker`);
  123. const selectedDayElem = popup.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-day-selected`);
  124. const nextOffsetDayElem = _.times(dayOffset).reduce(node => node.nextElementSibling, selectedDayElem);
  125. nextOffsetDayElem.click();
  126. await sleep(animationMs * 3);
  127. expect(onChange.called).toBeTruthy();
  128. });
  129. it(`test needConfirm`, async () => {
  130. const currentValue = new Date(baseDate);
  131. const open = true;
  132. const motion = false;
  133. const type = 'dateTime';
  134. const needConfirm = true;
  135. const dayOffset = 3;
  136. const demo = mount(
  137. <DatePicker motion={motion} defaultValue={currentValue} open={open} type={type} needConfirm={needConfirm} />
  138. );
  139. const elem = demo.find(BaseDatePicker);
  140. const btns = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-popover .${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-datepicker-footer .${BASE_CLASS_PREFIX}-button`);
  141. expect(btns.length).toBe(2);
  142. const selectedDayElem = document.querySelector(`.${BASE_CLASS_PREFIX}-popover .${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-datepicker-day-selected`);
  143. const nextOffsetDayElem = _.times(dayOffset).reduce(node => node.nextElementSibling, selectedDayElem);
  144. /**
  145. * click next day
  146. */
  147. nextOffsetDayElem.click();
  148. await sleep();
  149. expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBeTruthy();
  150. /**
  151. * click cancel button
  152. */
  153. btns[0].click();
  154. await sleep();
  155. expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBeTruthy();
  156. expect(_.isEqual(elem.state('cachedSelectedValue'), [currentValue])).toBe(true);
  157. /**
  158. * click ensure button
  159. */
  160. btns[1].click();
  161. await sleep();
  162. expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBe(true);
  163. /**
  164. * re click next day
  165. */
  166. nextOffsetDayElem.click();
  167. await sleep();
  168. expect(_.first(elem.state('value')).getDate() === currentValue.getDate()).toBeTruthy();
  169. /**
  170. * re click ensure button
  171. */
  172. btns[1].click();
  173. await sleep();
  174. expect(_.first(elem.state('value')).getDate() - currentValue.getDate()).toBe(dayOffset);
  175. demo.unmount();
  176. });
  177. it(`test events`, async () => {
  178. const currentValue = new Date(baseDate);
  179. const open = true;
  180. const motion = false;
  181. const type = 'dateTime';
  182. const needConfirm = true;
  183. const dayOffset = 3;
  184. const onOpenChange = sinon.spy();
  185. const onChange = sinon.spy();
  186. const elem = mount(
  187. <DatePicker
  188. onOpenChange={onOpenChange}
  189. motion={motion}
  190. defaultValue={currentValue}
  191. type={type}
  192. needConfirm={needConfirm}
  193. onChange={onChange}
  194. />
  195. );
  196. /**
  197. * click outside
  198. */
  199. document.body.click();
  200. await sleep();
  201. expect(onOpenChange.called).toBeFalsy();
  202. /**
  203. * click datePicker
  204. */
  205. const inputWrapper = document.querySelector(`.${BASE_CLASS_PREFIX}-input-wrapper`);
  206. const dateInputDom = inputWrapper.parentElement;
  207. dateInputDom.click();
  208. await sleep();
  209. expect(onOpenChange.called).toBeTruthy();
  210. /**
  211. * input value change
  212. */
  213. elem.find(`.${BASE_CLASS_PREFIX}-input-wrapper input`).simulate('change', { target: { value: '2019-10-02 08:30:02' } });
  214. expect(onChange.called).toBeTruthy();
  215. });
  216. it(`test range picker`, async () => {
  217. const open = true;
  218. const motion = false;
  219. const type = 'dateTimeRange';
  220. const needConfirm = false;
  221. const dayOffset = 3;
  222. const leftPrevClickTimes = 3;
  223. const currentValue = [new Date(baseDate), new Date(baseDate).setDate(baseDay + dayOffset)];
  224. const demo = mount(
  225. <DatePicker
  226. motion={motion}
  227. defaultOpen={open}
  228. defaultValue={currentValue}
  229. type={type}
  230. needConfirm={needConfirm}
  231. />
  232. );
  233. const elem = demo.find(BaseDatePicker);
  234. const startDayDom = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-day-selected-start`);
  235. const endDayDom = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-day-selected-end`);
  236. /**
  237. * check started day and ended day's gap offset
  238. */
  239. expect(_.times(dayOffset).reduce(cur => cur.nextElementSibling, startDayDom)).toBe(endDayDom);
  240. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  241. const leftNavBtns = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-navigation .${BASE_CLASS_PREFIX}-button`);
  242. const rightPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-right`);
  243. const rightNavBtns = rightPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-navigation .${BASE_CLASS_PREFIX}-button`);
  244. // 点击右边面板下一月
  245. _.get(rightNavBtns, 2).click();
  246. await sleep();
  247. // 点击左边面板上一月
  248. _.times(leftPrevClickTimes).forEach(() => _.get(leftNavBtns, 1).click());
  249. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  250. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  251. const startIndex = 0;
  252. /**
  253. * select 2019-06-02 ~ 2019-06-05
  254. */
  255. demo.find('input').at(0).simulate('focus');
  256. leftSecondWeekDays[startIndex].click();
  257. demo.find('input').at(1).simulate('focus');
  258. leftSecondWeekDays[startIndex + dayOffset].click();
  259. const value = elem.state('value');
  260. const startDay = 2;
  261. expect(value[0].getMonth()).toBe(baseMon - leftPrevClickTimes);
  262. expect(value[0].getDate()).toBe(startDay);
  263. expect(value[1].getMonth()).toBe(baseMon - leftPrevClickTimes);
  264. expect(value[1].getDate()).toBe(startDay + dayOffset);
  265. });
  266. it(`test change panel in range picker`, async () => {
  267. const motion = false;
  268. const type = 'dateRange';
  269. const needConfirm = false;
  270. const dayOffset = 3;
  271. const currentValue = [new Date(baseDate), new Date(baseDate).setDate(baseDay + dayOffset)];
  272. const demo = mount(
  273. <DatePicker
  274. motion={motion}
  275. defaultOpen={open}
  276. defaultValue={currentValue}
  277. type={type}
  278. needConfirm={needConfirm}
  279. />
  280. );
  281. const elem = demo.find(BaseDatePicker);
  282. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  283. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  284. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  285. const startIndex = 0;
  286. demo.find('input').at(0).simulate('focus');
  287. leftSecondWeekDays[startIndex].click();
  288. await sleep();
  289. expect(elem.state('rangeInputFocus')).toBe('rangeEnd');
  290. expect(elem.instance().focusRecordsRef.current.rangeStart).toBe(true);
  291. leftSecondWeekDays[startIndex + dayOffset].click();
  292. await sleep();
  293. expect(elem.instance().focusRecordsRef.current.rangeStart).toBe(false);
  294. expect(elem.instance().focusRecordsRef.current.rangeEnd).toBe(false);
  295. expect(elem.state('rangeInputFocus')).toBe(false);
  296. });
  297. // github workflow 过不了,本地可以,先跳过
  298. it.skip(`test change panel in range picker with start greater than endTime`, async () => {
  299. const motion = false;
  300. const type = 'dateRange';
  301. const needConfirm = false;
  302. const dayOffset = 3;
  303. const currentValue = [new Date(baseDate), new Date(baseDate).setDate(baseDay + dayOffset)];
  304. const demo = mount(
  305. <DatePicker
  306. motion={motion}
  307. defaultOpen={open}
  308. defaultValue={currentValue}
  309. type={type}
  310. needConfirm={needConfirm}
  311. />
  312. );
  313. const elem = demo.find(BaseDatePicker);
  314. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  315. const leftThirdWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[2];
  316. const leftThirdWeekDays = leftThirdWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  317. const startIndex = 0;
  318. demo.find('input').at(0).simulate('focus');
  319. leftThirdWeekDays[startIndex].click();
  320. await sleep(600);
  321. expect(elem.state('rangeInputFocus')).toBe('rangeEnd');
  322. const inputValue = elem.state('inputValue');
  323. expect(inputValue.split('~')[1].trim()).toBe('');
  324. });
  325. /**
  326. * this test suite won't end up with result
  327. */
  328. it.skip(`test year or month picker`, async () => {
  329. const open = true;
  330. const motion = false;
  331. const type = 'month';
  332. const monOffset = 2;
  333. const yearOffset = 3;
  334. const currentValue = new Date(baseDate);
  335. const elem = mount(<DatePicker motion={motion} defaultOpen={open} defaultValue={currentValue} type={type} />);
  336. await sleep();
  337. const lists = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-scrolllist-item-wheel`);
  338. /**
  339. * select year
  340. */
  341. const currentSelectedYear = lists[0].querySelector(`ul .${BASE_CLASS_PREFIX}-scrolllist-item-selected`);
  342. _.times(yearOffset)
  343. .reduce(cur => cur.nextElementSibling, currentSelectedYear)
  344. .click();
  345. /**
  346. * select month
  347. */
  348. const currentSelectedMon = lists[1].querySelector(`ul .${BASE_CLASS_PREFIX}-scrolllist-item-selected`);
  349. _.times(monOffset)
  350. .reduce(cur => cur.nextElementSibling, currentSelectedMon)
  351. .click();
  352. await sleep();
  353. const value = elem.state('value');
  354. expect(value[0].getYear()).toBe(baseYear + yearOffset);
  355. expect(value[0].getMonth()).toBe(baseMon + monOffset);
  356. });
  357. it('test week select', async () => {
  358. const demo = mount(
  359. <DatePicker
  360. type="dateRange"
  361. defaultOpen={open}
  362. weekStartsOn={1}
  363. startDateOffset={date => startOfWeek(date, { weekStartsOn: 1 })}
  364. endDateOffset={date => endOfWeek(date, { weekStartsOn: 1 })}
  365. />
  366. );
  367. const elem = demo.find(BaseDatePicker);
  368. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  369. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  370. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  371. const startIndex = 3;
  372. /**
  373. * 点击当前月第二个星期的第四天
  374. */
  375. leftSecondWeekDays[startIndex].click();
  376. const now = new Date();
  377. const year = now.getFullYear();
  378. const month = now.getMonth();
  379. const monthFirstDay = add(new Date(year, 0, 1, 0, 0, 0), { months: month });
  380. const clickDay = addDays(startOfWeek(addWeeks(monthFirstDay, 1), { weekStartsOn: 1 }), 3);
  381. const value = elem.state('value');
  382. const dateFormat = 'yyyy-MM-dd';
  383. expect(format(value[0], dateFormat)).toBe(format(startOfWeek(clickDay, { weekStartsOn: 1 }), dateFormat));
  384. expect(format(value[1], dateFormat)).toBe(format(endOfWeek(clickDay, { weekStartsOn: 1 }), dateFormat));
  385. });
  386. it('test autoFocus', async () => {
  387. const motion = false;
  388. const elem = mount(<DatePicker motion={motion} autoFocus={true} />);
  389. const elem2 = mount(<DatePicker motion={motion} autoFocus={false} />);
  390. expect(elem.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input-wrapper-focus`).length).toBe(1);
  391. expect(elem2.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input-wrapper-focus`).length).toBe(0);
  392. });
  393. it('custom dropdownClassName & dropdownStyle', async () => {
  394. let props = {
  395. dropdownClassName: 'my-datePicker',
  396. dropdownStyle: {
  397. color: 'red',
  398. },
  399. defaultOpen: true,
  400. motion: false,
  401. };
  402. const elem = mount(<DatePicker {...props} />);
  403. expect(elem.exists('.my-datePicker')).toEqual(true);
  404. expect(elem.find('.my-datePicker')).toHaveStyle('color', 'red');
  405. });
  406. it('onClear', async () => {
  407. const onClear = sinon.spy();
  408. let props = {
  409. defaultOpen: true,
  410. motion: false,
  411. autoFocus: true,
  412. defaultValue: baseDate,
  413. showClear: true,
  414. onClear: onClear,
  415. };
  416. const elem = mount(<DatePicker {...props} />);
  417. const clearBtn = elem.find('.semi-input-clearbtn');
  418. clearBtn.simulate('mouseDown', { target: { value: 'test' } });
  419. expect(onClear.called).toBeTruthy();
  420. });
  421. it('input disabled date should not trigger onChange', async () => {
  422. const onChange = sinon.spy();
  423. const defaultValue = '2021-04-12';
  424. const disabeldDate = '2021-04-15';
  425. const notDisabledDate = '2021-04-13';
  426. let props = {
  427. defaultOpen: true,
  428. motion: false,
  429. value: defaultValue,
  430. onChange,
  431. disabledDate: dateStr => {
  432. const date = new Date(dateStr);
  433. const day = date.getDate();
  434. if (day === 15) {
  435. return true;
  436. }
  437. return false;
  438. }
  439. };
  440. const elem = mount(<DatePicker {...props} />);
  441. elem.find(`.${BASE_CLASS_PREFIX}-input-wrapper input`).simulate('change', { target: { value: disabeldDate } });
  442. await sleep();
  443. expect(onChange.called).toBeFalsy();
  444. elem.find(`.${BASE_CLASS_PREFIX}-input-wrapper input`).simulate('change', { target: { value: notDisabledDate } });
  445. await sleep();
  446. expect(onChange.called).toBeTruthy();
  447. });
  448. it('click presets disabled date should not trigger onChange', async () => {
  449. const onChange = sinon.spy();
  450. const defaultValue = '2021-04-12';
  451. const disabledValue = '2021-04-15';
  452. const notDisabledValue = '2021-04-30';
  453. const defaultDate = new Date(`${defaultValue} 00:00:00`);
  454. const disableDate = new Date(`${disabledValue} 00:00:00`);
  455. const notDisabledDate = new Date(`${notDisabledValue} 00:00:00`);
  456. let props = {
  457. open: true,
  458. motion: false,
  459. defaultValue,
  460. onChange,
  461. disabledDate: date => {
  462. const day = date.getDate();
  463. if (day === 15) {
  464. return true;
  465. }
  466. return false;
  467. },
  468. presets: [
  469. {
  470. text: 'disabled date',
  471. start: disableDate,
  472. },
  473. {
  474. text: 'valid date',
  475. start: notDisabledValue,
  476. },
  477. ],
  478. };
  479. const demo = mount(<DatePicker {...props} />);
  480. const elem = demo.find(BaseDatePicker);
  481. const btns = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-quick-control button`);
  482. // click disabled date
  483. btns[0].click();
  484. let value = elem.state('value');
  485. expect(value[0].getDate()).toEqual(defaultDate.getDate());
  486. expect(onChange.called).toBeFalsy();
  487. // click valid date
  488. btns[1].click();
  489. await sleep();
  490. value = elem.state('value');
  491. expect(value[0].getDate()).toEqual(notDisabledDate.getDate());
  492. expect(onChange.called).toBeTruthy();
  493. });
  494. it('check inputValue is correct when change timeZone', async () => {
  495. const today = set(new Date(), { hours: 22, minutes: 0, seconds: 0 });
  496. const [originZone, originOffset] = getRandomIANATimeZone();
  497. const [newZone, newOffset] = getRandomIANATimeZone();
  498. const elem = mount(
  499. <ConfigProvider timeZone={originZone}>
  500. <DatePicker type="dateTime" defaultOpen={true} motion={false} defaultPickerValue={today} />
  501. </ConfigProvider>
  502. );
  503. const demo = elem.find(BaseDatePicker);
  504. // 选中一个日期
  505. const days = document.querySelectorAll('.semi-datepicker-day');
  506. // 6 无实际意义,第一行的第7个肯定是有效日期,如第一行最后一天是月首
  507. days[6].click();
  508. await sleep();
  509. // 查看value值
  510. expect(elem.find('.semi-datepicker-day-selected')).toBeTruthy();
  511. const selectedDay = demo.state('value')[0];
  512. const input = document.querySelector('.semi-input');
  513. expect(input.value).toEqual(format(selectedDay, strings.FORMAT_DATE_TIME));
  514. // 切换时区
  515. elem.setProps({ timeZone: newZone });
  516. const newZoneDate = add(selectedDay, { hours: newOffset - originOffset })
  517. const formatNewZoneDate = format(newZoneDate, strings.FORMAT_DATE_TIME);
  518. expect(input.value).toEqual(formatNewZoneDate);
  519. });
  520. it('check inputValue in controlled mode when change timeZone', async () => {
  521. const now = new Date();
  522. const [originZone, originOffset] = getRandomIANATimeZone();
  523. const [newZone, newOffset] = getRandomIANATimeZone();
  524. // 给定一个时区下的date value
  525. const originZoneDate = zonedTimeToUtc(now, originZone);
  526. const elem = mount(
  527. <ConfigProvider timeZone={originZone}>
  528. <DatePicker type="dateTime" defaultOpen={true} motion={false} value={originZoneDate} />
  529. </ConfigProvider>
  530. );
  531. const input = document.querySelector('.semi-input');
  532. const originFormatDate = format(now, strings.FORMAT_DATE_TIME);
  533. expect(input.value).toEqual(originFormatDate);
  534. // 切换时区
  535. elem.setProps({ timeZone: newZone });
  536. const newZoneDate = add(now, { hours: newOffset - originOffset })
  537. const formatNewZoneDate = format(newZoneDate, strings.FORMAT_DATE_TIME);
  538. expect(input.value).toEqual(formatNewZoneDate);
  539. });
  540. it(`test locale format default`, () => {
  541. const localeFormatten = 'yyyy-MM-dd EEEE';
  542. const defaultValue = new Date('2021-04-30');
  543. const localeValue = format(defaultValue, localeFormatten, { locale: zhCN })
  544. // 默认为中文
  545. const elem = mount(<DatePicker format={localeFormatten} defaultValue={defaultValue} />);
  546. expect(elem.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input`).instance().value).toBe(localeValue);
  547. });
  548. it(`test locale format enUS`, () => {
  549. const localeFormatten = 'yyyy-MM-dd EEEE';
  550. const defaultValue = new Date('2021-04-30');
  551. const localeValue = format(defaultValue, localeFormatten, { locale: enUS })
  552. // 英文
  553. const elem = mount(
  554. <LocaleProvider locale={en_US}>
  555. <DatePicker format={localeFormatten} defaultValue={defaultValue} />
  556. </LocaleProvider>
  557. );
  558. expect(elem.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input`).instance().value).toBe(localeValue);
  559. });
  560. it(`test onPresetClick`, async () => {
  561. const dayOffset = 1;
  562. const presets = [
  563. {
  564. text: 'Today',
  565. start: new Date(baseDate),
  566. end: new Date(baseDate),
  567. },
  568. {
  569. text: 'Next Day',
  570. start: addDays(new Date(baseDate), dayOffset),
  571. end: addDays(new Date(baseDate), dayOffset),
  572. },
  573. ];
  574. const defaultValue = new Date(addDays(new Date(baseDate), -dayOffset));
  575. const open = true;
  576. const motion = false;
  577. const handlePresetClick = sinon.spy();
  578. const demo = mount(<DatePicker onPresetClick={handlePresetClick} presets={presets} motion={motion} open={open} defaultValue={defaultValue} />);
  579. const elem = demo.find(BaseDatePicker);
  580. const btns = document.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-quick-control button`);
  581. btns[0].click();
  582. btns[1].click();
  583. expect(handlePresetClick.calledTwice).toBeTruthy();
  584. const args0 = handlePresetClick.getCall(0).args;
  585. const args1 = handlePresetClick.getCall(1).args;
  586. expect(args0[0]).toEqual(presets[0]);
  587. expect(args0[1] instanceof Event).toBeTruthy;
  588. expect(args1[0]).toEqual(presets[1]);
  589. expect(args1[1] instanceof Event).toBeTruthy;
  590. });
  591. it(`test range type click one not trigger notifyChange`, async () => {
  592. const onChange = sinon.spy(async (date, str) => {
  593. elem.setProps({ value: date });
  594. });
  595. let props = {
  596. defaultOpen: true,
  597. motion: false,
  598. value: undefined,
  599. onChange,
  600. defaultPickerValue: '2021-08-13',
  601. type: 'dateRange'
  602. };
  603. const elem = mount(<DatePicker {...props} />);
  604. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  605. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  606. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  607. const startIndex = 0;
  608. const endIndex = 2;
  609. elem.find('input').at(0).simulate('focus');
  610. leftSecondWeekDays[startIndex].click();
  611. expect(onChange.calledOnce).toBe(false);
  612. elem.find('input').at(1).simulate('focus');
  613. leftSecondWeekDays[endIndex].click();
  614. expect(onChange.calledOnce).toBe(true);
  615. const [rangeStart, rangeEnd] = onChange.getCall(0).args[0];
  616. const dateFormat = 'yyyy-MM-dd';
  617. expect(format(rangeStart, dateFormat)).toBe('2021-08-08');
  618. expect(format(rangeEnd, dateFormat)).toBe('2021-08-10');
  619. const inputs = elem.find(`.${BASE_CLASS_PREFIX}-datepicker .${BASE_CLASS_PREFIX}-input`);
  620. expect(inputs.at(0).instance().value).toBe('2021-08-08');
  621. expect(inputs.at(1).instance().value).toBe('2021-08-10');
  622. });
  623. /**
  624. * test disabled rangeStart and select a not disabled range end
  625. * e.g.
  626. * You can select a no disabled date(like one day of september) when defaultValue=['2021-08-06', '2021-08-15'] and disabled august.
  627. */
  628. it('test rangeStart disabled and select rangeEnd', async () => {
  629. const onChange = sinon.spy();
  630. const defaultValue = ['2021-08-06', '2021-08-15'];
  631. let props = {
  632. type: 'dateRange',
  633. defaultOpen: true,
  634. motion: false,
  635. defaultValue,
  636. onChange,
  637. // disabled august
  638. disabledDate: dateStr => {
  639. const date = new Date(dateStr);
  640. const month = date.getMonth();
  641. if (month === 7) {
  642. return true;
  643. }
  644. return false;
  645. },
  646. style: { width: 300 }
  647. };
  648. const elem = mount(<DatePicker {...props} />);
  649. const baseElem = elem.find(BaseDatePicker);
  650. const rightPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-right`);
  651. const rightSecondWeek = rightPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  652. const rightSecondWeekDays = rightSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  653. /**
  654. * select 2021-09-10 as rangeEnd
  655. */
  656. elem.find('input').at(1).simulate('focus');
  657. const endIndex = 5; // 2021-09-10
  658. rightSecondWeekDays[endIndex].click();
  659. const value = baseElem.state('value');
  660. // test rangeEnd is selected
  661. expect(value[0].getMonth()).toBe(7);
  662. expect(value[0].getDate()).toBe(6);
  663. expect(value[1].getMonth()).toBe(8);
  664. expect(value[1].getDate()).toBe(10);
  665. // test input value is same with state value
  666. expect(elem.find('input').at(0).instance().value).toBe(defaultValue[0]);
  667. expect(elem.find('input').at(1).instance().value).toBe('2021-09-10');
  668. // test event is called
  669. expect(onChange.calledOnce).toBe(true);
  670. });
  671. /**
  672. * test disabled some day and select a no disabled day in multiple mode
  673. */
  674. it('test disabled multiple select', async () => {
  675. const onChange = sinon.spy();
  676. const defaultValue = ['2021-08-06', '2021-08-15'];
  677. let props = {
  678. type: 'date',
  679. multiple: true,
  680. defaultOpen: true,
  681. motion: false,
  682. defaultValue,
  683. onChange,
  684. // disabled august
  685. disabledDate: dateStr => {
  686. const date = new Date(dateStr);
  687. const day = date.getDate();
  688. if (day > 20 && day < 25) {
  689. return true;
  690. }
  691. return false;
  692. },
  693. style: { width: 300 }
  694. };
  695. const elem = mount(<DatePicker {...props} />);
  696. const baseElem = elem.find(BaseDatePicker);
  697. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  698. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  699. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  700. /**
  701. * select 2021-08-10(not disabled)
  702. */
  703. leftSecondWeekDays[2].click();
  704. let value = baseElem.state('value');
  705. // test 2021-08-10 is selected
  706. expect(value.length).toBe(3);
  707. expect(value[2].getMonth()).toBe(7);
  708. expect(value[2].getDate()).toBe(10);
  709. // test input value is same with state value
  710. expect(elem.find('input').at(0).instance().value).toBe('2021-08-06,2021-08-15,2021-08-10');
  711. // test event is called
  712. expect(onChange.calledOnce).toBe(true);
  713. /**
  714. * select 2021-08-21(disabled)
  715. */
  716. const leftThirdWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[2];
  717. const leftThirdWeekDays = leftThirdWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  718. leftThirdWeekDays[6].click();
  719. await sleep();
  720. value = baseElem.state('value');
  721. // test 2021-08-21 is not selected
  722. expect(value.length).toBe(3);
  723. expect(elem.find('input').at(0).instance().value).toBe('2021-08-06,2021-08-15,2021-08-10');
  724. expect(onChange.calledOnce).toBe(true); // still calledOnce
  725. });
  726. it('test disabled time callback', async () => {
  727. const disabledTime = sinon.spy((date, panelType) => {
  728. if (panelType === 'left') {
  729. return { disabledHours: () => [17, 18] };
  730. } else {
  731. return { disabledHours: () => [12, 13, 14, 15, 16, 17, 18] };
  732. }
  733. });
  734. let props = {
  735. type: 'dateTimeRange',
  736. defaultValue: ['2021-09-08', '2021-10-03'],
  737. defaultOpen: true,
  738. motion: false,
  739. disabledTime,
  740. style: { width: 400 },
  741. timePickerOpts: {
  742. scrollItemProps: { cycled: false }
  743. }
  744. };
  745. const elem = mount(<DatePicker {...props} />);
  746. elem.find('.semi-datepicker-month-grid-left .semi-datepicker-switch-time').simulate('click');
  747. const args = disabledTime.lastCall.args;
  748. expect(args[0].length).toBe(2);
  749. expect(args[1]).toBe('left');
  750. elem.setProps({ type: 'dateTime' });
  751. elem.update();
  752. elem.find('.semi-datepicker-month-grid-left .semi-datepicker-switch-time').simulate('click');
  753. const args2 = disabledTime.lastCall.args;
  754. expect(Array.isArray(args2[0])).toBe(false);
  755. expect(args2[1]).toBe('left');
  756. });
  757. it('test rangeSeparator', async () => {
  758. const rangeSeparator = '-'
  759. const defaultValue = ['2021-08-06', '2021-08-15'];
  760. let props = {
  761. type: 'dateRange',
  762. motion: false,
  763. defaultValue,
  764. style: { width: 300 },
  765. rangeSeparator,
  766. };
  767. const elem = mount(
  768. <div>
  769. <DatePicker {...props} />
  770. <DatePicker {...props} type="dateTimeRange" />
  771. </div>
  772. );
  773. const allSeparators = document.querySelectorAll('.semi-datepicker-range-input-separator');
  774. expect(allSeparators[0].textContent.trim()).toBe(rangeSeparator);
  775. expect(allSeparators[1].textContent.trim()).toBe(rangeSeparator);
  776. });
  777. /**
  778. * fix https://github.com/DouyinFE/semi-design/issues/422
  779. */
  780. it('test input year length larger than 4', async () => {
  781. const props = {
  782. motion: false,
  783. defaultOpen: true,
  784. defaultValue: '2021-12-21',
  785. };
  786. const handleChange = sinon.spy();
  787. const elem = mount(
  788. <DatePicker {...props} onChange={handleChange} />
  789. );
  790. elem.find('input').simulate('change', { target: { value: '20221-12-21' }});
  791. expect(handleChange.called).toBeFalsy();
  792. });
  793. it('test click next/prev year buttons', () => {
  794. let props = {
  795. type: 'dateRange',
  796. motion: false,
  797. style: { width: 300 },
  798. defaultPickerValue: new Date('2021-12-01'),
  799. defaultOpen: true,
  800. };
  801. const elem = mount(<DatePicker {...props} />);
  802. const leftPanel = document.querySelector(`.semi-datepicker-month-grid-left`);
  803. const leftNavBtns = leftPanel.querySelector(`.semi-datepicker-navigation`).children;
  804. const rightPanel = document.querySelector(`.semi-datepicker-month-grid-right`);
  805. const rightNavBtns = rightPanel.querySelector(`.semi-datepicker-navigation`).children;
  806. // 点击左边面板上一年
  807. _.get(leftNavBtns, 0).click();
  808. expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2020年 12月');
  809. // 点击左边面板下一年
  810. _.get(leftNavBtns, 4).click();
  811. expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 12月');
  812. // 点击右边面板下一年
  813. _.get(rightNavBtns, 4).click();
  814. expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2023年 1月');
  815. // 点击右边面板上一年
  816. _.get(rightNavBtns, 0).click();
  817. expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
  818. });
  819. const testMonthSyncChange = type => {
  820. let props = {
  821. type,
  822. motion: false,
  823. style: { width: 300 },
  824. defaultPickerValue: new Date('2021-12-01'),
  825. defaultOpen: true,
  826. };
  827. const elem = mount(<DatePicker {...props} />);
  828. const leftPanel = document.querySelector(`.semi-datepicker-month-grid-left`);
  829. const leftNavBtns = leftPanel.querySelector(`.semi-datepicker-navigation`).children;
  830. const rightPanel = document.querySelector(`.semi-datepicker-month-grid-right`);
  831. const rightNavBtns = rightPanel.querySelector(`.semi-datepicker-navigation`).children;
  832. // 点击左边面板下一月,自动切换右面板
  833. _.get(leftNavBtns, 3).click();
  834. expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
  835. expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 2月');
  836. // 点击右边面板上一月,自动切换左面板
  837. _.get(rightNavBtns, 1).click();
  838. expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 12月');
  839. expect(document.querySelector(`.semi-datepicker-month-grid-right .semi-datepicker-navigation-month`).textContent).toBe('2022年 1月');
  840. // 点击左边面板上一月,不需要自动切换右面板
  841. _.get(leftNavBtns, 1).click();
  842. expect(document.querySelector(`.semi-datepicker-month-grid-left .semi-datepicker-navigation-month`).textContent).toBe('2021年 11月');
  843. elem.unmount();
  844. }
  845. it('test month sync change dateRange type', () => { testMonthSyncChange('dateRange') });
  846. it('test month sync change dateTimeRange type', () => { testMonthSyncChange('dateTimeRange')});
  847. it(`test preset given null`, async () => {
  848. const props = {
  849. presets: [
  850. {
  851. text: 'Today',
  852. start: null,
  853. end: null,
  854. }
  855. ],
  856. defaultValue: baseDate,
  857. defaultOpen: true,
  858. motion: false,
  859. type: 'dateRange'
  860. }
  861. const handleChange = sinon.spy();
  862. const demo = mount(<DatePicker {...props} onChange={handleChange} />);
  863. const elem = demo.find(BaseDatePicker);
  864. const btns = document.querySelectorAll('.semi-datepicker-quick-control button');
  865. btns[0].click();
  866. expect(handleChange.called).toBeTruthy();
  867. const args = handleChange.getCall(0).args;
  868. expect(args[0].length).toEqual(0);
  869. expect(elem.state('panelShow')).toBeFalsy();
  870. });
  871. it(`test preset given null + needConfirm`, async () => {
  872. const props = {
  873. presets: [
  874. {
  875. text: 'Today',
  876. start: null,
  877. end: null,
  878. }
  879. ],
  880. defaultValue: baseDate,
  881. defaultOpen: true,
  882. motion: false,
  883. type: 'dateTimeRange',
  884. needConfirm: true,
  885. }
  886. const handleChange = sinon.spy();
  887. const handleConfirm = sinon.spy();
  888. const demo = mount(<DatePicker {...props} onChange={handleChange} onConfirm={handleConfirm} />);
  889. const elem = demo.find(BaseDatePicker);
  890. const btns = document.querySelectorAll('.semi-datepicker-quick-control button');
  891. // 点击 preset
  892. btns[0].click();
  893. expect(handleChange.called).toBe(true);
  894. const argsChange = handleChange.getCall(0).args;
  895. expect(argsChange[0].length).toBe(0);
  896. expect(elem.state('panelShow')).toBe(true);
  897. // 点击确定
  898. const footerBtns = document.querySelectorAll('.semi-datepicker-footer .semi-button');
  899. footerBtns[1].click();
  900. expect(handleConfirm.called).toBe(true);
  901. const argsConfirm = handleConfirm.getCall(0).args;
  902. expect(argsConfirm[0].length).toBe(0);
  903. expect(elem.state('panelShow')).toBe(false);
  904. });
  905. it('test dateRange triggerRender', async () => {
  906. const elem = mount(
  907. <DatePicker
  908. motion={false}
  909. // defaultOpen
  910. type="dateRange"
  911. triggerRender={({ placeholder }) => (
  912. <button>
  913. {placeholder}
  914. </button>
  915. )}
  916. />
  917. );
  918. const trigger = document.querySelector('button');
  919. trigger.click();
  920. await sleep();
  921. const leftPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-left`);
  922. const leftSecondWeek = leftPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  923. const leftSecondWeekDays = leftSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  924. const rightPanel = document.querySelector(`.${BASE_CLASS_PREFIX}-datepicker-month-grid-right`);
  925. const rightSecondWeek = rightPanel.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-week`)[1];
  926. const rightSecondWeekDays = rightSecondWeek.querySelectorAll(`.${BASE_CLASS_PREFIX}-datepicker-day`);
  927. leftSecondWeekDays[0].click();
  928. await sleep();
  929. rightSecondWeekDays[0].click();
  930. const baseElem = elem.find(BaseDatePicker);
  931. expect(baseElem.state('panelShow')).toBeFalsy();
  932. });
  933. });