1
0

DateRange.spec.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React from "react";
  14. import { render } from "@testing-library/react";
  15. import userEvent from "@testing-library/user-event";
  16. import "@testing-library/jest-dom";
  17. import { LocalizationProvider } from "@mui/x-date-pickers";
  18. import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFnsV3";
  19. import DateRange from "./DateRange";
  20. import { TaipyContext } from "../../context/taipyContext";
  21. import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
  22. import { getClientServerTimeZoneOffset } from "../../utils";
  23. jest.mock("../../utils", () => {
  24. const originalModule = jest.requireActual("../../utils");
  25. //Mock getClientServerTimeZoneOffset
  26. return {
  27. __esModule: true,
  28. ...originalModule,
  29. getClientServerTimeZoneOffset: () => 0,
  30. };
  31. });
  32. beforeEach(() => {
  33. // add window.matchMedia
  34. // this is necessary for the date picker to be rendered in desktop mode.
  35. // if this is not provided, the mobile mode is rendered, which might lead to unexpected behavior
  36. Object.defineProperty(window, "matchMedia", {
  37. writable: true,
  38. value: (query: string): MediaQueryList => ({
  39. media: query,
  40. // this is the media query that @material-ui/pickers uses to determine if a device is a desktop device
  41. matches: query === "(pointer: fine)",
  42. onchange: () => {},
  43. addEventListener: () => {},
  44. removeEventListener: () => {},
  45. addListener: () => {},
  46. removeListener: () => {},
  47. dispatchEvent: () => false,
  48. }),
  49. });
  50. });
  51. afterEach(() => {
  52. // @ts-ignore
  53. delete window.matchMedia;
  54. });
  55. const curDate = new Date();
  56. curDate.setHours(1, 1, 1, 1);
  57. const curDateStr = curDate.toISOString();
  58. const nextDate = new Date(curDate);
  59. nextDate.setDate(nextDate.getDate() + 1);
  60. const nextDateStr = nextDate.toISOString();
  61. const curDates = [curDateStr, nextDateStr];
  62. const cleanText = (val: string) => val.replace(/\u200e|\u2066|\u2067|\u2068|\u2069/g, "");
  63. const defaultDates = '["2001-01-01T00:00:01.001Z","2001-01-31T00:00:01.001Z"]';
  64. describe("DateRange Component", () => {
  65. it("renders", async () => {
  66. const { getAllByTestId } = render(
  67. <LocalizationProvider dateAdapter={AdapterDateFns}>
  68. <DateRange dates={curDates} />
  69. </LocalizationProvider>
  70. );
  71. const elts = getAllByTestId("CalendarIcon");
  72. expect(elts).toHaveLength(2);
  73. expect(elts[0].parentElement?.tagName).toBe("BUTTON");
  74. });
  75. it("displays the right info for string", async () => {
  76. const { getAllByTestId } = render(
  77. <LocalizationProvider dateAdapter={AdapterDateFns}>
  78. <DateRange dates={curDates} defaultDates={defaultDates} className="taipy-date-2" />
  79. </LocalizationProvider>
  80. );
  81. const elts = getAllByTestId("CalendarIcon");
  82. expect(elts).toHaveLength(2);
  83. expect(elts[0].parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  84. "taipy-date-2-picker",
  85. "taipy-date-2-picker-start"
  86. );
  87. expect(elts[0].parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  88. "taipy-date-2"
  89. );
  90. expect(elts[1].parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  91. "taipy-date-2-picker",
  92. "taipy-date-2-picker-end"
  93. );
  94. expect(elts[1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  95. "taipy-date-2"
  96. );
  97. });
  98. it("displays the default value", async () => {
  99. render(
  100. <LocalizationProvider dateAdapter={AdapterDateFns}>
  101. <DateRange
  102. defaultDates={defaultDates}
  103. dates={undefined as unknown as string[]}
  104. className="taipy-date-range"
  105. />
  106. </LocalizationProvider>
  107. );
  108. const input = document.querySelector(".taipy-date-range-picker-start input") as HTMLInputElement;
  109. expect(input).toBeInTheDocument();
  110. expect(cleanText(input?.value || "")).toEqual("01/01/2001");
  111. const input2 = document.querySelector(".taipy-date-range-picker-end input") as HTMLInputElement;
  112. expect(input2).toBeInTheDocument();
  113. expect(cleanText(input2?.value || "")).toEqual("01/31/2001");
  114. });
  115. it("displays the default value with format", async () => {
  116. render(
  117. <LocalizationProvider dateAdapter={AdapterDateFns}>
  118. <DateRange
  119. defaultDates={defaultDates}
  120. dates={undefined as unknown as string[]}
  121. className="taipy-date-range"
  122. format="dd-MM-yyyy"
  123. />
  124. </LocalizationProvider>
  125. );
  126. const input = document.querySelector(".taipy-date-range-picker-start input") as HTMLInputElement;
  127. expect(input).toBeInTheDocument();
  128. expect(cleanText(input?.value || "")).toEqual("01-01-2001");
  129. const input2 = document.querySelector(".taipy-date-range-picker-end input") as HTMLInputElement;
  130. expect(input2).toBeInTheDocument();
  131. expect(cleanText(input2?.value || "")).toEqual("31-01-2001");
  132. });
  133. it("shows labels", async () => {
  134. const { getByLabelText } = render(
  135. <LocalizationProvider dateAdapter={AdapterDateFns}>
  136. <DateRange
  137. defaultDates={defaultDates}
  138. dates={undefined as unknown as string[]}
  139. className="taipy-date-range"
  140. labelStart="start"
  141. labelEnd="end"
  142. />
  143. </LocalizationProvider>
  144. );
  145. const startInput = getByLabelText("start") as HTMLInputElement;
  146. expect(startInput.value).toBe("01/01/2001");
  147. const endInput = getByLabelText("end") as HTMLInputElement;
  148. expect(endInput.value).toBe("01/31/2001");
  149. });
  150. it("displays with width=70%", async () => {
  151. render(
  152. <LocalizationProvider dateAdapter={AdapterDateFns}>
  153. <DateRange dates={curDates} width="70%" />
  154. </LocalizationProvider>
  155. );
  156. const elt = document.querySelector(".MuiStack-root");
  157. expect(elt).toHaveStyle("width: 70%");
  158. });
  159. it("displays with width=500", async () => {
  160. render(
  161. <LocalizationProvider dateAdapter={AdapterDateFns}>
  162. <DateRange dates={curDates} width={500} />
  163. </LocalizationProvider>
  164. );
  165. const elt = document.querySelector(".MuiStack-root");
  166. expect(elt).toHaveStyle("width: 500px");
  167. });
  168. it("is disabled", async () => {
  169. render(
  170. <LocalizationProvider dateAdapter={AdapterDateFns}>
  171. <DateRange dates={curDates} active={false} className="taipy-date-range" />
  172. </LocalizationProvider>
  173. );
  174. const input = document.querySelector(".taipy-date-range-picker-start input");
  175. expect(input).toBeInTheDocument();
  176. expect(input).toBeDisabled();
  177. const input2 = document.querySelector(".taipy-date-range-picker-end input");
  178. expect(input2).toBeInTheDocument();
  179. expect(input2).toBeDisabled();
  180. });
  181. it("is enabled by default", async () => {
  182. render(
  183. <LocalizationProvider dateAdapter={AdapterDateFns}>
  184. <DateRange dates={curDates} />
  185. </LocalizationProvider>
  186. );
  187. const input = document.querySelector("input");
  188. expect(input).toBeInTheDocument();
  189. expect(input).not.toBeDisabled();
  190. });
  191. it("is enabled by active", async () => {
  192. render(
  193. <LocalizationProvider dateAdapter={AdapterDateFns}>
  194. <DateRange dates={curDates} active={true} />
  195. </LocalizationProvider>
  196. );
  197. const input = document.querySelector("input");
  198. expect(input).toBeInTheDocument();
  199. expect(input).not.toBeDisabled();
  200. });
  201. it("dispatch a well formed message", async () => {
  202. const dispatch = jest.fn();
  203. const state: TaipyState = INITIAL_STATE;
  204. render(
  205. <TaipyContext.Provider value={{ state, dispatch }}>
  206. <LocalizationProvider dateAdapter={AdapterDateFns}>
  207. <DateRange dates={curDates} className="taipy-date-range" />
  208. </LocalizationProvider>
  209. </TaipyContext.Provider>
  210. );
  211. const input = document.querySelector(".taipy-date-range-picker-start input");
  212. expect(input).toBeInTheDocument();
  213. const input2 = document.querySelector(".taipy-date-range-picker-end input");
  214. expect(input2).toBeInTheDocument();
  215. if (input && input2) {
  216. // await userEvent.clear(input);
  217. await userEvent.type(input, "{ArrowLeft}{ArrowLeft}{ArrowLeft}01012001", { delay: 1 });
  218. // await userEvent.clear(input2);
  219. await userEvent.type(input2, "{ArrowLeft}{ArrowLeft}{ArrowLeft}01312001", { delay: 1 });
  220. expect(dispatch).toHaveBeenLastCalledWith({
  221. name: "",
  222. payload: { value: ["Mon Jan 01 2001", "Wed Jan 31 2001"] },
  223. propagate: true,
  224. type: "SEND_UPDATE_ACTION",
  225. });
  226. }
  227. });
  228. });
  229. describe("DateRange with time Component", () => {
  230. it("renders", async () => {
  231. const { getAllByTestId } = render(
  232. <LocalizationProvider dateAdapter={AdapterDateFns}>
  233. <DateRange dates={curDates} withTime={true} />
  234. </LocalizationProvider>
  235. );
  236. const elts = getAllByTestId("CalendarIcon");
  237. expect(elts).toHaveLength(2);
  238. expect(elts[0].parentElement?.tagName).toBe("BUTTON");
  239. });
  240. it("displays the right info for string", async () => {
  241. const { getAllByTestId } = render(
  242. <LocalizationProvider dateAdapter={AdapterDateFns}>
  243. <DateRange dates={curDates} withTime={true} className="taipy-time" />
  244. </LocalizationProvider>
  245. );
  246. const elts = getAllByTestId("CalendarIcon");
  247. expect(elts).toHaveLength(2);
  248. expect(elts[0].parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  249. "taipy-time-picker",
  250. "taipy-time-picker-start"
  251. );
  252. expect(elts[0].parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  253. "taipy-time"
  254. );
  255. expect(elts[1].parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  256. "taipy-time-picker",
  257. "taipy-time-picker-end"
  258. );
  259. expect(elts[1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  260. "taipy-time"
  261. );
  262. });
  263. it("displays the default value", async () => {
  264. render(
  265. <LocalizationProvider dateAdapter={AdapterDateFns}>
  266. <DateRange
  267. defaultDates='["2001-01-01T00:00:01.001Z","2001-01-31T00:00:01.001Z"]'
  268. withTime={true}
  269. dates={undefined as unknown as string[]}
  270. className="tp-dt"
  271. />
  272. </LocalizationProvider>
  273. );
  274. const input = document.querySelector(".tp-dt-picker-start input") as HTMLInputElement;
  275. expect(input).toBeInTheDocument();
  276. expect(cleanText(input?.value || "").toLocaleLowerCase()).toEqual("01/01/2001 12:00 am");
  277. const input2 = document.querySelector(".tp-dt-picker-end input") as HTMLInputElement;
  278. expect(input2).toBeInTheDocument();
  279. expect(cleanText(input2?.value || "").toLocaleLowerCase()).toEqual("01/31/2001 12:00 am");
  280. });
  281. it("displays the default value with format", async () => {
  282. render(
  283. <LocalizationProvider dateAdapter={AdapterDateFns}>
  284. <DateRange
  285. defaultDates='["2001-01-01T00:10:01.001Z","2001-01-31T00:11:01.001Z"]'
  286. withTime={true}
  287. dates={undefined as unknown as string[]}
  288. className="tp-dt"
  289. format="dd-MM-yyyy mm"
  290. />
  291. </LocalizationProvider>
  292. );
  293. const input = document.querySelector(".tp-dt-picker-start input") as HTMLInputElement;
  294. expect(input).toBeInTheDocument();
  295. expect(cleanText(input?.value || "").toLocaleLowerCase()).toEqual("01-01-2001 10");
  296. const input2 = document.querySelector(".tp-dt-picker-end input") as HTMLInputElement;
  297. expect(input2).toBeInTheDocument();
  298. expect(cleanText(input2?.value || "").toLocaleLowerCase()).toEqual("31-01-2001 11");
  299. });
  300. it("shows labels", async () => {
  301. const { getByLabelText } = render(
  302. <LocalizationProvider dateAdapter={AdapterDateFns}>
  303. <DateRange
  304. defaultDates='["2001-01-01T00:00:01.001Z","2001-01-31T00:00:01.001Z"]'
  305. dates={undefined as unknown as string[]}
  306. withTime={true}
  307. className="taipy-date-range"
  308. labelStart="start"
  309. labelEnd="end"
  310. />
  311. </LocalizationProvider>
  312. );
  313. const startInput = getByLabelText("start") as HTMLInputElement;
  314. expect(startInput.value.toLocaleLowerCase()).toBe("01/01/2001 12:00 am");
  315. const endInput = getByLabelText("end") as HTMLInputElement;
  316. expect(endInput.value.toLocaleLowerCase()).toBe("01/31/2001 12:00 am");
  317. });
  318. it("is disabled", async () => {
  319. render(
  320. <LocalizationProvider dateAdapter={AdapterDateFns}>
  321. <DateRange dates={curDates} withTime={true} active={false} className="tp-dt" />
  322. </LocalizationProvider>
  323. );
  324. const input = document.querySelector(".tp-dt-picker-start input");
  325. expect(input).toBeInTheDocument();
  326. expect(input).toBeDisabled();
  327. const input2 = document.querySelector(".tp-dt-picker-end input");
  328. expect(input2).toBeInTheDocument();
  329. expect(input2).toBeDisabled();
  330. });
  331. it("is enabled by default", async () => {
  332. render(
  333. <LocalizationProvider dateAdapter={AdapterDateFns}>
  334. <DateRange dates={curDates} withTime={true} />
  335. </LocalizationProvider>
  336. );
  337. const input = document.querySelector("input");
  338. expect(input).toBeInTheDocument();
  339. expect(input).not.toBeDisabled();
  340. });
  341. it("is enabled by active", async () => {
  342. render(
  343. <LocalizationProvider dateAdapter={AdapterDateFns}>
  344. <DateRange dates={curDates} withTime={true} active={true} />
  345. </LocalizationProvider>
  346. );
  347. const input = document.querySelector("input");
  348. expect(input).toBeInTheDocument();
  349. expect(input).not.toBeDisabled();
  350. });
  351. it("dispatch a well formed message", async () => {
  352. const dispatch = jest.fn();
  353. const state: TaipyState = INITIAL_STATE;
  354. render(
  355. <TaipyContext.Provider value={{ state, dispatch }}>
  356. <LocalizationProvider dateAdapter={AdapterDateFns}>
  357. <DateRange dates={curDates} withTime={true} updateVarName="varname" className="tp-dt" />
  358. </LocalizationProvider>
  359. </TaipyContext.Provider>
  360. );
  361. const input = document.querySelector(".tp-dt-picker-start input");
  362. expect(input).toBeInTheDocument();
  363. const input2 = document.querySelector(".tp-dt-picker-end input");
  364. expect(input2).toBeInTheDocument();
  365. if (input && input2) {
  366. // await userEvent.clear(input);
  367. await userEvent.type(
  368. input,
  369. "{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}010120010101am",
  370. { delay: 1 }
  371. );
  372. // await userEvent.clear(input2);
  373. await userEvent.type(
  374. input2,
  375. "{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}123120010101am",
  376. { delay: 1 }
  377. );
  378. expect(dispatch).toHaveBeenLastCalledWith({
  379. name: "varname",
  380. payload: { value: ["2001-01-01T01:01:00.000Z", "2001-12-31T01:01:00.000Z"] },
  381. propagate: true,
  382. type: "SEND_UPDATE_ACTION",
  383. });
  384. }
  385. });
  386. });