datePicker.test.js 41 KB

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