diff --git a/examples/vite/src/CustomMessageUi/variants.tsx b/examples/vite/src/CustomMessageUi/variants.tsx index 3eb129454..30a24e9fb 100644 --- a/examples/vite/src/CustomMessageUi/variants.tsx +++ b/examples/vite/src/CustomMessageUi/variants.tsx @@ -274,7 +274,16 @@ const CustomMessageUiActions = () => { - diff --git a/src/components/Message/hooks/__tests__/useDeleteHandler.test.tsx b/src/components/Message/hooks/__tests__/useDeleteHandler.test.tsx index 25efba9fa..01b9e1cfb 100644 --- a/src/components/Message/hooks/__tests__/useDeleteHandler.test.tsx +++ b/src/components/Message/hooks/__tests__/useDeleteHandler.test.tsx @@ -59,6 +59,19 @@ describe('useDeleteHandler custom hook', () => { expect(typeof handleDelete).toBe('function'); }); + it('should throw without deleting when the message has no id', async () => { + const deleteSpy = vi.spyOn(channel.messageOperations, 'delete'); + const handleDelete = await renderUseDeleteHandler( + generateMessage({ id: undefined }) as typeof testMessage, + ); + await act(async () => { + await expect(handleDelete()).rejects.toThrow( + 'useDeleteHandler was called but it is missing one or more necessary parameters.', + ); + }); + expect(deleteSpy).not.toHaveBeenCalled(); + }); + it('should delete a message without options via the channel local-update path', async () => { const deleteSpy = vi .spyOn(channel.messageOperations, 'delete') diff --git a/src/components/Message/hooks/__tests__/useFlagHandler.test.tsx b/src/components/Message/hooks/__tests__/useFlagHandler.test.tsx index 2e3edab5c..5547055f4 100644 --- a/src/components/Message/hooks/__tests__/useFlagHandler.test.tsx +++ b/src/components/Message/hooks/__tests__/useFlagHandler.test.tsx @@ -50,11 +50,13 @@ describe('useHandleFlag custom hook', () => { expect(typeof handleFlag).toBe('function'); }); - it('should throw a warning when there are missing parameters and the handler is called', async () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => null); + it('should throw when there are missing parameters and the handler is called', async () => { + const flagSpy = vi.spyOn(client.moderation, 'flagMessage'); const handleFlag = await renderUseHandleFlagHook(undefined); - await handleFlag(mouseEventMock); - expect(consoleWarnSpy).toHaveBeenCalledWith(missingUseFlagHandlerParameterWarning); + await expect(handleFlag(mouseEventMock)).rejects.toThrow( + missingUseFlagHandlerParameterWarning, + ); + expect(flagSpy).not.toHaveBeenCalled(); }); it('should allow to flag a message when it is successful', async () => { diff --git a/src/components/Message/hooks/__tests__/useMuteHandler.test.tsx b/src/components/Message/hooks/__tests__/useMuteHandler.test.tsx index 1a2b04e1a..128f60ee2 100644 --- a/src/components/Message/hooks/__tests__/useMuteHandler.test.tsx +++ b/src/components/Message/hooks/__tests__/useMuteHandler.test.tsx @@ -55,11 +55,13 @@ describe('useHandleMute custom hook', () => { expect(typeof handleMute).toBe('function'); }); - it('should throw a warning when there are missing parameters and the handler is called', async () => { - const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementationOnce(() => null); + it('should throw when there are missing parameters and the handler is called', async () => { const handleMute = await renderUseHandleMuteHook(undefined); - await handleMute(mouseEventMock); - expect(consoleWarnSpy).toHaveBeenCalledWith(missingUseMuteHandlerParamsWarning); + await expect(handleMute(mouseEventMock)).rejects.toThrow( + missingUseMuteHandlerParamsWarning, + ); + expect(mute).not.toHaveBeenCalled(); + expect(unmute).not.toHaveBeenCalled(); }); it('should allow to mute a user when it is successful', async () => { diff --git a/src/components/Message/hooks/__tests__/usePinHandler.test.tsx b/src/components/Message/hooks/__tests__/usePinHandler.test.tsx index 2d61bd242..8c908c762 100644 --- a/src/components/Message/hooks/__tests__/usePinHandler.test.tsx +++ b/src/components/Message/hooks/__tests__/usePinHandler.test.tsx @@ -168,6 +168,17 @@ describe('usePinHandler custom hook', () => { expect(getStoredMessage(message.id)?.pinned_by).toEqual(newerPinner); }); + it('should throw without pinning when the message is missing', async () => { + const pinSpy = vi.spyOn(client, 'pinMessage'); + const { handlePin } = await renderUsePinHandler(undefined as unknown as LocalMessage); + await act(async () => { + await expect(handlePin(mouseEventMock)).rejects.toThrow( + 'usePinHandler was called but it is missing the message.', + ); + }); + expect(pinSpy).not.toHaveBeenCalled(); + }); + it('should keep a newer pin with the same pinned value that arrived while pinning', async () => { const rejectRequest = mockPendingRequest('pinMessage'); const newerPinner = generateUser(); diff --git a/src/components/Message/hooks/__tests__/userMarkUnreadHandler.test.tsx b/src/components/Message/hooks/__tests__/userMarkUnreadHandler.test.tsx index 6ecaefcdf..330024e97 100644 --- a/src/components/Message/hooks/__tests__/userMarkUnreadHandler.test.tsx +++ b/src/components/Message/hooks/__tests__/userMarkUnreadHandler.test.tsx @@ -10,9 +10,8 @@ import { generateMessage, initClientWithChannels } from '../../../../mock-builde import { Channel } from '../../../Channel'; import { Chat } from '../../../Chat'; -vi.spyOn(console, 'warn').mockImplementation(() => null); - const event = fromPartial({ preventDefault: vi.fn() }); +const missingMessageIdError = 'Mark unread handler does not have access to message id'; const message = generateMessage() as unknown as LocalMessage; let channel: ChannelType; @@ -47,22 +46,22 @@ describe('useMarkUnreadHandler', () => { vi.restoreAllMocks(); }); - it('does not call channel.markUnread if no message is provided', async () => { + it('throws without calling channel.markUnread if no message is provided', async () => { const markUnreadSpy = vi.spyOn(channel, 'markUnread'); const handleMarkUnread = renderUseMarkUnreadHandlerHook(); await act(async () => { - await handleMarkUnread(event); + await expect(handleMarkUnread(event)).rejects.toThrow(missingMessageIdError); }); expect(markUnreadSpy).not.toHaveBeenCalled(); }); - it('does not call channel.markUnread if message is missing id', async () => { + it('throws without calling channel.markUnread if message is missing id', async () => { const markUnreadSpy = vi.spyOn(channel, 'markUnread'); const handleMarkUnread = renderUseMarkUnreadHandlerHook({ message: generateMessage({ id: undefined }) as unknown as LocalMessage, }); await act(async () => { - await handleMarkUnread(event); + await expect(handleMarkUnread(event)).rejects.toThrow(missingMessageIdError); }); expect(markUnreadSpy).not.toHaveBeenCalled(); }); diff --git a/src/components/Message/hooks/useDeleteHandler.ts b/src/components/Message/hooks/useDeleteHandler.ts index 1101645ba..17f8b7c10 100644 --- a/src/components/Message/hooks/useDeleteHandler.ts +++ b/src/components/Message/hooks/useDeleteHandler.ts @@ -27,7 +27,9 @@ export const useDeleteHandler = ( return async (options?: DeleteMessageOptions) => { if (!message?.id || !client) { - return; + throw new Error( + 'useDeleteHandler was called but it is missing one or more necessary parameters.', + ); } try { diff --git a/src/components/Message/hooks/useFlagHandler.ts b/src/components/Message/hooks/useFlagHandler.ts index 5bd54a4dd..cd07db975 100644 --- a/src/components/Message/hooks/useFlagHandler.ts +++ b/src/components/Message/hooks/useFlagHandler.ts @@ -15,8 +15,7 @@ export const useFlagHandler = (message?: LocalMessage): ReactEventHandler => { event.preventDefault(); if (!client || !t || !message?.id) { - console.warn(missingUseFlagHandlerParameterWarning); - return; + throw new Error(missingUseFlagHandlerParameterWarning); } await client.moderation.flagMessage({ message_id: message.id }); diff --git a/src/components/Message/hooks/useMarkUnreadHandler.ts b/src/components/Message/hooks/useMarkUnreadHandler.ts index b9ea49bf7..3a1bf69bd 100644 --- a/src/components/Message/hooks/useMarkUnreadHandler.ts +++ b/src/components/Message/hooks/useMarkUnreadHandler.ts @@ -21,8 +21,7 @@ export const useMarkUnreadHandler = ( return async (event) => { event.preventDefault(); if (!message?.id) { - console.warn('Mark unread handler does not have access to message id'); - return; + throw new Error('Mark unread handler does not have access to message id'); } // Request failures propagate so the caller can report them; `notify` only reports success. diff --git a/src/components/Message/hooks/useMuteHandler.ts b/src/components/Message/hooks/useMuteHandler.ts index 9d86aa778..026c8945c 100644 --- a/src/components/Message/hooks/useMuteHandler.ts +++ b/src/components/Message/hooks/useMuteHandler.ts @@ -30,8 +30,7 @@ export const useMuteHandler = ( // The action runs even without `notify` (`Message` passes none); it only reports the outcome. if (!message?.user || !client) { - console.warn(missingUseMuteHandlerParamsWarning); - return; + throw new Error(missingUseMuteHandlerParamsWarning); } const mutes = client.mutedUsersStore.getLatestValue().mutedUsers ?? []; diff --git a/src/components/Message/hooks/usePinHandler.ts b/src/components/Message/hooks/usePinHandler.ts index 8e86712b5..827e8ca1a 100644 --- a/src/components/Message/hooks/usePinHandler.ts +++ b/src/components/Message/hooks/usePinHandler.ts @@ -77,7 +77,9 @@ export const usePinHandler = ( const handlePin: ReactEventHandler = async (event) => { event.preventDefault(); - if (!message) return; + if (!message) { + throw new Error('usePinHandler was called but it is missing the message.'); + } if (!message.pinned) { const optimisticMessage: LocalMessage = {