From ee4e3e3e1e6b5c3095bed79c49ea05e32a54791d Mon Sep 17 00:00:00 2001 From: namnguyen Date: Fri, 12 Jul 2024 04:10:04 +0700 Subject: [PATCH 1/2] add test cases for FileSelector.tsx --- .../components/Taipy/FileSelector.spec.tsx | 191 +++++++++++++++++- .../src/components/Taipy/FileSelector.tsx | 31 +-- 2 files changed, 203 insertions(+), 19 deletions(-) diff --git a/frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx b/frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx index c7ae9a6eb1..5c33b4ad4c 100644 --- a/frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx +++ b/frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx @@ -19,6 +19,11 @@ import userEvent from "@testing-library/user-event"; import FileSelector from "./FileSelector"; import { TaipyContext } from "../../context/taipyContext"; import { TaipyState, INITIAL_STATE } from "../../context/taipyReducers"; +import { uploadFile } from "../../workers/fileupload"; + +jest.mock("../../workers/fileupload", () => ({ + uploadFile: jest.fn().mockResolvedValue("mocked response"), // returns a Promise that resolves to 'mocked response' +})); describe("FileSelector Component", () => { it("renders", async () => { @@ -58,17 +63,17 @@ describe("FileSelector Component", () => { const { getByText } = render( - + , ); const elt = getByText("FileSelector"); const inputElt = elt.parentElement?.querySelector("input"); expect(inputElt).toBeInTheDocument(); - inputElt && await userEvent.upload(inputElt, file); + inputElt && (await userEvent.upload(inputElt, file)); expect(dispatch).toHaveBeenCalledWith({ name: "", payload: { args: [], action: "on_action" }, type: "SEND_ACTION_ACTION", - }) + }); }); it("dispatch a specific text on file drop", async () => { const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" }); @@ -86,7 +91,9 @@ describe("FileSelector Component", () => { }); it("displays a dropped custom message", async () => { const file = new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" }); - const { getByRole, getByText } = render(); + const { getByRole, getByText } = render( + , + ); const elt = getByRole("button"); const inputElt = elt.parentElement?.querySelector("input"); expect(inputElt).toBeInTheDocument(); @@ -98,4 +105,180 @@ describe("FileSelector Component", () => { }, }); }); + it("handles drag over event", () => { + const { getByRole } = render(); + const fileSelector = getByRole("button"); + + // Create a mock DragEvent and add a dataTransfer object + const mockEvent = new Event("dragover", { bubbles: true }) as unknown as DragEvent; + Object.assign(mockEvent, { + dataTransfer: { + dropEffect: "", + }, + }); + + // Dispatch the mock event + fireEvent(fileSelector, mockEvent); + + // Add assertion to check if dropEffect is set to "copy" + expect(mockEvent.dataTransfer!.dropEffect).toBe("copy"); + }); + + it("handles file drop", async () => { + // Create a mock file + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + + const { getByRole } = render(); + const elt = getByRole("button"); + // Create a mock DragEvent and add the mock file to the event's dataTransfer.files property + const mockEvent = new Event("drop", { bubbles: true }) as unknown as DragEvent; + Object.assign(mockEvent, { + dataTransfer: { + files: [file], + }, + }); + // Dispatch the mock event + fireEvent(elt, mockEvent); + expect(uploadFile).toHaveBeenCalledTimes(1); + }); + + it("resets dropLabel and dropSx on drag leave", async () => { + const { getByRole, getByTestId } = render(); + const elt = getByRole("button"); + + // Create a mock DragEvent + const mockEvent = new Event("dragleave", { bubbles: true }) as unknown as DragEvent; + + // Dispatch the mock event + fireEvent(elt, mockEvent); + + // Add assertions to check if dropLabel and dropSx have been reset + const dropLabelElement = getByTestId("file-selector"); + expect(dropLabelElement.textContent).toBe(" "); + const buttonElement = getByTestId("upload-button"); + expect(buttonElement).toHaveStyle("min-width: 0px"); + }); + + it("checks if notification is dispatched on file upload completion", async () => { + const mockDispatch = jest.fn(); + const { getByTestId } = render( + + + , + ); + + // Simulate file upload + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const inputElement = getByTestId("file-selector").querySelector("input"); + if (inputElement) { + fireEvent.change(inputElement, { target: { files: [file] } }); + } + + // Wait for the upload to complete + await waitFor(() => expect(mockDispatch).toHaveBeenCalled()); + + // Check if the alert action has been dispatched + expect(mockDispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: "SET_ALERT", + atype: "success", + duration: 3000, + message: "mocked response", + system: false, + }), + ); + }); + + it("checks if error notification is dispatched on file upload failure", async () => { + const mockUploadFile = uploadFile as jest.Mock; + + mockUploadFile.mockImplementation(() => { + return new Promise((resolve, reject) => { + reject("Upload failed"); + }); + }); + + const mockDispatch = jest.fn(); + const { getByTestId } = render( + + + , + ); + + // Simulate file upload + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const inputElement = getByTestId("file-selector").querySelector("input"); + if (inputElement) { + fireEvent.change(inputElement, { target: { files: [file] } }); + } + + // Wait for the upload to complete + await waitFor(() => expect(mockDispatch).toHaveBeenCalled()); + + // Check if the alert action has been dispatched + expect(mockDispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: "SET_ALERT", + atype: "error", + duration: 3000, + message: "Upload failed", + system: false, + }), + ); + }); + + it("checks if dispatch is called correctly", async () => { + // Mock the uploadFile function to resolve with a success message + (uploadFile as jest.Mock).mockImplementation(() => Promise.resolve("mocked response")); + + const mockDispatch = jest.fn(); + const { getByTestId, queryByRole } = render( + + + , + ); + + // Simulate file upload + const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }); + const inputElement = getByTestId("file-selector").querySelector("input"); + if (inputElement) { + fireEvent.change(inputElement, { target: { files: [file] } }); + } + + // Check if the progress bar is displayed during the upload process + expect(queryByRole("progressbar")).toBeInTheDocument(); + + // Wait for the upload to complete + await waitFor(() => expect(mockDispatch).toHaveBeenCalled()); + + // Check if the progress bar is not displayed after the upload is completed + expect(queryByRole("progressbar")).not.toBeInTheDocument(); + + // Check if the dispatch function has been called with the correct action + expect(mockDispatch).toHaveBeenCalledWith( + expect.objectContaining({ + type: "SEND_ACTION_ACTION", + name: "", + payload: { args: [], action: "testAction" }, + }), + ); + }); + + it("checks if no action is taken when no file is uploaded", async () => { + const mockDispatch = jest.fn(); + const { getByTestId } = render( + + + , + ); + + // Simulate file upload without providing a file + const inputElement = getByTestId("file-selector").querySelector("input"); + if (inputElement) { + fireEvent.change(inputElement, { target: { files: [] } }); + } + + // Check if the dispatch function has not been called + expect(mockDispatch).not.toHaveBeenCalled(); + }); }); diff --git a/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx b/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx index 5c2d071a7d..7193a4b332 100644 --- a/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx +++ b/frontend/taipy-gui/src/components/Taipy/FileSelector.tsx @@ -76,25 +76,27 @@ const FileSelector = (props: FileSelectorProps) => { (value) => { setUpload(false); onAction && dispatch(createSendActionNameAction(id, module, onAction)); - notify && dispatch( - createAlertAction({ atype: "success", message: value, system: false, duration: 3000 }) - ); + notify && + dispatch( + createAlertAction({ atype: "success", message: value, system: false, duration: 3000 }), + ); }, (reason) => { setUpload(false); - notify && dispatch( - createAlertAction({ atype: "error", message: reason, system: false, duration: 3000 }) - ); - } + notify && + dispatch( + createAlertAction({ atype: "error", message: reason, system: false, duration: 3000 }), + ); + }, ); } }, - [state.id, id, onAction, notify, updateVarName, dispatch, module] + [state.id, id, onAction, notify, updateVarName, dispatch, module], ); const handleChange = useCallback( (e: ChangeEvent) => handleFiles(e.target.files, e), - [handleFiles] + [handleFiles], ); const handleDrop = useCallback( @@ -103,7 +105,7 @@ const FileSelector = (props: FileSelectorProps) => { setDropSx(defaultSx); handleFiles(e.dataTransfer?.files, e); }, - [handleFiles] + [handleFiles], ); const handleDragLeave = useCallback(() => { @@ -116,14 +118,12 @@ const FileSelector = (props: FileSelectorProps) => { console.log(evt); const target = evt.currentTarget as HTMLElement; setDropSx((sx) => - sx.minWidth === defaultSx.minWidth && target - ? { minWidth: target.clientWidth + "px" } - : sx + sx.minWidth === defaultSx.minWidth && target ? { minWidth: target.clientWidth + "px" } : sx, ); setDropLabel(dropMessage); handleDragOver(evt); }, - [dropMessage] + [dropMessage], ); useEffect(() => { @@ -144,7 +144,7 @@ const FileSelector = (props: FileSelectorProps) => { }, [handleDrop, handleDragLeave, handleDragOverWithLabel]); return ( -