Input.spec.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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, waitFor, fireEvent, createEvent } from "@testing-library/react";
  15. import "@testing-library/jest-dom";
  16. import userEvent from "@testing-library/user-event";
  17. import Input from "./Input";
  18. import { TaipyContext } from "../../context/taipyContext";
  19. import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
  20. describe("Input Component", () => {
  21. it("renders", async () => {
  22. const { getByDisplayValue } = render(<Input type="text" value="toto" />);
  23. const elt = getByDisplayValue("toto");
  24. expect(elt.tagName).toBe("INPUT");
  25. });
  26. it("displays the right info for string", async () => {
  27. const { getByDisplayValue } = render(
  28. <Input value="toto" type="text" defaultValue="titi" className="taipy-input" />,
  29. );
  30. const elt = getByDisplayValue("toto");
  31. expect(elt.parentElement?.parentElement).toHaveClass("taipy-input");
  32. });
  33. it("displays the default value", async () => {
  34. const { getByDisplayValue } = render(
  35. <Input defaultValue="titi" value={undefined as unknown as string} type="text" />,
  36. );
  37. getByDisplayValue("titi");
  38. });
  39. it("displays with width=70%", async () => {
  40. const { getByDisplayValue, getByTestId } = render(
  41. <Input value="toto" type="text" defaultValue="titi" width="70%"/>
  42. );
  43. const element = getByDisplayValue("toto");
  44. expect(element.parentElement?.parentElement).toHaveStyle('max-width: 70%');
  45. });
  46. it("displays with width=500", async () => {
  47. const { getByDisplayValue, getByTestId } = render(
  48. <Input value="toto" type="text" defaultValue="titi" width={500}/>
  49. );
  50. const element = getByDisplayValue("toto");
  51. expect(element.parentElement?.parentElement).toHaveStyle('max-width: 500px');
  52. });
  53. it("is disabled", async () => {
  54. const { getByDisplayValue } = render(<Input value="val" type="text" active={false} />);
  55. const elt = getByDisplayValue("val");
  56. expect(elt).toBeDisabled();
  57. });
  58. it("is enabled by default", async () => {
  59. const { getByDisplayValue } = render(<Input value="val" type="text" />);
  60. const elt = getByDisplayValue("val");
  61. expect(elt).not.toBeDisabled();
  62. });
  63. it("is enabled by active", async () => {
  64. const { getByDisplayValue } = render(<Input value="val" type="text" active={true} />);
  65. const elt = getByDisplayValue("val");
  66. expect(elt).not.toBeDisabled();
  67. });
  68. it("dispatch a well formed message", async () => {
  69. const dispatch = jest.fn();
  70. const state: TaipyState = INITIAL_STATE;
  71. const { getByDisplayValue } = render(
  72. <TaipyContext.Provider value={{ state, dispatch }}>
  73. <Input value="Val" type="text" updateVarName="varname" />
  74. </TaipyContext.Provider>,
  75. );
  76. const elt = getByDisplayValue("Val");
  77. await userEvent.clear(elt);
  78. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  79. expect(dispatch).toHaveBeenLastCalledWith({
  80. name: "varname",
  81. payload: { value: "" },
  82. propagate: true,
  83. type: "SEND_UPDATE_ACTION",
  84. });
  85. });
  86. it("dispatch a well formed message on enter", async () => {
  87. const dispatch = jest.fn();
  88. const state: TaipyState = INITIAL_STATE;
  89. const { getByDisplayValue } = render(
  90. <TaipyContext.Provider value={{ state, dispatch }}>
  91. <Input value="Val" type="text" updateVarName="varname" onAction="on_action" />
  92. </TaipyContext.Provider>,
  93. );
  94. const elt = getByDisplayValue("Val");
  95. await userEvent.click(elt);
  96. await userEvent.keyboard("data{Enter}");
  97. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  98. expect(dispatch).toHaveBeenLastCalledWith({
  99. name: "",
  100. payload: { action: "on_action", args: ["Enter", "varname", "Valdata"] },
  101. type: "SEND_ACTION_ACTION",
  102. });
  103. });
  104. it("dispatch a well formed update message with change_delay=-1", async () => {
  105. const dispatch = jest.fn();
  106. const state: TaipyState = INITIAL_STATE;
  107. const { getByDisplayValue } = render(
  108. <TaipyContext.Provider value={{ state, dispatch }}>
  109. <Input value="Val" type="text" updateVarName="varname" changeDelay={-1} />
  110. </TaipyContext.Provider>,
  111. );
  112. const elt = getByDisplayValue("Val");
  113. await userEvent.click(elt);
  114. await userEvent.keyboard("data{Enter}");
  115. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  116. expect(dispatch).toHaveBeenLastCalledWith({
  117. name: "varname",
  118. payload: { value: "Valdata" },
  119. propagate: true,
  120. type: "SEND_UPDATE_ACTION",
  121. });
  122. });
  123. it("dispatch a well formed update message with change_delay=0", async () => {
  124. const dispatch = jest.fn();
  125. const state: TaipyState = INITIAL_STATE;
  126. const { getByDisplayValue } = render(
  127. <TaipyContext.Provider value={{ state, dispatch }}>
  128. <Input value="Val" type="text" updateVarName="varname" changeDelay={0} />
  129. </TaipyContext.Provider>,
  130. );
  131. const elt = getByDisplayValue("Val");
  132. await userEvent.click(elt);
  133. await userEvent.keyboard("data{Enter}");
  134. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  135. expect(dispatch).toHaveBeenLastCalledWith({
  136. name: "varname",
  137. payload: { value: "Valdata" },
  138. propagate: true,
  139. type: "SEND_UPDATE_ACTION",
  140. });
  141. });
  142. it("dispatch a no action message on unsupported key", async () => {
  143. const dispatch = jest.fn();
  144. const state: TaipyState = INITIAL_STATE;
  145. const { getByDisplayValue } = render(
  146. <TaipyContext.Provider value={{ state, dispatch }}>
  147. <Input value="Val" type="text" updateVarName="varname" onAction="on_action" />
  148. </TaipyContext.Provider>,
  149. );
  150. const elt = getByDisplayValue("Val");
  151. await userEvent.click(elt);
  152. await userEvent.keyboard("data{Escape}");
  153. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  154. expect(dispatch).toHaveBeenLastCalledWith({
  155. name: "varname",
  156. payload: { value: "Valdata" },
  157. propagate: true,
  158. type: "SEND_UPDATE_ACTION",
  159. });
  160. });
  161. it("should display visibility off icon when password is visible", async () => {
  162. const { getByLabelText } = render(<Input value={"Test Input"} type="password" />);
  163. const visibilityButton = getByLabelText("toggle password visibility");
  164. fireEvent.click(visibilityButton);
  165. const visibilityIcon = document.querySelector('svg[data-testid="VisibilityOffIcon"]');
  166. expect(visibilityIcon).toBeInTheDocument();
  167. });
  168. it("should display visibility icon when password is hidden", async () => {
  169. const { getByLabelText } = render(<Input value={"Test Input"} type="password" />);
  170. const visibilityButton = getByLabelText("toggle password visibility");
  171. expect(visibilityButton).toBeInTheDocument();
  172. });
  173. it("should prevent default action when mouse down event occurs on password visibility button", async () => {
  174. const { getByLabelText } = render(<Input value={"Test Input"} type="password" />);
  175. const visibilityButton = getByLabelText("toggle password visibility");
  176. const keyDown = createEvent.mouseDown(visibilityButton);
  177. fireEvent(visibilityButton, keyDown);
  178. expect(keyDown.defaultPrevented).toBe(true);
  179. });
  180. it("parses actionKeys correctly", () => {
  181. const { rerender } = render(<Input type="text" value="test" actionKeys="Enter;Escape;F1" />);
  182. rerender(<Input type="text" value="test" actionKeys="Enter;F1;F2" />);
  183. rerender(<Input type="text" value="test" actionKeys="F1;F2;F3" />);
  184. rerender(<Input type="text" value="test" actionKeys="F2;F3;F4" />);
  185. });
  186. });
  187. describe("Number Component", () => {
  188. it("renders", async () => {
  189. const { getByDisplayValue } = render(<Input type="number" value="12" />);
  190. const elt = getByDisplayValue("12");
  191. expect(elt.tagName).toBe("INPUT");
  192. });
  193. it("displays the right info for string", async () => {
  194. const { getByDisplayValue } = render(
  195. <Input value="12" type="number" defaultValue="1" className="taipy-number" />,
  196. );
  197. const elt = getByDisplayValue(12);
  198. expect(elt.parentElement?.parentElement).toHaveClass("taipy-number");
  199. });
  200. it("displays the default value", async () => {
  201. const { getByDisplayValue } = render(
  202. <Input defaultValue="1" value={undefined as unknown as string} type="number" />,
  203. );
  204. getByDisplayValue("1");
  205. });
  206. it("is disabled", async () => {
  207. const { getByDisplayValue, getByLabelText } = render(<Input value={"33"} type="number" active={false} />);
  208. const elt = getByDisplayValue("33");
  209. expect(elt).toBeDisabled();
  210. const upSpinner = getByLabelText("Increment value");
  211. expect(upSpinner).toBeDisabled();
  212. });
  213. it("is enabled by default", async () => {
  214. const { getByDisplayValue } = render(<Input value={"33"} type="number" />);
  215. const elt = getByDisplayValue("33");
  216. expect(elt).not.toBeDisabled();
  217. });
  218. it("is enabled by active", async () => {
  219. const { getByDisplayValue } = render(<Input value={"33"} type="number" active={true} />);
  220. const elt = getByDisplayValue("33");
  221. expect(elt).not.toBeDisabled();
  222. });
  223. it("dispatch a well formed message", async () => {
  224. const dispatch = jest.fn();
  225. const state: TaipyState = INITIAL_STATE;
  226. const { getByDisplayValue } = render(
  227. <TaipyContext.Provider value={{ state, dispatch }}>
  228. <Input value={"33"} type="number" updateVarName="varname" />
  229. </TaipyContext.Provider>,
  230. );
  231. const elt = getByDisplayValue("33");
  232. await userEvent.clear(elt);
  233. await userEvent.type(elt, "666");
  234. await waitFor(() => expect(dispatch).toHaveBeenCalled());
  235. expect(dispatch).toHaveBeenLastCalledWith({
  236. name: "varname",
  237. payload: { value: "666" },
  238. propagate: true,
  239. type: "SEND_UPDATE_ACTION",
  240. });
  241. });
  242. xit("shows 0", async () => {
  243. //not working cf. https://github.com/testing-library/user-event/issues/1066
  244. const { getByDisplayValue } = render(<Input value={"0"} type="number" />);
  245. const elt = getByDisplayValue("0") as HTMLInputElement;
  246. expect(elt).toBeInTheDocument();
  247. await userEvent.type(elt, "{ArrowUp}");
  248. expect(elt.value).toBe("1");
  249. await userEvent.type(elt, "{ArrowDown}");
  250. expect(elt.value).toBe("0");
  251. });
  252. it("Validates increment by step value on up click", async () => {
  253. const { getByDisplayValue, getByLabelText } = render(
  254. <Input id={"Test Input"} value={"0"} type="number" step={2} />,
  255. );
  256. const upSpinner = getByLabelText("Increment value");
  257. const elt = getByDisplayValue("0") as HTMLInputElement;
  258. await userEvent.click(upSpinner);
  259. expect(elt.value).toBe("2");
  260. });
  261. it("Validates decrement by step value on down click", async () => {
  262. const { getByDisplayValue, getByLabelText } = render(
  263. <Input id={"Test Input"} value={"0"} type="number" step={2} />,
  264. );
  265. const downSpinner = getByLabelText("Decrement value");
  266. const elt = getByDisplayValue("0") as HTMLInputElement;
  267. await userEvent.click(downSpinner);
  268. expect(elt.value).toBe("-2");
  269. });
  270. it("Validates increment when holding shift key and clicking up", async () => {
  271. const user = userEvent.setup();
  272. const { getByDisplayValue, getByLabelText } = render(
  273. <Input id={"Test Input"} value={"0"} type="number" step={2} />,
  274. );
  275. const upSpinner = getByLabelText("Increment value");
  276. const elt = getByDisplayValue("0") as HTMLInputElement;
  277. await user.keyboard("[ShiftLeft>]");
  278. await user.click(upSpinner);
  279. expect(elt.value).toBe("20");
  280. });
  281. it("Validates decrement when holding shift key and clicking down", async () => {
  282. const user = userEvent.setup();
  283. const { getByDisplayValue, getByLabelText } = render(
  284. <Input id={"Test Input"} value={"0"} type="number" step={2} />,
  285. );
  286. const downSpinner = getByLabelText("Decrement value");
  287. const elt = getByDisplayValue("0") as HTMLInputElement;
  288. await user.keyboard("[ShiftLeft>]");
  289. await user.click(downSpinner);
  290. expect(elt.value).toBe("-20");
  291. });
  292. it("Validate increment when holding shift key and arrow up", async () => {
  293. const user = userEvent.setup();
  294. const { getByDisplayValue } = render(<Input value={"0"} type="number" step={2} />);
  295. const elt = getByDisplayValue("0") as HTMLInputElement;
  296. await user.click(elt);
  297. await user.keyboard("[ShiftLeft>]");
  298. await user.keyboard("[ArrowUp]");
  299. expect(elt.value).toBe("20");
  300. });
  301. it("Validate value when reaching max value", async () => {
  302. const user = userEvent.setup();
  303. const { getByDisplayValue } = render(<Input value={"0"} type="number" step={2} max={20} />);
  304. const elt = getByDisplayValue("0") as HTMLInputElement;
  305. await user.click(elt);
  306. await user.keyboard("[ShiftLeft>]");
  307. // Press the arrow up twice to validate that the value will not exceed the maximum value when reached
  308. await user.keyboard("[ArrowUp]");
  309. await user.keyboard("[ArrowUp]");
  310. expect(elt.value).toBe("20");
  311. });
  312. it("Validate value when reaching min value", async () => {
  313. const user = userEvent.setup();
  314. const { getByDisplayValue } = render(<Input value={"20"} type="number" step={2} min={0} />);
  315. const elt = getByDisplayValue("20") as HTMLInputElement;
  316. await user.click(elt);
  317. await user.keyboard("[ShiftLeft>]");
  318. // Press the arrow down twice to validate that the value will not exceed the minimum value when reached
  319. await user.keyboard("[ArrowDown]");
  320. await user.keyboard("[ArrowDown]");
  321. expect(elt.value).toBe("0");
  322. });
  323. it("it should not decrement below the min value", () => {
  324. const { getByLabelText } = render(<Input id={"Test Input"} type="number" value="0" min={0} />);
  325. const downSpinner = getByLabelText("Decrement value");
  326. fireEvent.mouseDown(downSpinner);
  327. const inputElement = document.getElementById("Test Input") as HTMLInputElement;
  328. expect(inputElement.value).toBe("0");
  329. });
  330. it("should not exceed max value when incrementing", async () => {
  331. const { getByLabelText } = render(
  332. <Input id={"Test Input"} type="number" value="0" max={20} step={2} stepMultiplier={15} />,
  333. );
  334. const upSpinner = getByLabelText("Increment value");
  335. fireEvent.mouseDown(upSpinner, { shiftKey: true });
  336. const inputElement = document.getElementById("Test Input") as HTMLInputElement;
  337. await waitFor(() => {
  338. expect(inputElement.value).toBe("20");
  339. });
  340. });
  341. });