FileSelector.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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 { fireEvent, render, waitFor } from "@testing-library/react";
  15. import "@testing-library/jest-dom";
  16. import userEvent from "@testing-library/user-event";
  17. import FileSelector from "./FileSelector";
  18. import { TaipyContext } from "../../context/taipyContext";
  19. import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers";
  20. import { uploadFile } from "../../workers/fileupload";
  21. jest.mock("../../workers/fileupload", () => ({
  22. uploadFile: jest.fn().mockResolvedValue("mocked response"), // returns a Promise that resolves to 'mocked response'
  23. }));
  24. describe("FileSelector Component", () => {
  25. it("renders", async () => {
  26. const { getByText } = render(<FileSelector label="toto" />);
  27. const elt = getByText("toto");
  28. expect(elt.tagName).toBe("SPAN");
  29. });
  30. it("displays the right info for string", async () => {
  31. const { getByText } = render(<FileSelector label="toto" defaultLabel="titi" className="taipy-file-selector" />);
  32. const elt = getByText("toto");
  33. expect(elt.parentElement).toHaveClass("taipy-file-selector");
  34. });
  35. it("displays the default value", async () => {
  36. const { getByText } = render(<FileSelector defaultLabel="titi" label={undefined as unknown as string} />);
  37. getByText("titi");
  38. });
  39. it("is disabled", async () => {
  40. const { getByText } = render(<FileSelector label="val" active={false} />);
  41. const elt = getByText("val");
  42. expect(elt).toHaveClass("Mui-disabled");
  43. });
  44. it("is enabled by default", async () => {
  45. const { getByText } = render(<FileSelector label="val" />);
  46. const elt = getByText("val");
  47. expect(elt).not.toHaveClass("Mui-disabled");
  48. });
  49. it("is enabled by active", async () => {
  50. const { getByText } = render(<FileSelector label="val" active={true} />);
  51. const elt = getByText("val");
  52. expect(elt).not.toHaveClass("Mui-disabled");
  53. });
  54. //looks like userEvent upload does not fire onchange
  55. xit("dispatch a well formed message on file selection", async () => {
  56. const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
  57. const dispatch = jest.fn();
  58. const state: TaipyState = INITIAL_STATE;
  59. const { getByText } = render(
  60. <TaipyContext.Provider value={{ state, dispatch }}>
  61. <FileSelector label="FileSelector" onAction="on_action" />
  62. </TaipyContext.Provider>,
  63. );
  64. const elt = getByText("FileSelector");
  65. const inputElt = elt.parentElement?.querySelector("input");
  66. expect(inputElt).toBeInTheDocument();
  67. inputElt && (await userEvent.upload(inputElt, file));
  68. expect(dispatch).toHaveBeenCalledWith({
  69. name: "",
  70. payload: { args: [], action: "on_action" },
  71. type: "SEND_ACTION_ACTION",
  72. });
  73. });
  74. it("dispatch a specific text on file drop", async () => {
  75. const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" });
  76. const { getByRole, getByText } = render(<FileSelector label="FileSelectorDrop" />);
  77. const elt = getByRole("button");
  78. const inputElt = elt.parentElement?.querySelector("input");
  79. expect(inputElt).toBeInTheDocument();
  80. waitFor(() => getByText("Drop here to Upload"));
  81. inputElt &&
  82. fireEvent.drop(inputElt, {
  83. dataTransfer: {
  84. files: [file],
  85. },
  86. });
  87. });
  88. it("displays a dropped custom message", async () => {
  89. const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" });
  90. const { getByRole, getByText } = render(
  91. <FileSelector label="FileSelectorDrop" dropMessage="drop here those files" />,
  92. );
  93. const elt = getByRole("button");
  94. const inputElt = elt.parentElement?.querySelector("input");
  95. expect(inputElt).toBeInTheDocument();
  96. waitFor(() => getByText("drop here those files"));
  97. inputElt &&
  98. fireEvent.drop(inputElt, {
  99. dataTransfer: {
  100. files: [file],
  101. },
  102. });
  103. });
  104. it("handles drag over event", () => {
  105. const { getByRole } = render(<FileSelector label="FileSelectorDrag" />);
  106. const fileSelector = getByRole("button");
  107. // Create a mock DragEvent and add a dataTransfer object
  108. const mockEvent = new Event("dragover", { bubbles: true }) as unknown as DragEvent;
  109. Object.assign(mockEvent, {
  110. dataTransfer: {
  111. dropEffect: "",
  112. },
  113. });
  114. // Dispatch the mock event
  115. fireEvent(fileSelector, mockEvent);
  116. // Add assertion to check if dropEffect is set to "copy"
  117. expect(mockEvent.dataTransfer!.dropEffect).toBe("copy");
  118. });
  119. it("handles file drop", async () => {
  120. // Create a mock file
  121. const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
  122. const { getByRole } = render(<FileSelector label="FileSelectorDrop" />);
  123. const elt = getByRole("button");
  124. // Create a mock DragEvent and add the mock file to the event's dataTransfer.files property
  125. const mockEvent = new Event("drop", { bubbles: true }) as unknown as DragEvent;
  126. Object.assign(mockEvent, {
  127. dataTransfer: {
  128. files: [file],
  129. },
  130. });
  131. // Dispatch the mock event
  132. fireEvent(elt, mockEvent);
  133. expect(uploadFile).toHaveBeenCalledTimes(1);
  134. });
  135. it("resets dropLabel and dropSx on drag leave", async () => {
  136. const { getByRole, getByTestId } = render(<FileSelector />);
  137. const elt = getByRole("button");
  138. // Create a mock DragEvent
  139. const mockEvent = new Event("dragleave", { bubbles: true }) as unknown as DragEvent;
  140. // Dispatch the mock event
  141. fireEvent(elt, mockEvent);
  142. // Add assertions to check if dropLabel and dropSx have been reset
  143. const dropLabelElement = getByTestId("file-selector");
  144. expect(dropLabelElement.textContent).toBe(" ");
  145. const buttonElement = getByTestId("upload-button");
  146. expect(buttonElement).toHaveStyle("min-width: 0px");
  147. });
  148. it("checks if notification is dispatched on file upload completion", async () => {
  149. const mockDispatch = jest.fn();
  150. const { getByTestId } = render(
  151. <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
  152. <FileSelector label="FileSelector" notify={true} />
  153. </TaipyContext.Provider>,
  154. );
  155. // Simulate file upload
  156. const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
  157. const inputElement = getByTestId("file-selector").querySelector("input");
  158. if (inputElement) {
  159. fireEvent.change(inputElement, { target: { files: [file] } });
  160. }
  161. // Wait for the upload to complete
  162. await waitFor(() => expect(mockDispatch).toHaveBeenCalled());
  163. // Check if the alert action has been dispatched
  164. expect(mockDispatch).toHaveBeenCalledWith(
  165. expect.objectContaining({
  166. type: "SET_ALERT",
  167. atype: "success",
  168. duration: 3000,
  169. message: "mocked response",
  170. system: false,
  171. }),
  172. );
  173. });
  174. it("checks if error notification is dispatched on file upload failure", async () => {
  175. const mockUploadFile = uploadFile as jest.Mock;
  176. mockUploadFile.mockImplementation(() => {
  177. return new Promise((resolve, reject) => {
  178. reject("Upload failed");
  179. });
  180. });
  181. const mockDispatch = jest.fn();
  182. const { getByTestId } = render(
  183. <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
  184. <FileSelector label="FileSelector" notify={true} />
  185. </TaipyContext.Provider>,
  186. );
  187. // Simulate file upload
  188. const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
  189. const inputElement = getByTestId("file-selector").querySelector("input");
  190. if (inputElement) {
  191. fireEvent.change(inputElement, { target: { files: [file] } });
  192. }
  193. // Wait for the upload to complete
  194. await waitFor(() => expect(mockDispatch).toHaveBeenCalled());
  195. // Check if the alert action has been dispatched
  196. expect(mockDispatch).toHaveBeenCalledWith(
  197. expect.objectContaining({
  198. type: "SET_ALERT",
  199. atype: "error",
  200. duration: 3000,
  201. message: "Upload failed",
  202. system: false,
  203. }),
  204. );
  205. });
  206. it("checks if dispatch is called correctly", async () => {
  207. // Mock the uploadFile function to resolve with a success message
  208. (uploadFile as jest.Mock).mockImplementation(() => Promise.resolve("mocked response"));
  209. const mockDispatch = jest.fn();
  210. const { getByTestId, queryByRole } = render(
  211. <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
  212. <FileSelector label="FileSelector" notify={true} onAction="testAction" />
  213. </TaipyContext.Provider>,
  214. );
  215. // Simulate file upload
  216. const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
  217. const inputElement = getByTestId("file-selector").querySelector("input");
  218. if (inputElement) {
  219. fireEvent.change(inputElement, { target: { files: [file] } });
  220. }
  221. // Check if the progress bar is displayed during the upload process
  222. expect(queryByRole("progressbar")).toBeInTheDocument();
  223. // Wait for the upload to complete
  224. await waitFor(() => expect(mockDispatch).toHaveBeenCalled());
  225. // Check if the progress bar is not displayed after the upload is completed
  226. expect(queryByRole("progressbar")).not.toBeInTheDocument();
  227. // Check if the dispatch function has been called with the correct action
  228. expect(mockDispatch).toHaveBeenCalledWith(
  229. expect.objectContaining({
  230. type: "SEND_ACTION_ACTION",
  231. name: "",
  232. payload: { args: [], action: "testAction" },
  233. }),
  234. );
  235. });
  236. it("checks if no action is taken when no file is uploaded", async () => {
  237. const mockDispatch = jest.fn();
  238. const { getByTestId } = render(
  239. <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
  240. <FileSelector label="FileSelector" notify={true} />
  241. </TaipyContext.Provider>,
  242. );
  243. // Simulate file upload without providing a file
  244. const inputElement = getByTestId("file-selector").querySelector("input");
  245. if (inputElement) {
  246. fireEvent.change(inputElement, { target: { files: [] } });
  247. }
  248. // Check if the dispatch function has not been called
  249. expect(mockDispatch).not.toHaveBeenCalled();
  250. });
  251. });