Skip to content

Commit db8a786

Browse files
committed
fix: improve useSingleFileInput and add test coverage
1 parent 75dec42 commit db8a786

File tree

7 files changed

+84
-10
lines changed

7 files changed

+84
-10
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { renderHook, act } from '@testing-library/react';
2+
import { useSingleFileInput } from './useSingleFileInput';
3+
4+
afterEach(() => {
5+
document.body.innerHTML = '';
6+
});
7+
8+
describe('useSingleFileInput', () => {
9+
it('should create an input element and append it to the body', () => {
10+
const onSetFile = jest.fn();
11+
renderHook(() => useSingleFileInput(onSetFile));
12+
13+
const input = document.createElement('input');
14+
input.type = 'file';
15+
input.style.display = 'none';
16+
document.body.appendChild(input);
17+
expect(input).toBeDefined();
18+
expect((input as HTMLInputElement).style.display).toBe('none');
19+
});
20+
21+
it('should set the accept attribute based on fileType option', () => {
22+
const onSetFile = jest.fn();
23+
renderHook(() => useSingleFileInput(onSetFile, 'image', { fileType: 'video/*' }));
24+
25+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
26+
expect(input.getAttribute('accept')).toBe('video/*');
27+
});
28+
29+
it('should use default accept attribute if not provided', () => {
30+
const onSetFile = jest.fn();
31+
renderHook(() => useSingleFileInput(onSetFile));
32+
33+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
34+
expect(input.getAttribute('accept')).toBe('image/*');
35+
});
36+
37+
it('should trigger click on the input when calling onClick', () => {
38+
const onSetFile = jest.fn();
39+
const { result } = renderHook(() => useSingleFileInput(onSetFile));
40+
const [onClick] = result.current;
41+
42+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
43+
const clickSpy = jest.spyOn(input, 'click');
44+
45+
act(() => {
46+
onClick();
47+
});
48+
49+
expect(clickSpy).toHaveBeenCalled();
50+
});
51+
52+
it('should call onSetFile when a file is selected', () => {
53+
const onSetFile = jest.fn();
54+
renderHook(() => useSingleFileInput(onSetFile, 'test-field'));
55+
56+
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
57+
const file = new File(['foo'], 'foo.txt', { type: 'text/plain' });
58+
59+
Object.defineProperty(input, 'files', {
60+
value: [file],
61+
});
62+
63+
act(() => {
64+
input.dispatchEvent(new Event('change'));
65+
});
66+
67+
expect(onSetFile).toHaveBeenCalledWith(file, expect.any(FormData));
68+
const formData = onSetFile.mock.calls[0][1] as FormData;
69+
expect(formData.get('test-field')).toBe(file);
70+
});
71+
});

apps/meteor/client/hooks/useSingleFileInput.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ import { useRef, useEffect } from 'react';
33

44
export const useSingleFileInput = (
55
onSetFile: (file: File, formData: FormData) => void,
6-
fileType = 'image/*',
76
fileField = 'image',
7+
options?: {
8+
fileType?: string;
9+
},
810
): [onClick: () => void, reset: () => void] => {
911
const ref = useRef<HTMLInputElement>();
12+
const fileType = options?.fileType || 'image/*';
1013

1114
useEffect(() => {
1215
const fileInput = document.createElement('input');

apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const AddCustomEmoji = ({ close, onChange, ...props }: AddCustomEmojiProps): Rea
6060
await saveAction(formData);
6161
}, [emojiFile, name, aliases, saveAction]);
6262

63-
const [clickUpload] = useSingleFileInput(setEmojiPreview, 'image/*', 'emoji');
63+
const [clickUpload] = useSingleFileInput(setEmojiPreview, 'emoji');
6464

6565
const handleChangeName = (e: ChangeEvent<HTMLInputElement>): void => {
6666
if (e.currentTarget.value !== '') {

apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ const EditCustomEmoji = ({ close, onChange, data, ...props }: EditCustomEmojiPro
135135
[setAliases, name],
136136
);
137137

138-
const [clickUpload] = useSingleFileInput(setEmojiFile, 'image/*', 'emoji');
138+
const [clickUpload] = useSingleFileInput(setEmojiFile, 'emoji');
139139

140140
const handleChangeName = (e: ChangeEvent<HTMLInputElement>): void => {
141141
if (e.currentTarget.value !== '') {

apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr
2828
setSound(soundFile);
2929
}, []);
3030

31-
const [clickUpload] = useSingleFileInput(handleChangeFile, 'audio/mp3');
31+
const [clickUpload] = useSingleFileInput(handleChangeFile, 'sound', { fileType: 'audio/mp3' });
3232

3333
const saveAction = useCallback(
3434
// FIXME

apps/meteor/client/views/admin/customSounds/EditSound.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl
2929
const [name, setName] = useState(() => data?.name ?? '');
3030
const [sound, setSound] = useState<
3131
| {
32-
_id: string;
33-
name: string;
34-
extension?: string;
35-
}
32+
_id: string;
33+
name: string;
34+
extension?: string;
35+
}
3636
| File
3737
>(() => data);
3838

@@ -123,7 +123,7 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl
123123
);
124124
}, [_id, close, deleteCustomSound, dispatchToastMessage, onChange, setModal, t]);
125125

126-
const [clickUpload] = useSingleFileInput(handleChangeFile, 'audio/mp3');
126+
const [clickUpload] = useSingleFileInput(handleChangeFile, 'sound', { fileType: 'audio/mp3' });
127127

128128
return (
129129
<>

apps/meteor/client/views/marketplace/AppInstallPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const AppInstallPage = () => {
1515
const { file } = watch();
1616
const { install, isInstalling } = useInstallApp(file);
1717

18-
const [handleUploadButtonClick] = useSingleFileInput((value) => setValue('file', value), 'app');
18+
const [handleUploadButtonClick] = useSingleFileInput((value) => setValue('file', value), 'app', { fileType: '.zip' });
1919

2020
const handleCancel = useCallback(() => {
2121
router.navigate({

0 commit comments

Comments
 (0)