Skip to content

Commit 9bcf0f1

Browse files
authored
Merge pull request #108 from sendbird/fix/file-utils
fix(CLNP-386): file utils
2 parents 6fc1fa5 + acd9942 commit 9bcf0f1

File tree

6 files changed

+190
-12
lines changed

6 files changed

+190
-12
lines changed

jest.setup.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55
// https://github.com/facebook/react/issues/20756#issuecomment-780927519
66
// eslint-disable-next-line no-undef
77
delete global.MessageChannel;
8+
// eslint-disable-next-line no-undef
9+
global.fetch = require('node-fetch');

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"eslint-config-prettier": "^8.5.0",
5353
"jest": "^29.4.3",
5454
"lerna": "^5.1.6",
55+
"node-fetch": "2.x",
5556
"patch-package": "^6.4.7",
5657
"postinstall-postinstall": "^2.1.0",
5758
"prettier": "^2.7.1",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import normalizeFile from '../../utils/normalizeFile';
2+
3+
const Images = {
4+
png: 'https://user-images.githubusercontent.com/26326015/253041267-4fd6c9f8-7bb4-4197-813c-43a45d0de95e.png',
5+
jpeg: 'https://user-images.githubusercontent.com/26326015/253041558-3028125e-a016-402c-a9bd-d30b1831d19b.jpg',
6+
};
7+
8+
describe('normalizeFile', () => {
9+
const dateNow = Date.now();
10+
11+
beforeAll(() => {
12+
Date.now = jest.fn(() => dateNow);
13+
});
14+
15+
it('should return null if uri is not provided', async () => {
16+
const result = await normalizeFile({ uri: null, size: null, name: null, type: null });
17+
expect(result).toBeNull();
18+
});
19+
20+
it('should return size as 0 if size is not provided', async () => {
21+
const result = await normalizeFile({ uri: 'uri', size: null, name: null, type: 'image/png' });
22+
expect(result?.size).toBe(0);
23+
});
24+
25+
it('should set name as current timestamp if name is not provided', async () => {
26+
const result = await normalizeFile({ uri: 'uri', size: null, name: null, type: 'image/png' });
27+
expect(result).toStrictEqual({ uri: 'uri', size: 0, name: `${dateNow}.png`, type: 'image/png' });
28+
});
29+
30+
it('should file name contain extension if type is provided', async () => {
31+
const result = await normalizeFile({ uri: 'uri', size: null, name: 'filename', type: 'image/png' });
32+
expect(result).toStrictEqual({ uri: 'uri', size: 0, name: 'filename.png', type: 'image/png' });
33+
});
34+
35+
it('should not override extension of name if name already has extension', async () => {
36+
const withExtName = await normalizeFile({ uri: Images.jpeg, size: 1, name: 'fileName.jpeg', type: 'image/png' });
37+
expect(withExtName).toStrictEqual({ uri: Images.jpeg, size: 1, name: 'fileName.jpeg', type: 'image/png' });
38+
});
39+
40+
it('should get type from extension of name if type is invalid', async () => {
41+
const emptyResult = await normalizeFile({ uri: 'uri', size: null, name: 'fromName.png', type: '' });
42+
expect(emptyResult).toStrictEqual({ uri: 'uri', size: 0, name: 'fromName.png', type: 'image/png' });
43+
44+
const nullResult = await normalizeFile({ uri: 'uri', size: null, name: 'fromName.jpeg', type: null });
45+
expect(nullResult).toStrictEqual({ uri: 'uri', size: 0, name: 'fromName.jpeg', type: 'image/jpeg' });
46+
47+
const undefResult = await normalizeFile({ uri: 'uri', size: null, name: 'fromName.gif', type: undefined });
48+
expect(undefResult).toStrictEqual({ uri: 'uri', size: 0, name: 'fromName.gif', type: 'image/gif' });
49+
50+
const invalidResult = await normalizeFile({ uri: 'uri', size: null, name: 'fromName.pdf', type: 'invalid' });
51+
expect(invalidResult).toStrictEqual({ uri: 'uri', size: 0, name: 'fromName.pdf', type: 'application/pdf' });
52+
});
53+
54+
it('should get type from uri if name and type are invalid', async () => {
55+
const emptyResult = await normalizeFile({ uri: Images.png, size: 111, name: '', type: '' });
56+
expect(emptyResult).toStrictEqual({ uri: Images.png, size: 111, name: `${dateNow}.png`, type: 'image/png' });
57+
58+
const nullResult = await normalizeFile({ uri: Images.jpeg, size: 222, name: null, type: null });
59+
expect(nullResult).toStrictEqual({ uri: Images.jpeg, size: 222, name: `${dateNow}.jpg`, type: 'image/jpeg' });
60+
61+
const undefResult = await normalizeFile({ uri: Images.jpeg, size: 333, name: undefined, type: undefined });
62+
expect(undefResult).toStrictEqual({ uri: Images.jpeg, size: 333, name: `${dateNow}.jpg`, type: 'image/jpeg' });
63+
64+
const invalidResult = await normalizeFile({ uri: Images.jpeg, size: null, name: 'name', type: 'invalid' });
65+
expect(invalidResult).toStrictEqual({ uri: Images.jpeg, size: 0, name: 'name.jpg', type: 'image/jpeg' });
66+
67+
const invalidResult2 = await normalizeFile({ uri: Images.jpeg, size: null, name: 'name.jpeg', type: 'invalid' });
68+
expect(invalidResult2).toStrictEqual({ uri: Images.jpeg, size: 0, name: 'name.jpeg', type: 'image/jpeg' });
69+
});
70+
});

packages/uikit-utils/src/__tests__/shared/file.test.ts

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,71 @@ import {
22
getDownscaleSize,
33
getFileExtension,
44
getFileExtensionFromMime,
5+
getFileExtensionFromUri,
6+
getFileType,
57
getMimeFromFileExtension,
68
normalizeFileName,
79
parseMimeType,
810
} from '../../shared/file';
911

12+
describe('getFileType', function () {
13+
it('should return the proper file type with mime-type', () => {
14+
expect(getFileType('image/jpeg')).toBe('image');
15+
expect(getFileType('image/png')).toBe('image');
16+
expect(getFileType('image/gif')).toBe('image');
17+
expect(getFileType('video/mp4')).toBe('video');
18+
expect(getFileType('video/quicktime')).toBe('video');
19+
expect(getFileType('audio/mpeg')).toBe('audio');
20+
expect(getFileType('audio/mp3')).toBe('audio');
21+
expect(getFileType('audio/ogg')).toBe('audio');
22+
expect(getFileType('application/pdf')).toBe('file');
23+
expect(getFileType('application/json')).toBe('file');
24+
expect(getFileType('application/zip')).toBe('file');
25+
expect(getFileType('application/x-gzip')).toBe('file');
26+
expect(getFileType('text/plain')).toBe('file');
27+
});
28+
29+
it('should return the proper file type with file extension', () => {
30+
expect(getFileType('.jpeg')).toBe('image');
31+
expect(getFileType('jpeg')).toBe('image');
32+
expect(getFileType('.jpg')).toBe('image');
33+
expect(getFileType('jpg')).toBe('image');
34+
expect(getFileType('.png')).toBe('image');
35+
expect(getFileType('png')).toBe('image');
36+
expect(getFileType('.gif')).toBe('image');
37+
expect(getFileType('gif')).toBe('image');
38+
expect(getFileType('.mp4')).toBe('video');
39+
expect(getFileType('mp4')).toBe('video');
40+
expect(getFileType('.mov')).toBe('video');
41+
expect(getFileType('mov')).toBe('video');
42+
expect(getFileType('.mpeg')).toBe('video');
43+
expect(getFileType('mpeg')).toBe('video');
44+
expect(getFileType('.mp3')).toBe('audio');
45+
expect(getFileType('mp3')).toBe('audio');
46+
expect(getFileType('.ogg')).toBe('audio');
47+
expect(getFileType('ogg')).toBe('audio');
48+
expect(getFileType('.wav')).toBe('audio');
49+
expect(getFileType('wav')).toBe('audio');
50+
expect(getFileType('.pdf')).toBe('file');
51+
expect(getFileType('pdf')).toBe('file');
52+
expect(getFileType('.json')).toBe('file');
53+
expect(getFileType('json')).toBe('file');
54+
expect(getFileType('.zip')).toBe('file');
55+
expect(getFileType('zip')).toBe('file');
56+
expect(getFileType('.gzip')).toBe('file');
57+
expect(getFileType('gzip')).toBe('file');
58+
expect(getFileType('.txt')).toBe('file');
59+
expect(getFileType('txt')).toBe('file');
60+
});
61+
62+
it('should return the proper file type with type', () => {
63+
expect(getFileType('image')).toBe('image');
64+
expect(getFileType('video')).toBe('video');
65+
expect(getFileType('audio')).toBe('audio');
66+
expect(getFileType('invalid-value')).toBe('file');
67+
});
68+
});
69+
1070
describe('getDownscaleSize', () => {
1171
it('should return the original size when no resizing is necessary', () => {
1272
const origin = { width: 500, height: 400 };
@@ -66,6 +126,13 @@ describe('normalizeFileName', () => {
66126
expect(result).toEqual(`${fileName}.${extension}`);
67127
});
68128

129+
it('should append dot+extension to filename if it does not exist', () => {
130+
const fileName = 'testFile';
131+
const extension = '.txt';
132+
const result = normalizeFileName(fileName, extension);
133+
expect(result).toEqual(`${fileName}${extension}`);
134+
});
135+
69136
it('should return filename as is if it already contains extension', () => {
70137
const fileName = 'testFile.txt';
71138
const extension = 'txt';
@@ -121,12 +188,12 @@ describe('getFileExtensionFromMime', () => {
121188
});
122189

123190
it('should return correct extension for known mime type', () => {
124-
expect(getFileExtensionFromMime('image/jpeg')).toMatch(/jpg|jpeg/);
125-
expect(getFileExtensionFromMime('video/mp4')).toBe('mp4');
126-
expect(getFileExtensionFromMime('audio/mpeg')).toBe('mp3');
127-
expect(getFileExtensionFromMime('text/plain')).toBe('txt');
128-
expect(getFileExtensionFromMime('application/pdf')).toBe('pdf');
129-
expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('xls');
191+
expect(getFileExtensionFromMime('image/jpeg')).toMatch(/\.jpg|\.jpeg/);
192+
expect(getFileExtensionFromMime('video/mp4')).toBe('.mp4');
193+
expect(getFileExtensionFromMime('audio/mpeg')).toBe('.mp3');
194+
expect(getFileExtensionFromMime('text/plain')).toBe('.txt');
195+
expect(getFileExtensionFromMime('application/pdf')).toBe('.pdf');
196+
expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('.xls');
130197
});
131198

132199
it('should return empty string for unknown mime type', () => {
@@ -143,6 +210,14 @@ describe('getMimeFromFileExtension', () => {
143210
);
144211
});
145212

213+
it('should return the correct MIME type for a given file extension with dot', () => {
214+
expect(getMimeFromFileExtension('..pdf')).toEqual('application/pdf');
215+
expect(getMimeFromFileExtension('.jpg')).toEqual('image/jpeg');
216+
expect(getMimeFromFileExtension('.docx')).toEqual(
217+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
218+
);
219+
});
220+
146221
it('should return an empty string for null or undefined input', () => {
147222
expect(getMimeFromFileExtension(null)).toEqual('');
148223
expect(getMimeFromFileExtension(undefined)).toEqual('');
@@ -179,3 +254,18 @@ describe('getFileExtension', () => {
179254
expect(getFileExtension('/path/to/file?query=string;key=test123')).toEqual('');
180255
});
181256
});
257+
258+
describe('getFileExtensionFromUri', () => {
259+
it('should return the correct file extension for a given file uri', async () => {
260+
await expect(
261+
getFileExtensionFromUri(
262+
'https://user-images.githubusercontent.com/26326015/253041267-4fd6c9f8-7bb4-4197-813c-43a45d0de95e.png',
263+
),
264+
).resolves.toEqual('.png');
265+
await expect(
266+
getFileExtensionFromUri(
267+
'https://user-images.githubusercontent.com/26326015/253041558-3028125e-a016-402c-a9bd-d30b1831d19b.jpg',
268+
),
269+
).resolves.toEqual('.jpg');
270+
});
271+
});

packages/uikit-utils/src/shared/file.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ const EXTENSION_MIME_MAP = {
3939
} as Record<string, string>;
4040

4141
export const imageExtRegex = /jpeg|jpg|png|webp|gif/i;
42-
export const audioExtRegex = /3gp|aac|aax|act|aiff|flac|gsm|m4a|m4b|m4p|tta|wma|mp3|webm|wav/i;
43-
export const videoExtRegex = /mov|vod|mp4|avi/i;
42+
export const audioExtRegex = /3gp|aac|aax|act|aiff|flac|gsm|m4a|m4b|m4p|tta|wma|mp3|webm|wav|ogg/i;
43+
export const videoExtRegex = /mov|vod|mp4|avi|mpeg|ogv/i;
4444
export const getFileType = (extensionOrType: string) => {
4545
const lowerCased = extensionOrType.toLowerCase();
4646

@@ -157,7 +157,10 @@ export function getFileExtensionFromMime(mimeType?: string | null): string {
157157
acc[value] = key;
158158
return acc;
159159
}, {} as Record<string, string>);
160-
return MIME_EXTENSION_MAP[mimeType.toLowerCase()] || '';
160+
161+
const extension = MIME_EXTENSION_MAP[mimeType.toLowerCase()];
162+
if (extension) return '.' + extension;
163+
return '';
161164
}
162165

163166
/**
@@ -168,7 +171,11 @@ export function getFileExtensionFromMime(mimeType?: string | null): string {
168171
*/
169172
export function getMimeFromFileExtension(ext?: string | null) {
170173
if (!ext) return '';
171-
return EXTENSION_MIME_MAP[ext.toLowerCase()] || '';
174+
175+
const sliceIdx = ext.lastIndexOf('.');
176+
const extWithoutDot = sliceIdx === -1 ? ext : ext.slice(sliceIdx + 1);
177+
178+
return EXTENSION_MIME_MAP[extWithoutDot.toLowerCase()] || '';
172179
}
173180

174181
/**
@@ -188,8 +195,9 @@ export function getFileExtension(filePath: string) {
188195
else return result;
189196
}
190197

191-
export function getFileExtensionFromUri(uri: string) {
192-
return fetch(uri).then((response) => response.headers.get('content-type'));
198+
export async function getFileExtensionFromUri(uri: string) {
199+
const type = await fetch(uri).then((response) => response.headers.get('content-type'));
200+
return getFileExtensionFromMime(type);
193201
}
194202

195203
export function isImage(filePath: string, mimeType?: string) {

yarn.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11440,6 +11440,13 @@ [email protected]:
1144011440
dependencies:
1144111441
whatwg-url "^5.0.0"
1144211442

11443+
11444+
version "2.6.12"
11445+
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
11446+
integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
11447+
dependencies:
11448+
whatwg-url "^5.0.0"
11449+
1144311450
node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7:
1144411451
version "2.6.9"
1144511452
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"

0 commit comments

Comments
 (0)