Selector.spec.tsx 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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 "@testing-library/jest-dom";
  16. import userEvent from "@testing-library/user-event";
  17. import Selector from "./Selector";
  18. import { LoV } from "./lovUtils";
  19. import { INITIAL_STATE, TaipyState } from "../../context/taipyReducers";
  20. import { TaipyContext } from "../../context/taipyContext";
  21. import { stringIcon } from "../../utils/icon";
  22. const lov: LoV = [
  23. ["id1", "Item 1"],
  24. ["id2", "Item 2"],
  25. ["id3", "Item 3"],
  26. ["id4", "Item 4"],
  27. ];
  28. const defLov = '[["id10","Default Item"]]';
  29. const imageItem: [string, stringIcon] = ["ii1", { path: "/img/fred.png", text: "Image" }];
  30. describe("Selector Component", () => {
  31. it("renders", async () => {
  32. const { getByText } = render(<Selector lov={lov} />);
  33. const elt = getByText("Item 1");
  34. expect(elt.tagName).toBe("SPAN");
  35. });
  36. it("uses the class", async () => {
  37. const { getByText } = render(<Selector lov={lov} className="taipy-selector" />);
  38. const elt = getByText("Item 1");
  39. expect(elt.parentElement?.parentElement?.parentElement?.parentElement?.parentElement).toHaveClass(
  40. "taipy-selector"
  41. );
  42. });
  43. it("can display an image", async () => {
  44. const lovWithImage = [...lov, imageItem];
  45. const { getByAltText } = render(<Selector lov={lovWithImage} />);
  46. const elt = getByAltText("Image");
  47. expect(elt.tagName).toBe("IMG");
  48. });
  49. it("displays the right info for lov vs defaultLov", async () => {
  50. const { getByText, queryAllByText } = render(<Selector lov={lov} defaultLov={defLov} />);
  51. getByText("Item 1");
  52. expect(queryAllByText("Default Item")).toHaveLength(0);
  53. });
  54. it("displays the default LoV", async () => {
  55. const { getByText } = render(<Selector lov={undefined as unknown as []} defaultLov={defLov} />);
  56. getByText("Default Item");
  57. });
  58. it("shows a selection at start", async () => {
  59. const { getByText } = render(<Selector defaultValue="id1" lov={lov} />);
  60. const elt = getByText("Item 1");
  61. expect(elt.parentElement?.parentElement).toHaveClass("Mui-selected");
  62. });
  63. it("shows a selection at start through value", async () => {
  64. const { getByText } = render(<Selector defaultValue="id1" value="id2" lov={lov} />);
  65. const elt = getByText("Item 1");
  66. expect(elt.parentElement?.parentElement).not.toHaveClass("Mui-selected");
  67. const elt2 = getByText("Item 2");
  68. expect(elt2.parentElement?.parentElement).toHaveClass("Mui-selected");
  69. });
  70. it("is disabled", async () => {
  71. const { getAllByRole } = render(<Selector lov={lov} active={false} />);
  72. const elts = getAllByRole("button");
  73. elts.forEach((elt) => expect(elt).toHaveClass("Mui-disabled"));
  74. });
  75. it("is enabled by default", async () => {
  76. const { getAllByRole } = render(<Selector lov={lov} />);
  77. const elts = getAllByRole("button");
  78. elts.forEach((elt) => expect(elt).not.toHaveClass("Mui-disabled"));
  79. });
  80. it("is enabled by active", async () => {
  81. const { getAllByRole } = render(<Selector lov={lov} active={true} />);
  82. const elts = getAllByRole("button");
  83. elts.forEach((elt) => expect(elt).not.toHaveClass("Mui-disabled"));
  84. });
  85. it("dispatch a well formed message", async () => {
  86. const dispatch = jest.fn();
  87. const state: TaipyState = INITIAL_STATE;
  88. const { getByText } = render(
  89. <TaipyContext.Provider value={{ state, dispatch }}>
  90. <Selector lov={lov} updateVarName="varname" updateVars="lov=lov" />
  91. </TaipyContext.Provider>
  92. );
  93. const elt = getByText("Item 1");
  94. await userEvent.click(elt);
  95. expect(dispatch).toHaveBeenCalledWith({
  96. name: "varname",
  97. payload: { value: "id1", relvar: "lov" },
  98. propagate: true,
  99. type: "SEND_UPDATE_ACTION",
  100. });
  101. });
  102. //multiple
  103. describe("Selector Component with multiple", () => {
  104. it("displays checkboxes when multiple", async () => {
  105. const { queryAllByRole } = render(<Selector lov={lov} multiple={true} />);
  106. expect(queryAllByRole("checkbox")).toHaveLength(4);
  107. });
  108. it("does not display checkboxes when not multiple", async () => {
  109. const { queryAllByRole } = render(<Selector lov={lov} multiple={false} />);
  110. expect(queryAllByRole("checkbox")).toHaveLength(0);
  111. });
  112. it("selects 2 items", async () => {
  113. const { queryAllByRole } = render(<Selector lov={lov} multiple={true} value={["id1", "id2"]} />);
  114. const cks = queryAllByRole("checkbox");
  115. const ccks = cks.filter((ck) => (ck as HTMLInputElement).checked);
  116. expect(ccks).toHaveLength(2);
  117. });
  118. it("selects the checkbox when the line is selected", async () => {
  119. const { getByText } = render(<Selector lov={lov} multiple={true} value="id2" />);
  120. const elt = getByText("Item 2");
  121. const ck = elt.parentElement?.parentElement?.querySelector('input[type="checkbox"]') as HTMLInputElement;
  122. expect(ck).toBeDefined();
  123. expect(ck.checked).toBe(true);
  124. });
  125. it("dispatch a well formed message for multiple", async () => {
  126. const user = userEvent.setup();
  127. const dispatch = jest.fn();
  128. const state: TaipyState = INITIAL_STATE;
  129. const { getByText } = render(
  130. <TaipyContext.Provider value={{ state, dispatch }}>
  131. <Selector lov={lov} updateVarName="varname" multiple={true} />
  132. </TaipyContext.Provider>
  133. );
  134. const elt = getByText("Item 1");
  135. await user.click(elt);
  136. const elt2 = getByText("Item 2");
  137. await user.click(elt2);
  138. const elt3 = getByText("Item 3");
  139. await user.click(elt3);
  140. await user.click(elt2);
  141. expect(dispatch).toHaveBeenLastCalledWith({
  142. name: "varname",
  143. payload: { value: ["id1", "id3"] },
  144. propagate: true,
  145. type: "SEND_UPDATE_ACTION",
  146. });
  147. });
  148. });
  149. describe("Selector Component with filter", () => {
  150. //filter
  151. it("displays an input when filter", async () => {
  152. const { getByPlaceholderText } = render(<Selector lov={lov} filter={true} />);
  153. getByPlaceholderText("Search field");
  154. });
  155. it("does not display an input when filter is off", async () => {
  156. const { queryAllByPlaceholderText } = render(<Selector lov={lov} filter={false} />);
  157. expect(queryAllByPlaceholderText("Search field")).toHaveLength(0);
  158. });
  159. it("filters items by name", async () => {
  160. const { getByPlaceholderText, queryAllByText } = render(<Selector lov={lov} filter={true} />);
  161. expect(queryAllByText(/Item /)).toHaveLength(4);
  162. const search = getByPlaceholderText("Search field");
  163. await userEvent.type(search, "m 3");
  164. expect(queryAllByText(/Item /)).toHaveLength(1);
  165. await userEvent.clear(search);
  166. expect(queryAllByText(/Item /)).toHaveLength(4);
  167. });
  168. });
  169. describe("Selector Component with dropdown", () => {
  170. //dropdown
  171. it("displays as an empty control with arrow", async () => {
  172. const { getByTestId } = render(<Selector lov={lov} dropdown={true} />);
  173. getByTestId("ArrowDropDownIcon");
  174. });
  175. it("displays as a simple input with default value", async () => {
  176. const { getByText, getByTestId, queryAllByTestId } = render(
  177. <Selector lov={lov} defaultValue="id1" dropdown={true} />
  178. );
  179. getByText("Item 1");
  180. expect(queryAllByTestId("CancelIcon")).toHaveLength(0);
  181. getByTestId("ArrowDropDownIcon");
  182. });
  183. it("displays a delete icon when multiple", async () => {
  184. const { getByTestId } = render(<Selector lov={lov} defaultValue="id1" dropdown={true} multiple={true} />);
  185. getByTestId("CancelIcon");
  186. });
  187. it("is disabled", async () => {
  188. const { getByText } = render(<Selector lov={lov} defaultValue="id1" active={false} dropdown={true} />);
  189. const elt = getByText("Item 1");
  190. expect(elt.parentElement).toHaveClass("Mui-disabled");
  191. });
  192. it("is enabled by default", async () => {
  193. const { getByText } = render(<Selector lov={lov} defaultValue="id1" dropdown={true} />);
  194. const elt = getByText("Item 1");
  195. expect(elt.parentElement).not.toHaveClass("Mui-disabled");
  196. });
  197. it("is enabled by active", async () => {
  198. const { getByText } = render(<Selector defaultValue="id1" lov={lov} active={true} dropdown={true} />);
  199. const elt = getByText("Item 1");
  200. expect(elt.parentElement).not.toHaveClass("Mui-disabled");
  201. });
  202. it("opens a dropdown on click", async () => {
  203. const { getByText, getByRole, queryAllByRole } = render(<Selector lov={lov} dropdown={true} />);
  204. const butElt = getByRole("combobox");
  205. expect(butElt).toBeInTheDocument();
  206. await userEvent.click(butElt);
  207. getByRole("listbox");
  208. const elt = getByText("Item 2");
  209. await userEvent.click(elt);
  210. expect(queryAllByRole("listbox")).toHaveLength(0);
  211. });
  212. it("renders selectionMessage if defined", async () => {
  213. const { getByText, getByRole } = render(<Selector lov={lov} dropdown={true} selectionMessage="a selection message" />);
  214. const butElt = getByRole("combobox");
  215. expect(butElt).toBeInTheDocument();
  216. await userEvent.click(butElt);
  217. getByRole("listbox");
  218. const elt = getByText("Item 2");
  219. await userEvent.click(elt);
  220. const msg = getByText("a selection message");
  221. expect(msg).toBeInTheDocument();
  222. });
  223. it("renders showSelectAll in dropdown if True", async () => {
  224. const { getByText, getByRole } = render(<Selector lov={lov} dropdown={true} multiple={true} showSelectAll={true} />);
  225. const checkElt = getByRole("checkbox");
  226. expect(checkElt).toBeInTheDocument();
  227. expect(checkElt).not.toBeChecked();
  228. const butElt = getByRole("combobox");
  229. await userEvent.click(butElt);
  230. getByRole("listbox");
  231. const elt = getByText("Item 2");
  232. await userEvent.click(elt);
  233. expect(checkElt.parentElement).toHaveClass("MuiCheckbox-indeterminate");
  234. await userEvent.click(checkElt);
  235. expect(checkElt).toBeChecked();
  236. });
  237. it("renders showSelectAll in list if True", async () => {
  238. const { getByText, getByRole } = render(<Selector lov={lov} multiple={true} showSelectAll={true} />);
  239. const msgElt = getByText(/select all/i);
  240. expect(msgElt).toBeInTheDocument();
  241. const checkElement = msgElt.parentElement?.querySelector("input");
  242. expect(checkElement).not.toBeNull();
  243. expect(checkElement).not.toBeChecked();
  244. const elt = getByText("Item 2");
  245. await userEvent.click(elt);
  246. expect(checkElement?.parentElement).toHaveClass("MuiCheckbox-indeterminate");
  247. checkElement && await userEvent.click(checkElement);
  248. expect(checkElement).toBeChecked();
  249. });
  250. });
  251. describe("Selector Component with dropdown + filter", () => {
  252. //dropdown
  253. it("displays as an empty control with arrow", async () => {
  254. const { getByTestId } = render(<Selector lov={lov} dropdown={true} filter={true} />);
  255. getByTestId("ArrowDropDownIcon");
  256. });
  257. it("displays as a simple input with default value", async () => {
  258. const { getByRole, getByTestId, queryAllByTestId } = render(
  259. <Selector lov={lov} defaultValue="id1" dropdown={true} filter={true} />
  260. );
  261. expect(getByRole("combobox")).toHaveValue("Item 1");
  262. expect(queryAllByTestId("CancelIcon")).toHaveLength(0);
  263. getByTestId("ArrowDropDownIcon");
  264. });
  265. it("displays a delete icon when multiple", async () => {
  266. const { getByTestId } = render(
  267. <Selector lov={lov} defaultValue="id1" dropdown={true} multiple={true} filter={true} />
  268. );
  269. getByTestId("CancelIcon");
  270. });
  271. it("is disabled", async () => {
  272. const { getByRole } = render(
  273. <Selector lov={lov} defaultValue="id1" active={false} dropdown={true} filter={true} />
  274. );
  275. const elt = getByRole("combobox");
  276. expect(elt.parentElement).toHaveClass("Mui-disabled");
  277. });
  278. it("is enabled by default", async () => {
  279. const { getByRole } = render(<Selector lov={lov} defaultValue="id1" dropdown={true} filter={true} />);
  280. const elt = getByRole("combobox");
  281. expect(elt.parentElement).not.toHaveClass("Mui-disabled");
  282. });
  283. it("is enabled by active", async () => {
  284. const { getByRole } = render(
  285. <Selector defaultValue="id1" lov={lov} active={true} dropdown={true} filter={true} />
  286. );
  287. const elt = getByRole("combobox");
  288. expect(elt.parentElement).not.toHaveClass("Mui-disabled");
  289. });
  290. it("opens a dropdown on click", async () => {
  291. const { getByText, getByRole, queryAllByRole } = render(
  292. <Selector lov={lov} dropdown={true} filter={true} />
  293. );
  294. const butElt = getByRole("combobox");
  295. expect(butElt).toBeInTheDocument();
  296. await userEvent.click(butElt);
  297. getByRole("listbox");
  298. const elt = getByText("Item 2");
  299. await userEvent.click(elt);
  300. expect(queryAllByRole("listbox")).toHaveLength(0);
  301. });
  302. });
  303. describe("Selector Component radio mode", () => {
  304. //dropdown
  305. it("displays a list of unselected radios", async () => {
  306. const { getByText, getByRole } = render(<Selector lov={lov} mode="radio" className="taipy-selector" />);
  307. getByText("Item 1");
  308. getByRole("radiogroup");
  309. expect(document.querySelector("div.taipy-selector-radio-group")).not.toBeNull();
  310. });
  311. it("displays a list of radios with one selected", async () => {
  312. const { getByText } = render(<Selector lov={lov} defaultValue="id1" mode="radio" />);
  313. const elt = getByText("Item 1");
  314. expect(elt.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  315. });
  316. it("selects on click", async () => {
  317. const { getByText, getByRole, queryAllByRole } = render(
  318. <Selector lov={lov} defaultValue="id1" mode="radio" />
  319. );
  320. const elt = getByText("Item 2");
  321. expect(elt.parentElement?.querySelector("span.Mui-checked")).toBeNull();
  322. await userEvent.click(elt);
  323. expect(elt.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  324. });
  325. });
  326. describe("Selector Component check mode", () => {
  327. //dropdown
  328. it("displays a list of unselected checks", async () => {
  329. const { getByText } = render(<Selector lov={lov} mode="check" className="taipy-selector" />);
  330. const elt = getByText("Item 1");
  331. expect(elt.parentElement?.parentElement).toHaveClass("taipy-selector-check-group");
  332. expect(document.querySelector("span.MuiCheckbox-root")).not.toBeNull();
  333. });
  334. it("displays a list of checks with one selected", async () => {
  335. const { getByText } = render(<Selector lov={lov} defaultValue="id1" mode="check" />);
  336. const elt = getByText("Item 1");
  337. expect(elt.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  338. });
  339. it("selects on click", async () => {
  340. const { getByText, getByRole, queryAllByRole } = render(
  341. <Selector lov={lov} defaultValue="id1" mode="check" />
  342. );
  343. const elt1 = getByText("Item 1");
  344. expect(elt1.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  345. const elt2 = getByText("Item 2");
  346. expect(elt2.parentElement?.querySelector("span.Mui-checked")).toBeNull();
  347. await userEvent.click(elt2);
  348. expect(elt1.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  349. expect(elt2.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  350. const elt3 = getByText("Item 3");
  351. expect(elt3.parentElement?.querySelector("span.Mui-checked")).toBeNull();
  352. await userEvent.click(elt3);
  353. expect(elt1.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  354. expect(elt2.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  355. expect(elt3.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  356. await userEvent.click(elt1);
  357. expect(elt1.parentElement?.querySelector("span.Mui-checked")).toBeNull();
  358. expect(elt2.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  359. expect(elt3.parentElement?.querySelector("span.Mui-checked")).not.toBeNull();
  360. });
  361. });
  362. });