Pārlūkot izejas kodu

Merge pull request #1507 from Avaiga/test/fileSelector

add test cases for FileSelector.tsx
Nam Nguyen 10 mēneši atpakaļ
vecāks
revīzija
1e9c8a1f84

+ 178 - 4
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(
             <TaipyContext.Provider value={{ state, dispatch }}>
                 <FileSelector label="FileSelector" onAction="on_action" />
-            </TaipyContext.Provider>
+            </TaipyContext.Provider>,
         );
         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(<FileSelector label="FileSelectorDrop" dropMessage="drop here those files" />);
+        const { getByRole, getByText } = render(
+            <FileSelector label="FileSelectorDrop" dropMessage="drop here those files" />,
+        );
         const elt = getByRole("button");
         const inputElt = elt.parentElement?.querySelector("input");
         expect(inputElt).toBeInTheDocument();
@@ -98,4 +105,171 @@ describe("FileSelector Component", () => {
                 },
             });
     });
+    it("handles drag over event", () => {
+        const { getByRole } = render(<FileSelector label="FileSelectorDrag" />);
+        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(<FileSelector label="FileSelectorDrop" />);
+        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 } = render(<FileSelector />);
+        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 labelElement = elt.querySelector("span");
+        expect(labelElement!.textContent).toBe("");
+        expect(elt).toHaveStyle("min-width: 0px");
+    });
+
+    it("checks if notification is dispatched on file upload completion", async () => {
+        const mockDispatch = jest.fn();
+        const { getByLabelText } = render(
+            <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
+                <FileSelector label="FileSelector" notify={true} />
+            </TaipyContext.Provider>,
+        );
+
+        // Simulate file upload
+        const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
+        const inputElement = getByLabelText("FileSelector");
+        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 { getByLabelText } = render(
+            <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
+                <FileSelector label="FileSelector" notify={true} />
+            </TaipyContext.Provider>,
+        );
+
+        // Simulate file upload
+        const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
+        const inputElement = getByLabelText("FileSelector");
+        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 { getByLabelText, queryByRole } = render(
+            <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
+                <FileSelector label="FileSelector" notify={true} onAction="testAction" />
+            </TaipyContext.Provider>,
+        );
+
+        // Simulate file upload
+        const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
+        const inputElement = getByLabelText("FileSelector");
+        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 { getByLabelText } = render(
+            <TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
+                <FileSelector label="FileSelector" notify={true} />
+            </TaipyContext.Provider>,
+        );
+
+        // Simulate file upload without providing a file
+        const inputElement = getByLabelText("FileSelector");
+        fireEvent.change(inputElement, { target: { files: [] } });
+
+        // Check if the dispatch function has not been called
+        expect(mockDispatch).not.toHaveBeenCalled();
+    });
 });

+ 14 - 14
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<HTMLInputElement>) => 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(() => {