From 7714c0b6c2d6d57e0c32c2257b73e79f58f32e20 Mon Sep 17 00:00:00 2001 From: PatrykBuniX Date: Tue, 25 Oct 2022 09:50:23 +0200 Subject: [PATCH 1/4] runfix: enable sending the same asset multiple times in a row --- .../message-list/InputBarControls/ControlButtons.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/script/page/message-list/InputBarControls/ControlButtons.tsx b/src/script/page/message-list/InputBarControls/ControlButtons.tsx index cc58803b1a2..6fbb397697c 100644 --- a/src/script/page/message-list/InputBarControls/ControlButtons.tsx +++ b/src/script/page/message-list/InputBarControls/ControlButtons.tsx @@ -61,6 +61,15 @@ const ControlButtons: React.FC = ({ const pingTooltip = t('tooltipConversationPing'); + const handleFileChange = (event: React.ChangeEvent) => { + const {files} = event.target; + if (!files) { + return; + } + onSelectFiles(Array.from(files)); + event.target.value = ''; + }; + if (isEditing) { return (
  • @@ -134,7 +143,7 @@ const ControlButtons: React.FC = ({ accept={acceptedFileTypes ?? null} id="conversation-input-bar-files" tabIndex={-1} - onChange={({target: {files}}) => files && onSelectFiles(Array.from(files))} + onChange={handleFileChange} type="file" /> From cdf33a2a950056fe4df143aea62922e73c5c9288 Mon Sep 17 00:00:00 2001 From: PatrykBuniX Date: Tue, 25 Oct 2022 12:20:12 +0200 Subject: [PATCH 2/4] refactor: move AssetUploadButton to separate component and reset file input by rerendering --- .../page/message-list/AssetUploadButton.tsx | 69 +++++++++++++++++++ .../InputBarControls/ControlButtons.tsx | 32 +-------- 2 files changed, 71 insertions(+), 30 deletions(-) create mode 100644 src/script/page/message-list/AssetUploadButton.tsx diff --git a/src/script/page/message-list/AssetUploadButton.tsx b/src/script/page/message-list/AssetUploadButton.tsx new file mode 100644 index 00000000000..c17ef1d7d8a --- /dev/null +++ b/src/script/page/message-list/AssetUploadButton.tsx @@ -0,0 +1,69 @@ +/* + * Wire + * Copyright (C) 2022 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {useState, useRef} from 'react'; +import {Config} from '../../Config'; +import {t} from 'Util/LocalizerUtil'; +import Icon from 'Components/Icon'; + +interface AssetUploadButtonProps { + onSelectFiles: (files: File[]) => void; +} + +export const AssetUploadButton = ({onSelectFiles}: AssetUploadButtonProps) => { + const [inputKey, setInputKey] = useState(Date.now()); + const acceptedFileTypes = Config.getConfig().FEATURE.ALLOWED_FILE_UPLOAD_EXTENSIONS.join(','); + const fileRef = useRef(null!); + + const handleFileChange = (event: React.ChangeEvent) => { + const {files} = event.target; + + if (!files) { + return; + } + + onSelectFiles(Array.from(files)); + + //reset file input's value by re-rendering the component + setInputKey(Date.now()); + }; + + return ( + + ); +}; diff --git a/src/script/page/message-list/InputBarControls/ControlButtons.tsx b/src/script/page/message-list/InputBarControls/ControlButtons.tsx index 6fbb397697c..1a88c948ecc 100644 --- a/src/script/page/message-list/InputBarControls/ControlButtons.tsx +++ b/src/script/page/message-list/InputBarControls/ControlButtons.tsx @@ -23,6 +23,7 @@ import MessageTimerButton from '../MessageTimerButton'; import {t} from 'Util/LocalizerUtil'; import {Conversation} from 'src/script/entity/Conversation'; import {Config} from '../../../Config'; +import {AssetUploadButton} from '../AssetUploadButton'; export type ControlButtonsProps = { input: string; @@ -54,22 +55,11 @@ const ControlButtons: React.FC = ({ onClickGif, }) => { const acceptedImageTypes = Config.getConfig().ALLOWED_IMAGE_TYPES.join(','); - const acceptedFileTypes = Config.getConfig().FEATURE.ALLOWED_FILE_UPLOAD_EXTENSIONS.join(','); const imageRef = useRef(null!); - const fileRef = useRef(null!); const pingTooltip = t('tooltipConversationPing'); - const handleFileChange = (event: React.ChangeEvent) => { - const {files} = event.target; - if (!files) { - return; - } - onSelectFiles(Array.from(files)); - event.target.value = ''; - }; - if (isEditing) { return (
  • @@ -128,25 +118,7 @@ const ControlButtons: React.FC = ({
  • - +
  • )} From 78795d1045116e4e9c1914c8292509e795336137 Mon Sep 17 00:00:00 2001 From: PatrykBuniX Date: Tue, 25 Oct 2022 12:21:09 +0200 Subject: [PATCH 3/4] test: AssetUploadButton component test --- .../message-list/AssetUploadButton.test.tsx | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/script/page/message-list/AssetUploadButton.test.tsx diff --git a/src/script/page/message-list/AssetUploadButton.test.tsx b/src/script/page/message-list/AssetUploadButton.test.tsx new file mode 100644 index 00000000000..514c905ae72 --- /dev/null +++ b/src/script/page/message-list/AssetUploadButton.test.tsx @@ -0,0 +1,67 @@ +/* + * Wire + * Copyright (C) 2022 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {render, fireEvent} from '@testing-library/react'; +import {AssetUploadButton} from './AssetUploadButton'; + +jest.mock('../../Config', () => ({ + Config: { + getConfig: () => ({ + ALLOWED_IMAGE_TYPES: ['image/gif', 'image/avif'], + FEATURE: {ALLOWED_FILE_UPLOAD_EXTENSIONS: ['*']}, + }), + }, +})); + +const pngFile = new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'}); + +describe('AssetUploadButton', () => { + it('Does call onSelectFiles with uploaded file', async () => { + const onSelectFiles = jest.fn(); + + const {getByTestId} = render(); + + const fileInput = getByTestId('conversation-input-bar-files') as HTMLInputElement; + fireEvent.change(fileInput, { + target: {files: [pngFile]}, + }); + + expect(onSelectFiles).toHaveBeenCalledWith([pngFile]); + }); + + it('Does reset file input after upload', async () => { + const onSelectFiles = jest.fn(); + + const {getByTestId} = render(); + + const getFileInput = () => getByTestId('conversation-input-bar-files') as HTMLInputElement; + let fileInput = getFileInput(); + + fireEvent.change(fileInput, { + target: {files: [pngFile]}, + }); + + expect(fileInput.files?.[0].name).toEqual(pngFile.name); + expect(onSelectFiles).toHaveBeenCalledWith([pngFile]); + + //input did reset we grab new reference + fileInput = getFileInput(); + expect(fileInput.files).toHaveLength(0); + }); +}); From f98c289f00e370a6fdf01b296bc0bdd6562c0662 Mon Sep 17 00:00:00 2001 From: PatrykBuniX Date: Tue, 25 Oct 2022 13:53:54 +0200 Subject: [PATCH 4/4] refactor: wrap upload button with form to reset it easily after upload --- .../message-list/AssetUploadButton.test.tsx | 23 +++++---- .../page/message-list/AssetUploadButton.tsx | 48 +++++++++---------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/script/page/message-list/AssetUploadButton.test.tsx b/src/script/page/message-list/AssetUploadButton.test.tsx index 514c905ae72..75a50f2a3f1 100644 --- a/src/script/page/message-list/AssetUploadButton.test.tsx +++ b/src/script/page/message-list/AssetUploadButton.test.tsx @@ -32,12 +32,12 @@ jest.mock('../../Config', () => ({ const pngFile = new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'}); describe('AssetUploadButton', () => { - it('Does call onSelectFiles with uploaded file', async () => { + it('Does call onSelectFiles with uploaded file', () => { const onSelectFiles = jest.fn(); - const {getByTestId} = render(); + const {container} = render(); + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; - const fileInput = getByTestId('conversation-input-bar-files') as HTMLInputElement; fireEvent.change(fileInput, { target: {files: [pngFile]}, }); @@ -45,23 +45,22 @@ describe('AssetUploadButton', () => { expect(onSelectFiles).toHaveBeenCalledWith([pngFile]); }); - it('Does reset file input after upload', async () => { + it('Does reset a form with input after upload', () => { const onSelectFiles = jest.fn(); - const {getByTestId} = render(); + const {container} = render(); - const getFileInput = () => getByTestId('conversation-input-bar-files') as HTMLInputElement; - let fileInput = getFileInput(); + const form = container.querySelector('form'); + jest.spyOn(form!, 'reset'); + + const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement; fireEvent.change(fileInput, { target: {files: [pngFile]}, }); - expect(fileInput.files?.[0].name).toEqual(pngFile.name); expect(onSelectFiles).toHaveBeenCalledWith([pngFile]); - - //input did reset we grab new reference - fileInput = getFileInput(); - expect(fileInput.files).toHaveLength(0); + expect(fileInput.files?.[0].name).toEqual(pngFile.name); + expect(form!.reset).toHaveBeenCalled(); }); }); diff --git a/src/script/page/message-list/AssetUploadButton.tsx b/src/script/page/message-list/AssetUploadButton.tsx index c17ef1d7d8a..086738c5dc5 100644 --- a/src/script/page/message-list/AssetUploadButton.tsx +++ b/src/script/page/message-list/AssetUploadButton.tsx @@ -17,7 +17,7 @@ * */ -import {useState, useRef} from 'react'; +import {useRef} from 'react'; import {Config} from '../../Config'; import {t} from 'Util/LocalizerUtil'; import Icon from 'Components/Icon'; @@ -27,9 +27,9 @@ interface AssetUploadButtonProps { } export const AssetUploadButton = ({onSelectFiles}: AssetUploadButtonProps) => { - const [inputKey, setInputKey] = useState(Date.now()); const acceptedFileTypes = Config.getConfig().FEATURE.ALLOWED_FILE_UPLOAD_EXTENSIONS.join(','); const fileRef = useRef(null!); + const formRef = useRef(null); const handleFileChange = (event: React.ChangeEvent) => { const {files} = event.target; @@ -40,30 +40,30 @@ export const AssetUploadButton = ({onSelectFiles}: AssetUploadButtonProps) => { onSelectFiles(Array.from(files)); - //reset file input's value by re-rendering the component - setInputKey(Date.now()); + //reset file input's value resetting form wrapper + formRef.current?.reset(); }; return ( - +
    + +
    ); };