Skip to content

Commit

Permalink
chore: other changes agreed in team discussion
Browse files Browse the repository at this point in the history
  • Loading branch information
robinzigmond committed Oct 17, 2023
1 parent 0fc1f62 commit 6552c29
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ export const FileUploadStatus = ({
iconType = "file_generic",
...statusProps
}: FileUploadStatusProps) => {
const l = useLocale();
const statusMessage = message || l.fileInput.fileUploadStatus();
const locale = useLocale();
const statusMessage = message || locale.fileInput.fileUploadStatus();

let buttonText;
let linkProps;
let progressBar = null;
switch (status) {
case "uploading":
buttonText = l.fileInput.actions.cancel();
buttonText = locale.fileInput.actions.cancel();
progressBar =
(statusProps as StatusUploadingProps).progress === undefined ? (
<LoaderBar size="small" />
Expand All @@ -78,11 +78,11 @@ export const FileUploadStatus = ({
break;
case "previously":
case "completed":
buttonText = l.fileInput.actions.delete();
buttonText = locale.fileInput.actions.delete();
linkProps = { ...statusProps, icon: iconType };
break;
case "error":
buttonText = l.fileInput.actions.clear();
buttonText = locale.fileInput.actions.clear();
break;
// istanbul ignore next
default:
Expand Down
4 changes: 2 additions & 2 deletions src/components/file-input/components.test-pw.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react";
import FileInput, { FileInputProps } from ".";

export default (props: FileInputProps) => {
return <FileInput label="File input" {...props} />;
export default (props: Partial<FileInputProps>) => {
return <FileInput label="File input" onChange={() => {}} {...props} />;
};
2 changes: 1 addition & 1 deletion src/components/file-input/file-input-test.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ export const AllStatuses = () => {
},
];
return statuses.map((status) => (
<FileInput my={20} label="test" uploadStatus={status} />
<FileInput my={20} label="test" uploadStatus={status} onChange={() => {}} />
));
};
8 changes: 4 additions & 4 deletions src/components/file-input/file-input.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface FileInputProps
/** A valid CSS string for the min-width CSS property. Defaults to the same as the maxWidth. */
minWidth?: string;
/** onChange event handler. Accepts a list of all files currently entered to the input. */
onChange?: (files: FileList) => void;
onChange: (files: FileList) => void;
/** used to control how to display the progress of uploaded file(s) within the component */
uploadStatus?: FileUploadStatusProps | FileUploadStatusProps[];
}
Expand Down Expand Up @@ -81,9 +81,9 @@ export const FileInput = React.forwardRef(
}: FileInputProps,
ref: React.ForwardedRef<HTMLInputElement>
) => {
const l = useLocale();
const textOnButton = buttonText || l.fileInput.selectFile();
const mainText = dragAndDropText || l.fileInput.dragAndDrop();
const locale = useLocale();
const textOnButton = buttonText || locale.fileInput.selectFile();
const mainText = dragAndDropText || locale.fileInput.dragAndDrop();

const sizeProps = {
maxHeight: maxHeight || (isVertical ? undefined : minHeight),
Expand Down
76 changes: 50 additions & 26 deletions src/components/file-input/file-input.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,75 @@ import FileInput, { FileUploadStatusProps } from ".";

describe("rendering with no file uploaded", () => {
it("renders a hidden HTML file input element", () => {
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const hiddenInput = screen.queryByLabelText("file input");
expect(hiddenInput).toBeInTheDocument();
expect(hiddenInput).not.toBeVisible();
});

it("renders an HTML button to choose a file to add", () => {
render(<FileInput />);
render(<FileInput onChange={() => {}} />);
expect(
screen.queryByRole("button", { name: "Select file" })
).toBeInTheDocument();
});

it("accepts an accept prop and passes it to the underlying input", () => {
render(<FileInput label="file input" accept="image/*,.pdf" />);
render(
<FileInput label="file input" accept="image/*,.pdf" onChange={() => {}} />
);
const hiddenInput = screen.queryByLabelText("file input");
expect(hiddenInput).toHaveAttribute("accept", "image/*,.pdf");
});

it("accepts a buttonText prop to change the button text", () => {
render(<FileInput buttonText="add a file" />);
render(<FileInput buttonText="add a file" onChange={() => {}} />);
expect(
screen.queryByRole("button", { name: "add a file" })
).toBeInTheDocument();
});

it("accepts a dragAndDropText prop to change the main text", () => {
render(<FileInput dragAndDropText="drop zone" />);
render(<FileInput dragAndDropText="drop zone" onChange={() => {}} />);
expect(screen.queryByText("drop zone")).toBeInTheDocument();
});

it("accepts a label prop and renders it as a visible label", () => {
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const label = screen.getByText("file input");
expect(label).toBeInTheDocument();
expect(label).toBeVisible();
});

it("accepts a disabled prop that disables the button", () => {
render(<FileInput label="file input" disabled />);
render(<FileInput label="file input" disabled onChange={() => {}} />);
const button = screen.queryByRole("button", { name: "Select file" });
expect(button).toBeDisabled();
});

it("accepts an inputHint prop", () => {
render(<FileInput inputHint="help" />);
render(<FileInput inputHint="help" onChange={() => {}} />);
const hintText = screen.getByText("help");
expect(hintText).toBeInTheDocument();
expect(hintText).toBeVisible();
});

it("accepts data tag props", () => {
render(<FileInput data-element="element-test" data-role="role-test" />);
render(
<FileInput
data-element="element-test"
data-role="role-test"
onChange={() => {}}
/>
);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement?.parentElement?.parentElement
?.parentElement as HTMLElement;
rootTagTestRtl(wrapperElement, "file-input", "element-test", "role-test");
});

it("accepts an error prop as a boolean", () => {
render(<FileInput error />);
render(<FileInput error onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement;
assertStyleMatch(
Expand All @@ -80,7 +88,7 @@ describe("rendering with no file uploaded", () => {
});

it("accepts an error prop as a string", () => {
render(<FileInput error="error text" />);
render(<FileInput error="error text" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement;
assertStyleMatch(
Expand All @@ -93,19 +101,21 @@ describe("rendering with no file uploaded", () => {
});

it("accepts a name prop and passes it to the underlying input", () => {
render(<FileInput label="file input" name="input-name" />);
render(
<FileInput label="file input" name="input-name" onChange={() => {}} />
);
const hiddenInput = screen.queryByLabelText("file input");
expect(hiddenInput).toHaveAttribute("name", "input-name");
});

it("accepts a required prop", () => {
render(<FileInput label="file input" required />);
render(<FileInput label="file input" required onChange={() => {}} />);
const label = screen.getByText("file input");
assertStyleMatch({ content: '"*"' }, label, { modifier: "::after" });
});

it("accepts an isVertical prop which removes the CSS max-height", () => {
render(<FileInput isVertical />);
render(<FileInput isVertical onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
assertStyleMatch({ maxHeight: undefined }, wrapperElement);
Expand All @@ -115,14 +125,18 @@ describe("rendering with no file uploaded", () => {
const refObject: React.MutableRefObject<HTMLInputElement | null> = {
current: null,
};
render(<FileInput label="file input" ref={refObject} />);
render(
<FileInput label="file input" ref={refObject} onChange={() => {}} />
);
const hiddenInput = screen.queryByLabelText("file input");
expect(refObject.current).toBe(hiddenInput);
});

it("accepts a callback ref", () => {
const callbackRef = jest.fn();
render(<FileInput label="file input" ref={callbackRef} />);
render(
<FileInput label="file input" ref={callbackRef} onChange={() => {}} />
);
const hiddenInput = screen.queryByLabelText("file input");
expect(callbackRef).toHaveBeenCalledTimes(1);
expect(callbackRef).toHaveBeenCalledWith(hiddenInput);
Expand All @@ -132,7 +146,7 @@ describe("rendering with no file uploaded", () => {
describe("interactions", () => {
it("clicking the button fires a click on the hidden file input", async () => {
const inputOnClick = jest.spyOn(HTMLInputElement.prototype, "click");
render(<FileInput />);
render(<FileInput onChange={() => {}} />);
await userEvent.click(screen.getByRole("button", { name: "Select file" }));
expect(inputOnClick).toHaveBeenCalledTimes(1);
});
Expand All @@ -156,7 +170,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(document.body, {
Expand All @@ -172,7 +186,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" error />);
render(<FileInput label="file input" error onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(document.body, {
Expand All @@ -190,7 +204,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" disabled />);
render(<FileInput label="file input" disabled onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(document.body, {
Expand All @@ -205,7 +219,7 @@ describe("interactions", () => {
});

it("dragging something that isn't a file has no effect", () => {
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(document.body, {
Expand All @@ -221,7 +235,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(wrapperElement, {
Expand All @@ -237,7 +251,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(wrapperElement, {
Expand All @@ -256,7 +270,7 @@ describe("interactions", () => {
const file = new File(["dummy file content"], "foo.txt", {
type: "text/plain",
});
render(<FileInput label="file input" disabled />);
render(<FileInput label="file input" disabled onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(wrapperElement, {
Expand All @@ -269,7 +283,7 @@ describe("interactions", () => {
});

it("dragging something that isn't a file over the input area has no effect", () => {
render(<FileInput label="file input" />);
render(<FileInput label="file input" onChange={() => {}} />);
const wrapperElement = screen.getByText("Drag and drop your file")
.parentElement as HTMLElement;
fireEvent.dragOver(wrapperElement, {
Expand Down Expand Up @@ -334,7 +348,13 @@ describe("with uploadStatus prop set", () => {
] as FileUploadStatusProps[])(
"when the status is `$status`, the hidden file input and the button are not rendered",
(statusProps) => {
render(<FileInput label="file input" uploadStatus={statusProps} />);
render(
<FileInput
label="file input"
uploadStatus={statusProps}
onChange={() => {}}
/>
);
const hiddenInput = screen.queryByLabelText("file input");
const button = screen.queryByRole("button", { name: "Select file" });
expect(hiddenInput).not.toBeInTheDocument();
Expand All @@ -351,6 +371,7 @@ describe("with uploadStatus prop set", () => {
onAction: () => {},
progress: 30,
}}
onChange={() => {}}
/>
);
expect(screen.queryByRole("progressbar")).toBeInTheDocument();
Expand All @@ -365,6 +386,7 @@ describe("with uploadStatus prop set", () => {
onAction: () => {},
href: "http://carbon.sage.com",
}}
onChange={() => {}}
/>
);
expect(screen.queryByRole("link", { name: "foo.pdf" })).toBeInTheDocument();
Expand All @@ -380,6 +402,7 @@ describe("with uploadStatus prop set", () => {
onAction: () => {},
href: "http://carbon.sage.com",
}}
onChange={() => {}}
/>
);
expect(screen.queryByRole("link", { name: "foo.pdf" })).toBeInTheDocument();
Expand All @@ -394,6 +417,7 @@ describe("with uploadStatus prop set", () => {
filename: "foo.pdf",
onAction: () => {},
}}
onChange={() => {}}
/>
);
const wrapperElement = screen.getByText("File upload status").parentElement
Expand Down
3 changes: 3 additions & 0 deletions src/components/file-input/file-input.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,9 @@ The `uploadStatus` prop is used to indicate when one or more files have been add
the upload - and allowing users to cancel or remove uploaded files. It is required to set this (to an actual props object, or to
a non-empty array for multiple files) in order for the users to see any indication of the uploaded file(s).

For this reason the `onChange` function prop is mandatory - without providing an implementation for this event handler that updates the
components `uploadStatus`, there will be no visual change once a file has been added to the input.

Here is a relatively simple example of using a [FileReader](https://developer.mozilla.org/en-US/docs/Web/API/FileReader) to track progress purely
on the client side, resulting in storing the file locally as a `data:` URL. This is a way to show users a preview of their file if you do not intend
to track upload progress on the server, or do not actually send the files to a server until form submission.
Expand Down
Loading

0 comments on commit 6552c29

Please sign in to comment.