Skip to content

Commit 3f843b9

Browse files
authored
Merge pull request #244 from sebgroup/feat/download-file-util
feat: add download file util
2 parents aa90e5e + 107be67 commit 3f843b9

File tree

6 files changed

+114
-1
lines changed

6 files changed

+114
-1
lines changed

jest.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ if (specific.length) {
2323
testMatch.push("**/*.test.(ts|js)");
2424
}
2525

26-
collectCoverageFrom.push("!src/index.js");
26+
collectCoverageFrom.push("!src/index.(ts|js)");
27+
collectCoverageFrom.push("!src/**/index.(ts|js)");
2728

2829
module.exports = {
2930
setupFilesAfterEnv: ["<rootDir>/setupTests.js"],

setupTests.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
require("jsdom-global/register");
2+
3+
global.URL.createObjectURL = jest.fn();
4+
5+
global.URL.revokeObjectURL = jest.fn();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { downloadFile, FileType } from "./downloadFile";
2+
3+
describe("Util: downloadFile", () => {
4+
const DUMMY_BASE64_FILE_CONTENT: string = "ZHVtbXkgZmlsZQ==";
5+
6+
it("should download base64 content successfully", () => {
7+
downloadFile(DUMMY_BASE64_FILE_CONTENT, "dummy.pdf", FileType.PDF);
8+
});
9+
10+
it("should download binary content successfully", () => {
11+
downloadFile(new ArrayBuffer(20), "dummy.pdf", FileType.PDF);
12+
});
13+
14+
for (const fileType in FileType) {
15+
it(`should download ${fileType} file successfully`, () => {
16+
downloadFile(
17+
DUMMY_BASE64_FILE_CONTENT,
18+
`dummy.${fileType.toLowerCase()}`,
19+
FileType[fileType]
20+
);
21+
});
22+
}
23+
24+
it(`should download unknown file type successfully`, () => {
25+
downloadFile(
26+
DUMMY_BASE64_FILE_CONTENT,
27+
`dummy.txt`,
28+
undefined as unknown as FileType
29+
);
30+
});
31+
});

src/downloadFile/downloadFile.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
export type FileContent = string | ArrayBuffer;
2+
3+
export enum FileType {
4+
JSON = "json",
5+
PDF = "pdf",
6+
SpreadSheet = "xlsx",
7+
XML = "xml",
8+
}
9+
10+
export function downloadFile(
11+
content: FileContent,
12+
fileName: string,
13+
type: FileType
14+
) {
15+
const blob: Blob = toBlob(content, type);
16+
save(blob, fileName);
17+
}
18+
19+
function toBlob(content: FileContent, type: FileType): Blob {
20+
const byteArray: Array<ArrayBuffer | Uint8Array> =
21+
typeof content === "string" ? toByteArray(content) : [content];
22+
return new Blob(byteArray, { type: toContentType(type) });
23+
}
24+
25+
function toByteArray(
26+
base64Data: string,
27+
sliceSize: number = 512
28+
): Array<Uint8Array> {
29+
const byteCharacters: string = window.atob(base64Data);
30+
const byteArrays: Array<Uint8Array> = [];
31+
32+
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
33+
const slice: string = byteCharacters.slice(offset, offset + sliceSize);
34+
const byteNumbers: Array<number> = new Array(slice.length);
35+
36+
for (let i = 0; i < slice.length; i++) {
37+
byteNumbers[i] = slice.charCodeAt(i);
38+
}
39+
40+
byteArrays.push(new Uint8Array(byteNumbers));
41+
}
42+
43+
return byteArrays;
44+
}
45+
46+
function toContentType(type: FileType): string {
47+
switch (type) {
48+
case FileType.PDF:
49+
return "application/pdf";
50+
51+
case FileType.SpreadSheet:
52+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
53+
54+
case FileType.JSON:
55+
return "application/json";
56+
57+
case FileType.XML:
58+
return "text/xml";
59+
60+
default:
61+
return "text/plain";
62+
}
63+
}
64+
65+
function save(blob: Blob, fileName: string): void {
66+
const url: string = window.URL.createObjectURL(blob);
67+
const downloadableLink: HTMLAnchorElement = document.createElement("a");
68+
downloadableLink.download = fileName;
69+
downloadableLink.href = url;
70+
downloadableLink.style.display = "none";
71+
document.body.appendChild(downloadableLink);
72+
downloadableLink.click();
73+
downloadableLink.remove();
74+
window.URL.revokeObjectURL(url);
75+
}

src/downloadFile/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./downloadFile";

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from "./clearTime";
55
export * from "./CookieStorage";
66
export * from "./dateDiff";
77
export * from "./deepCopy";
8+
export * from "./downloadFile";
89
export * from "./formatDate";
910
export * from "./FormValidator";
1011
export * from "./getBrowserName";

0 commit comments

Comments
 (0)