Skip to content

Commit ab428f9

Browse files
authored
runfix: upload same image multiple times (#13969)
* runfix: wrapp image upload button with form and move it to separate file * test: add ImageUploadButton component test * refactor: organize directory structure in page folder * refactor: reorganise folder structure
1 parent 22b582f commit ab428f9

File tree

17 files changed

+271
-60
lines changed

17 files changed

+271
-60
lines changed

src/script/page/AccentColorPicker.test.tsx renamed to src/script/page/AccentColorPicker/AccentColorPicker.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import {AccentColor} from '@wireapp/commons';
2121

2222
import {render, act} from '@testing-library/react';
2323

24-
import AccentColorPicker, {AccentColorPickerProps} from './AccentColorPicker';
25-
import {User} from '../entity/User';
24+
import {AccentColorPicker, AccentColorPickerProps} from './';
25+
import {User} from '../../entity/User';
2626
import ko from 'knockout';
2727

2828
describe('AccentColorPicker', () => {

src/script/page/AccentColorPicker.tsx renamed to src/script/page/AccentColorPicker/AccentColorPicker.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ import React from 'react';
2121
import {CSS_SQUARE} from 'Util/CSSMixin';
2222
import {t} from 'Util/LocalizerUtil';
2323
import {CSSObject} from '@emotion/serialize';
24-
import {User} from '../entity/User';
24+
import {User} from '../../entity/User';
2525
import {useKoSubscribableChildren} from 'Util/ComponentUtil';
26-
import {ACCENT_ID} from '../Config';
26+
import {ACCENT_ID} from '../../Config';
2727

2828
export interface AccentColorPickerProps {
2929
doSetAccentColor: (id: number) => void;
@@ -37,7 +37,7 @@ const headerStyles: CSSObject = {
3737
textAlign: 'center',
3838
};
3939

40-
const AccentColorPicker: React.FunctionComponent<AccentColorPickerProps> = ({user, doSetAccentColor}) => {
40+
export const AccentColorPicker: React.FunctionComponent<AccentColorPickerProps> = ({user, doSetAccentColor}) => {
4141
const {accent_id: accentId} = useKoSubscribableChildren(user, ['accent_id']);
4242
return (
4343
<>
@@ -142,5 +142,3 @@ const AccentColorPicker: React.FunctionComponent<AccentColorPickerProps> = ({use
142142
</>
143143
);
144144
};
145-
146-
export default AccentColorPicker;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
export * from './AccentColorPicker';

src/script/page/AppLock.test.tsx renamed to src/script/page/AppLock/AppLock.test.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ import {WebAppEvents} from '@wireapp/webapp-events';
2323
import {amplify} from 'amplify';
2424
import {FeatureStatus} from '@wireapp/api-client/src/team/feature/';
2525

26-
import type {ClientRepository} from '../client/ClientRepository';
27-
import AppLock, {APPLOCK_STATE} from './AppLock';
28-
import {AppLockState} from '../user/AppLockState';
29-
import {AppLockRepository} from '../user/AppLockRepository';
30-
import {UserState} from '../user/UserState';
26+
import type {ClientRepository} from '../../client/ClientRepository';
27+
import AppLock, {APPLOCK_STATE} from './';
28+
import {AppLockState} from '../../user/AppLockState';
29+
import {AppLockRepository} from '../../user/AppLockRepository';
30+
import {UserState} from '../../user/UserState';
3131
import {createRandomUuid} from 'Util/util';
32-
import {TeamState} from '../team/TeamState';
32+
import {TeamState} from '../../team/TeamState';
3333
import {render} from '@testing-library/react';
3434

3535
// https://github.com/jedisct1/libsodium.js/issues/235

src/script/page/AppLock.tsx renamed to src/script/page/AppLock/AppLock.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ import {StatusCodes as HTTP_STATUS} from 'http-status-codes';
2626

2727
import {t} from 'Util/LocalizerUtil';
2828

29-
import {ClientRepository} from '../client/ClientRepository';
30-
import {Config} from '../Config';
29+
import {ClientRepository} from '../../client/ClientRepository';
30+
import {Config} from '../../Config';
3131
import ModalComponent from 'Components/ModalComponent';
32-
import {SIGN_OUT_REASON} from '../auth/SignOutReason';
33-
import {ClientState} from '../client/ClientState';
34-
import {AppLockState} from '../user/AppLockState';
35-
import {AppLockRepository} from '../user/AppLockRepository';
32+
import {SIGN_OUT_REASON} from '../../auth/SignOutReason';
33+
import {ClientState} from '../../client/ClientState';
34+
import {AppLockState} from '../../user/AppLockState';
35+
import {AppLockRepository} from '../../user/AppLockRepository';
3636
import {registerReactComponent, useKoSubscribableChildren} from 'Util/ComponentUtil';
37-
import {ModalsViewModel} from '../view_model/ModalsViewModel';
37+
import {ModalsViewModel} from '../../view_model/ModalsViewModel';
3838
import Icon from 'Components/Icon';
3939

4040
export enum APPLOCK_STATE {

src/script/page/AppLock/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
export * from './AppLock';
21+
export {default} from './AppLock';

src/script/page/MainContent/panels/preferences/AccountPreferences.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {RichProfileRepository} from '../../../../user/RichProfileRepository';
3939
import type {UserRepository} from '../../../../user/UserRepository';
4040
import {UserState} from '../../../../user/UserState';
4141
import {modals, ModalsViewModel} from '../../../../view_model/ModalsViewModel';
42-
import AccentColorPicker from '../../../AccentColorPicker';
42+
import {AccentColorPicker} from '../../../AccentColorPicker';
4343
import AccountInput from './accountPreferences/AccountInput';
4444
import AccountSecuritySection from './accountPreferences/AccountSecuritySection';
4545
import AvailabilityButtons from './accountPreferences/AvailabilityButtons';

src/script/page/message-list/AssetUploadButton.test.tsx renamed to src/script/page/message-list/AssetUploadButton/AssetUploadButton.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
*/
1919

2020
import {render, fireEvent} from '@testing-library/react';
21-
import {AssetUploadButton} from './AssetUploadButton';
21+
import {AssetUploadButton} from './';
2222

23-
jest.mock('../../Config', () => ({
23+
jest.mock('../../../Config', () => ({
2424
Config: {
2525
getConfig: () => ({
2626
ALLOWED_IMAGE_TYPES: ['image/gif', 'image/avif'],

src/script/page/message-list/AssetUploadButton.tsx renamed to src/script/page/message-list/AssetUploadButton/AssetUploadButton.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import {useRef} from 'react';
21-
import {Config} from '../../Config';
21+
import {Config} from '../../../Config';
2222
import {t} from 'Util/LocalizerUtil';
2323
import Icon from 'Components/Icon';
2424

@@ -28,7 +28,8 @@ interface AssetUploadButtonProps {
2828

2929
export const AssetUploadButton = ({onSelectFiles}: AssetUploadButtonProps) => {
3030
const acceptedFileTypes = Config.getConfig().FEATURE.ALLOWED_FILE_UPLOAD_EXTENSIONS.join(',');
31-
const fileRef = useRef<HTMLInputElement>(null!);
31+
32+
const fileRef = useRef<HTMLInputElement>(null);
3233
const formRef = useRef<HTMLFormElement>(null);
3334

3435
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
export * from './AssetUploadButton';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {render, fireEvent} from '@testing-library/react';
21+
import {ImageUploadButton} from './';
22+
23+
jest.mock('../../../Config', () => ({
24+
Config: {
25+
getConfig: () => ({
26+
ALLOWED_IMAGE_TYPES: ['image/gif', 'image/avif'],
27+
FEATURE: {ALLOWED_FILE_UPLOAD_EXTENSIONS: ['*']},
28+
}),
29+
},
30+
}));
31+
32+
const pngFile = new File(['(⌐□_□)'], 'chucknorris.png', {type: 'image/png'});
33+
34+
describe('ImageUploadButton', () => {
35+
it('Does call onSelectImages with uploaded image file', () => {
36+
const onSelectImages = jest.fn();
37+
38+
const {container} = render(<ImageUploadButton onSelectImages={onSelectImages} />);
39+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
40+
41+
fireEvent.change(fileInput, {
42+
target: {files: [pngFile]},
43+
});
44+
45+
expect(onSelectImages).toHaveBeenCalledWith([pngFile]);
46+
});
47+
48+
it('Does reset a form with input after upload', () => {
49+
const onSelectImages = jest.fn();
50+
51+
const {container} = render(<ImageUploadButton onSelectImages={onSelectImages} />);
52+
53+
const form = container.querySelector('form');
54+
jest.spyOn(form!, 'reset');
55+
56+
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
57+
58+
fireEvent.change(fileInput, {
59+
target: {files: [pngFile]},
60+
});
61+
62+
expect(onSelectImages).toHaveBeenCalledWith([pngFile]);
63+
expect(fileInput.files?.[0].name).toEqual(pngFile.name);
64+
expect(form!.reset).toHaveBeenCalled();
65+
});
66+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
import {useRef} from 'react';
21+
import {t} from 'Util/LocalizerUtil';
22+
import Icon from 'Components/Icon';
23+
import {Config} from '../../../Config';
24+
25+
interface ImageUploadButtonProps {
26+
onSelectImages: (files: File[]) => void;
27+
}
28+
29+
export const ImageUploadButton = ({onSelectImages}: ImageUploadButtonProps) => {
30+
const acceptedImageTypes = Config.getConfig().ALLOWED_IMAGE_TYPES.join(',');
31+
32+
const imageRef = useRef<HTMLInputElement>(null);
33+
const formRef = useRef<HTMLFormElement>(null);
34+
35+
const handleImageFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
36+
const {files} = event.target;
37+
38+
if (!files) {
39+
return;
40+
}
41+
42+
onSelectImages(Array.from(files));
43+
formRef.current?.reset();
44+
};
45+
46+
return (
47+
<form ref={formRef}>
48+
<button
49+
type="button"
50+
aria-label={t('tooltipConversationAddImage')}
51+
title={t('tooltipConversationAddImage')}
52+
className="conversation-button controls-right-button no-radius file-button"
53+
onClick={() => imageRef.current?.click()}
54+
data-uie-name="do-share-image"
55+
>
56+
<Icon.Image />
57+
58+
<input
59+
ref={imageRef}
60+
accept={acceptedImageTypes}
61+
tabIndex={-1}
62+
id="conversation-input-bar-photo"
63+
onChange={handleImageFileChange}
64+
type="file"
65+
/>
66+
</button>
67+
</form>
68+
);
69+
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2022 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*
18+
*/
19+
20+
export * from './ImageUploadButton';

src/script/page/message-list/InputBarControls/ControlButtons.tsx

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
*/
1919

2020
import Icon from 'Components/Icon';
21-
import React, {useRef} from 'react';
22-
import MessageTimerButton from '../MessageTimerButton';
21+
import React from 'react';
22+
import {MessageTimerButton} from '../MessageTimerButton';
2323
import {t} from 'Util/LocalizerUtil';
2424
import {Conversation} from 'src/script/entity/Conversation';
25-
import {Config} from '../../../Config';
2625
import {AssetUploadButton} from '../AssetUploadButton';
26+
import {ImageUploadButton} from '../ImageUploadButton';
2727

2828
export type ControlButtonsProps = {
2929
input: string;
@@ -54,10 +54,6 @@ const ControlButtons: React.FC<ControlButtonsProps> = ({
5454
onCancelEditing,
5555
onClickGif,
5656
}) => {
57-
const acceptedImageTypes = Config.getConfig().ALLOWED_IMAGE_TYPES.join(',');
58-
59-
const imageRef = useRef<HTMLInputElement>(null!);
60-
6157
const pingTooltip = t('tooltipConversationPing');
6258

6359
if (isEditing) {
@@ -96,25 +92,7 @@ const ControlButtons: React.FC<ControlButtonsProps> = ({
9692
</li>
9793

9894
<li>
99-
<button
100-
type="button"
101-
aria-label={t('tooltipConversationAddImage')}
102-
title={t('tooltipConversationAddImage')}
103-
className="conversation-button controls-right-button no-radius file-button"
104-
onClick={() => imageRef.current?.click()}
105-
data-uie-name="do-share-image"
106-
>
107-
<Icon.Image />
108-
109-
<input
110-
ref={imageRef}
111-
accept={acceptedImageTypes}
112-
tabIndex={-1}
113-
id="conversation-input-bar-photo"
114-
onChange={({target: {files}}) => files && onSelectImages(Array.from(files))}
115-
type="file"
116-
/>
117-
</button>
95+
<ImageUploadButton onSelectImages={onSelectImages} />
11896
</li>
11997

12098
<li>

0 commit comments

Comments
 (0)