Skip to content

Commit 11fed82

Browse files
authored
Merge pull request #1762 from dxc-technology/Mil4n0r/duplicate-file_inputs-fix
Updates FileInput component logic to prevent duplicates
2 parents ff3389b + d997d8c commit 11fed82

File tree

3 files changed

+65
-23
lines changed

3 files changed

+65
-23
lines changed

lib/src/file-input/FileInput.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,41 @@ describe("FileInput component tests", () => {
125125
});
126126
});
127127

128+
test("Renders non-duplicated items passed in value when multiple file input", async () => {
129+
const callbackFile = jest.fn();
130+
const { getByLabelText, getByText } = render(
131+
<DxcFileInput
132+
label="File input label"
133+
helperText="File input helper text"
134+
value={allFiles}
135+
callbackFile={callbackFile}
136+
/>
137+
);
138+
await waitFor(() => {
139+
expect(getByText("file1.png")).toBeTruthy();
140+
expect(getByText("file2.txt")).toBeTruthy();
141+
expect(getByText("Error message")).toBeTruthy();
142+
const inputFile = getByLabelText("File input label", { hidden: true });
143+
fireEvent.change(inputFile, { target: { files: [file1] } });
144+
expect(callbackFile).toHaveBeenCalledWith([
145+
{
146+
file: file1,
147+
preview: "data:image/png;base64,ZmlsZTE=",
148+
},
149+
{
150+
error: "Error message",
151+
file: file2,
152+
preview: (
153+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
154+
<path fill="none" d="M0 0h24v24H0V0z" />
155+
<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" />
156+
</svg>
157+
),
158+
},
159+
]);
160+
});
161+
});
162+
128163
test("Renders file items when single file input", async () => {
129164
const callbackFile = jest.fn();
130165
const { getByText } = render(

lib/src/file-input/FileInput.tsx

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { spaces } from "../common/variables";
55
import useTheme from "../useTheme";
66
import useTranslatedLabels from "../useTranslatedLabels";
77
import DxcButton from "../button/Button";
8-
import FileInputPropsType, { RefType } from "./types";
8+
import FileInputPropsType, { FileData, RefType } from "./types";
99
import FileItem from "./FileItem";
1010

1111
const audioIcon = (
@@ -29,20 +29,31 @@ const fileIcon = (
2929
</svg>
3030
);
3131

32-
const getFilePreview = (file) => {
32+
const getFilePreview = async (file: File): Promise<string | React.JSX.Element> => {
3333
if (file.type.includes("video")) return videoIcon;
3434
else if (file.type.includes("audio")) return audioIcon;
35-
else if (file.type.includes("image"))
36-
return new Promise((resolve) => {
35+
else if (file.type.includes("image")) {
36+
return new Promise<string>((resolve) => {
3737
const reader = new FileReader();
3838
reader.readAsDataURL(file);
3939
reader.onload = (e) => {
40-
resolve(e.target.result);
40+
resolve(e.target.result as string);
4141
};
4242
});
43-
else return fileIcon;
43+
} else return fileIcon;
4444
};
4545

46+
const isFileIncluded = (file: FileData, fileList: FileData[]) => {
47+
const fileListInfo = fileList.map((existingFile) => existingFile.file);
48+
return fileListInfo.some(({ name, size, type, lastModified, webkitRelativePath }) =>
49+
name === file.file.name &&
50+
size === file.file.size &&
51+
type === file.file.type &&
52+
lastModified === file.file.lastModified &&
53+
webkitRelativePath === file.file.webkitRelativePath
54+
)
55+
}
56+
4657
const DxcFileInput = React.forwardRef<RefType, FileInputPropsType>(
4758
(
4859
{
@@ -66,38 +77,34 @@ const DxcFileInput = React.forwardRef<RefType, FileInputPropsType>(
6677
ref
6778
): JSX.Element => {
6879
const [isDragging, setIsDragging] = useState(false);
69-
const [files, setFiles] = useState([]);
80+
const [files, setFiles] = useState<FileData[]>([]);
7081
const [fileInputId] = useState(`file-input-${uuidv4()}`);
7182

7283
const colorsTheme = useTheme();
7384
const translatedLabels = useTranslatedLabels();
7485

75-
const checkFileSize = (file) => {
86+
const checkFileSize = (file: File) => {
7687
if (file.size < minSize) return translatedLabels.fileInput.fileSizeGreaterThanErrorMessage;
7788
else if (file.size > maxSize) return translatedLabels.fileInput.fileSizeLessThanErrorMessage;
7889
};
7990

80-
const getFilesToAdd = async (selectedFiles) => {
91+
const getFilesToAdd = async (selectedFiles: File[]) => {
8192
const filesToAdd = await Promise.all(selectedFiles.map((selectedFile) => getFilePreview(selectedFile))).then(
82-
(previews) =>
93+
(previews: string[]) =>
8394
selectedFiles.map((file, index) => {
8495
const fileInfo = { file, error: checkFileSize(file), preview: previews[index] };
8596
return fileInfo;
8697
})
8798
);
88-
return filesToAdd;
99+
return filesToAdd.filter(
100+
file => !isFileIncluded(file, files)
101+
)
89102
};
90103

91-
const addFile = async (selectedFiles) => {
92-
if (multiple) {
93-
const filesToAdd = await getFilesToAdd(selectedFiles);
94-
const finalFiles = [...files, ...filesToAdd];
95-
callbackFile?.(finalFiles);
96-
} else {
97-
const fileToAdd =
98-
selectedFiles.length === 1 ? await getFilesToAdd(selectedFiles) : await getFilesToAdd([selectedFiles[0]]);
99-
callbackFile?.(fileToAdd);
100-
}
104+
const addFile = async (selectedFiles: File[]) => {
105+
const filesToAdd = await getFilesToAdd(multiple ? selectedFiles : selectedFiles.length === 1 ? selectedFiles : [selectedFiles[0]]);
106+
const finalFiles = multiple ? [...files, ...filesToAdd] : filesToAdd;
107+
callbackFile?.(finalFiles);
101108
};
102109

103110
const selectFiles = (e) => {
@@ -157,7 +164,7 @@ const DxcFileInput = React.forwardRef<RefType, FileInputPropsType>(
157164
return { ...file, preview };
158165
}
159166
})
160-
);
167+
) as FileData[];
161168
setFiles(valueFiles);
162169
}
163170
};

lib/src/file-input/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ type Margin = {
55
left?: Space;
66
right?: Space;
77
};
8-
type FileData = {
8+
export type FileData = {
99
/**
1010
* Selected file.
1111
*/

0 commit comments

Comments
 (0)