From 7f2ebeee4afeabf329c9d08617eced462d8f8908 Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Mon, 23 Feb 2026 22:30:25 +0530 Subject: [PATCH 1/6] fix: replace any with proper types in AddCustomSound and EditSound Relax createSoundData soundFile param to { name: string } since only .name is used. AddCustomSound: soundFile: any -> File, update state type, add undefined guard. EditSound: add SoundFile union, narrow with instanceof File before validate/FileReader. Fixes FIXME comments in both components. --- .../admin/customSounds/AddCustomSound.tsx | 9 ++++--- .../views/admin/customSounds/EditSound.tsx | 26 ++++++++++++------- .../client/views/admin/customSounds/lib.ts | 2 +- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx index 3bc46c8742f1d..75101e0fd32ad 100644 --- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx @@ -19,7 +19,7 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const dispatchToastMessage = useToastMessageDispatch(); const [name, setName] = useState(''); - const [sound, setSound] = useState<{ name: string }>(); + const [sound, setSound] = useState(); const uploadCustomSound = useMethod('uploadCustomSound'); const insertOrUpdateSound = useMethod('insertOrUpdateSound'); @@ -31,8 +31,7 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const [clickUpload] = useSingleFileInput(handleChangeFile, 'audio/mp3'); const saveAction = useCallback( - // FIXME - async (name: string, soundFile: any) => { + async (name: string, soundFile: File) => { const soundData = createSoundData(soundFile, name); const validation = validate(soundData, soundFile) as Array[0]>; @@ -73,6 +72,10 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const handleSave = useCallback(async () => { try { + if (!sound) { + return; + } + const result = await saveAction(name, sound); if (result) { dispatchToastMessage({ type: 'success', message: t('Custom_Sound_Saved_Successfully') }); diff --git a/apps/meteor/client/views/admin/customSounds/EditSound.tsx b/apps/meteor/client/views/admin/customSounds/EditSound.tsx index 9f72df02ca7bd..2f2ab961f95c5 100644 --- a/apps/meteor/client/views/admin/customSounds/EditSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/EditSound.tsx @@ -18,6 +18,14 @@ type EditSoundProps = { }; }; +type ExistingSound = { + _id: string; + name: string; + extension?: string; +}; + +type SoundFile = File | ExistingSound; + function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactElement { const { t } = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); @@ -29,10 +37,10 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl const [name, setName] = useState(() => data?.name ?? ''); const [sound, setSound] = useState< | { - _id: string; - name: string; - extension?: string; - } + _id: string; + name: string; + extension?: string; + } | File >(() => data); @@ -52,10 +60,10 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl const hasUnsavedChanges = useMemo(() => previousName !== name || previousSound !== sound, [name, previousName, previousSound, sound]); const saveAction = useCallback( - // FIXME - async (sound: any) => { - const soundData = createSoundData(sound, name, { previousName, previousSound, _id, extension: sound.extension }); - const validation = validate(soundData, sound); + async (sound: SoundFile) => { + const soundData = createSoundData(sound, name, { previousName, previousSound, _id, extension: sound instanceof File ? '' : sound.extension ?? '' }); + const soundFile = sound instanceof File ? sound : undefined; + const validation = validate(soundData, soundFile); if (validation.length === 0) { let soundId: string; try { @@ -68,7 +76,7 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl soundData._id = soundId; soundData.random = Math.round(Math.random() * 1000); - if (sound && sound !== previousSound) { + if (sound instanceof File) { dispatchToastMessage({ type: 'success', message: t('Uploading_file') }); const reader = new FileReader(); diff --git a/apps/meteor/client/views/admin/customSounds/lib.ts b/apps/meteor/client/views/admin/customSounds/lib.ts index c447bad77bdeb..31ae18c2ca917 100644 --- a/apps/meteor/client/views/admin/customSounds/lib.ts +++ b/apps/meteor/client/views/admin/customSounds/lib.ts @@ -30,7 +30,7 @@ export function validate(soundData: ICustomSoundData, soundFile?: ICustomSoundFi } export const createSoundData = ( - soundFile: ICustomSoundFile, + soundFile: { name: string }, name: string, previousData?: { _id: string; From d1405407c8d2c0cf435b15a688946cb5c02f204a Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Mon, 23 Feb 2026 22:55:28 +0530 Subject: [PATCH 2/6] chore: apply prettier formatting to customSounds files --- .../client/views/admin/customSounds/EditSound.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/meteor/client/views/admin/customSounds/EditSound.tsx b/apps/meteor/client/views/admin/customSounds/EditSound.tsx index 2f2ab961f95c5..82858d6ca0e60 100644 --- a/apps/meteor/client/views/admin/customSounds/EditSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/EditSound.tsx @@ -37,10 +37,10 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl const [name, setName] = useState(() => data?.name ?? ''); const [sound, setSound] = useState< | { - _id: string; - name: string; - extension?: string; - } + _id: string; + name: string; + extension?: string; + } | File >(() => data); @@ -61,7 +61,12 @@ function EditSound({ close, onChange, data, ...props }: EditSoundProps): ReactEl const saveAction = useCallback( async (sound: SoundFile) => { - const soundData = createSoundData(sound, name, { previousName, previousSound, _id, extension: sound instanceof File ? '' : sound.extension ?? '' }); + const soundData = createSoundData(sound, name, { + previousName, + previousSound, + _id, + extension: sound instanceof File ? '' : (sound.extension ?? ''), + }); const soundFile = sound instanceof File ? sound : undefined; const validation = validate(soundData, soundFile); if (validation.length === 0) { From 3a86f233542db0be7d8b66af18a0daf2165b6b2c Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Mon, 23 Feb 2026 23:08:45 +0530 Subject: [PATCH 3/6] fix: preserve validation toast when no sound file selected in AddCustomSound --- .../views/admin/customSounds/AddCustomSound.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx index 75101e0fd32ad..6e271334d5e00 100644 --- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx @@ -31,8 +31,8 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const [clickUpload] = useSingleFileInput(handleChangeFile, 'audio/mp3'); const saveAction = useCallback( - async (name: string, soundFile: File) => { - const soundData = createSoundData(soundFile, name); + async (name: string, soundFile: File | undefined) => { + const soundData = createSoundData(soundFile ?? { name: '' }, name); const validation = validate(soundData, soundFile) as Array[0]>; validation.forEach((invalidFieldName) => { @@ -49,10 +49,11 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr dispatchToastMessage({ type: 'success', message: t('Uploading_file') }); const reader = new FileReader(); - reader.readAsBinaryString(soundFile); + // soundFile is defined here: validate() above throws when soundFile is missing + reader.readAsBinaryString(soundFile!); reader.onloadend = (): void => { try { - uploadCustomSound(reader.result as string, soundFile.type, { + uploadCustomSound(reader.result as string, soundFile!.type, { ...soundData, _id: soundId, random: Math.round(Math.random() * 1000), @@ -72,10 +73,6 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const handleSave = useCallback(async () => { try { - if (!sound) { - return; - } - const result = await saveAction(name, sound); if (result) { dispatchToastMessage({ type: 'success', message: t('Custom_Sound_Saved_Successfully') }); From 05d1baf4167dd9fd0a9447724c2ed90ceae0a90b Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Mon, 23 Feb 2026 23:13:12 +0530 Subject: [PATCH 4/6] fix: disable Save button in AddCustomSound when name or sound file is missing --- .../views/admin/customSounds/AddCustomSound.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx index 6e271334d5e00..624dca300af95 100644 --- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx @@ -31,8 +31,8 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr const [clickUpload] = useSingleFileInput(handleChangeFile, 'audio/mp3'); const saveAction = useCallback( - async (name: string, soundFile: File | undefined) => { - const soundData = createSoundData(soundFile ?? { name: '' }, name); + async (name: string, soundFile: File) => { + const soundData = createSoundData(soundFile, name); const validation = validate(soundData, soundFile) as Array[0]>; validation.forEach((invalidFieldName) => { @@ -49,11 +49,10 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr dispatchToastMessage({ type: 'success', message: t('Uploading_file') }); const reader = new FileReader(); - // soundFile is defined here: validate() above throws when soundFile is missing - reader.readAsBinaryString(soundFile!); + reader.readAsBinaryString(soundFile); reader.onloadend = (): void => { try { - uploadCustomSound(reader.result as string, soundFile!.type, { + uploadCustomSound(reader.result as string, soundFile.type, { ...soundData, _id: soundId, random: Math.round(Math.random() * 1000), @@ -110,7 +109,7 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr - From 1bb2875812c0f41a8420167590ce2a6a0d51d36e Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Mon, 23 Feb 2026 23:28:53 +0530 Subject: [PATCH 5/6] fix: add type narrowing guard in handleSave to satisfy TypeScript compiler --- .../client/views/admin/customSounds/AddCustomSound.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx index 624dca300af95..04ef6543f73b8 100644 --- a/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/AddCustomSound.tsx @@ -71,6 +71,11 @@ const AddCustomSound = ({ goToNew, close, onChange, ...props }: AddCustomSoundPr ); const handleSave = useCallback(async () => { + if (!sound) { + // unreachable: Save button is disabled when !sound — guard exists for type narrowing only + return; + } + try { const result = await saveAction(name, sound); if (result) { From 03277d04a9cb03362102556fea527a7c1dfed1cc Mon Sep 17 00:00:00 2001 From: Chetan Agarwal Date: Tue, 24 Feb 2026 00:03:00 +0530 Subject: [PATCH 6/6] test(gazzodown): mock react-i18next and ui-contexts in Markup.spec to fix code block tests --- packages/gazzodown/src/Markup.spec.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/gazzodown/src/Markup.spec.tsx b/packages/gazzodown/src/Markup.spec.tsx index d4f970d031895..c0f0975a24f84 100644 --- a/packages/gazzodown/src/Markup.spec.tsx +++ b/packages/gazzodown/src/Markup.spec.tsx @@ -8,6 +8,14 @@ jest.mock('highlight.js', () => ({ highlightElement: (): void => undefined, })); +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +jest.mock('@rocket.chat/ui-contexts', () => ({ + useToastMessageDispatch: () => (): void => undefined, +})); + it('renders empty', () => { const { container } = render(); expect(container).toBeEmptyDOMElement();