Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add test cases for FileSelector.tsx #1507

Merged
merged 5 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 187 additions & 4 deletions frontend/taipy-gui/src/components/Taipy/FileSelector.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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" });
Expand All @@ -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();
Expand All @@ -98,4 +105,180 @@ 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, getByTestId } = 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 dropLabelElement = getByTestId("file-selector");
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
expect(dropLabelElement.textContent).toBe(" ");
const buttonElement = getByTestId("upload-button");
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
expect(buttonElement).toHaveStyle("min-width: 0px");
});

it("checks if notification is dispatched on file upload completion", async () => {
const mockDispatch = jest.fn();
const { getByTestId } = 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 = getByTestId("file-selector").querySelector("input");
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
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(
<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 = getByTestId("file-selector").querySelector("input");
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
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(
<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 = 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(
<TaipyContext.Provider value={{ state: INITIAL_STATE, dispatch: mockDispatch }}>
<FileSelector label="FileSelector" notify={true} />
</TaipyContext.Provider>,
);

// 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();
});
});
31 changes: 16 additions & 15 deletions frontend/taipy-gui/src/components/Taipy/FileSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -103,7 +105,7 @@ const FileSelector = (props: FileSelectorProps) => {
setDropSx(defaultSx);
handleFiles(e.dataTransfer?.files, e);
},
[handleFiles]
[handleFiles],
);

const handleDragLeave = useCallback(() => {
Expand All @@ -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(() => {
Expand All @@ -144,7 +144,7 @@ const FileSelector = (props: FileSelectorProps) => {
}, [handleDrop, handleDragLeave, handleDragOverWithLabel]);

return (
<label htmlFor={inputId} className={className}>
<label data-testid="file-selector" htmlFor={inputId} className={className}>
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
<input
style={noDisplayStyle}
id={inputId}
Expand All @@ -157,6 +157,7 @@ const FileSelector = (props: FileSelectorProps) => {
<Tooltip title={hover || ""}>
<Button
id={id}
data-testid="upload-button"
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
component="span"
aria-label="upload"
variant="outlined"
Expand Down
Loading