diff --git a/docusaurus/docs/React/components/contexts/message-input-context.mdx b/docusaurus/docs/React/components/contexts/message-input-context.mdx index cc9327c44..f62e45044 100644 --- a/docusaurus/docs/React/components/contexts/message-input-context.mdx +++ b/docusaurus/docs/React/components/contexts/message-input-context.mdx @@ -246,6 +246,14 @@ Function that runs onSubmit to the underlying `textarea` component. | ---------------------------------------------------------------------- | | (event: React.BaseSyntheticEvent, customMessageData?: Message) => void | +### hideSendButton + +Allows to hide MessageInput's send button. Used by `MessageSimple` to hide the send button in `EditMessageForm`. Received from `MessageInputProps`. + +| Type | Default | +|---------|---------| +| boolean | false | + ### imageOrder The order in which image attachments have been added to the current message. diff --git a/docusaurus/docs/React/components/core-components/channel.mdx b/docusaurus/docs/React/components/core-components/channel.mdx index 2d7ab0523..fbf0495f1 100644 --- a/docusaurus/docs/React/components/core-components/channel.mdx +++ b/docusaurus/docs/React/components/core-components/channel.mdx @@ -329,6 +329,14 @@ The Giphy version to render - check the keys of the [Image Object](https://devel | ------ | -------------- | | string | 'fixed_height' | +### HeaderComponent + +Custom UI component to render at the top of the `MessageList`. + +| Type | Default | +| --------- | ------- | +| component | none | + ### imageAttachmentSizeHandler A custom function to provide size configuration for image attachments @@ -337,13 +345,54 @@ A custom function to provide size configuration for image attachments | ---------------------------------------------------------------- | | `(a: Attachment, e: HTMLElement) => ImageAttachmentConfigration` | -### HeaderComponent +### initializeOnMount + +Allows to prevent triggering the `channel.watch()` (triggers channel query HTTP request) call when mounting the `Channel` component (the default behavior) with uninitialized (`channel.initialized`) `Channel` instance. That means that no channel data from the back-end will be received neither channel WS events will be delivered to the client. Preventing to initialize the channel on mount allows us to postpone the channel creation in the Stream's DB to a later point in time, for example, when a first message is sent: + +```typescript jsx +import {useCallback} from "react"; +import { + getChannel, + MessageInput as StreamMessageInput, + MessageInputProps, MessageToSend, + useChannelActionContext, + useChatContext +} from "stream-chat-react"; +import {Message, SendMessageOptions} from "stream-chat"; + +import {useChannelInitContext} from "../../context/ChannelInitProvider"; +import type { MyStreamChatGenerics } from "../../types"; + +export const MessageInput = (props: MessageInputProps) => { + const {client} = useChatContext(); + const {sendMessage} = useChannelActionContext(); + const { setInitializedChannelOnMount} = useChannelInitContext(); + + const submitHandler: MessageInputProps['overrideSubmitHandler'] = useCallback(async ( + message: MessageToSend, + channelCid: string, + customMessageData?: Partial>, + options?: SendMessageOptions, + ) => { + const [channelType, channelId] = channelCid.split(":"); + const channel = client.channel(channelType, channelId); + if (!channel.initialized) { + await getChannel({channel, client}); + setInitializedChannelOnMount(true); + } + + await sendMessage(message, customMessageData, options); + }, [client, sendMessage, setInitializedChannelOnMount]); -Custom UI component to render at the top of the `MessageList`. + return ( + + ); +}; +``` -| Type | Default | -| --------- | ------- | -| component | none | +| Type | Default | +|---------|---------| +| boolean | true | ### Input diff --git a/docusaurus/docs/React/components/message-input-components/message-input.mdx b/docusaurus/docs/React/components/message-input-components/message-input.mdx index 517840f43..422f7177f 100644 --- a/docusaurus/docs/React/components/message-input-components/message-input.mdx +++ b/docusaurus/docs/React/components/message-input-components/message-input.mdx @@ -124,6 +124,14 @@ If true, expands the text input vertically for new lines. | ------- | ------- | | boolean | true | +### hideSendButton + +Allows to hide MessageInput's send button. Used by `MessageSimple` to hide the send button in `EditMessageForm`. + +| Type | Default | +|---------|---------| +| boolean | false | + ### Input Custom UI component handling how the message input is rendered. diff --git a/package.json b/package.json index 9e313e133..88ca1a254 100644 --- a/package.json +++ b/package.json @@ -169,7 +169,7 @@ "rollup-plugin-url": "^3.0.1", "rollup-plugin-visualizer": "^4.2.0", "semantic-release": "^19.0.5", - "stream-chat": "^8.12.0", + "stream-chat": "^8.12.4", "style-loader": "^2.0.0", "ts-jest": "^26.5.1", "typescript": "^4.7.4", diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx index fe6d5ede8..80d5728a7 100644 --- a/src/components/Channel/Channel.tsx +++ b/src/components/Channel/Channel.tsx @@ -152,7 +152,8 @@ export type ChannelProps< EmptyPlaceholder?: React.ReactElement; /** Custom UI component to be displayed when the `MessageList` is empty, defaults to and accepts same props as: [EmptyStateIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/EmptyStateIndicator/EmptyStateIndicator.tsx) */ EmptyStateIndicator?: ComponentContextValue['EmptyStateIndicator']; - /** A global flag to toggle the URL enrichment and link previews in `MessageInput` components. + /** + * A global flag to toggle the URL enrichment and link previews in `MessageInput` components. * By default, the feature is disabled. Can be overridden on Thread, MessageList level through additionalMessageInputProps * or directly on MessageInput level through urlEnrichmentConfig. */ @@ -169,6 +170,12 @@ export type ChannelProps< HeaderComponent?: ComponentContextValue['HeaderComponent']; /** A custom function to provide size configuration for image attachments */ imageAttachmentSizeHandler?: ImageAttachmentSizeHandler; + /** + * Allows to prevent triggering the channel.watch() call when mounting the component. + * That means that no channel data from the back-end will be received neither channel WS events will be delivered to the client. + * Preventing to initialize the channel on mount allows us to postpone the channel creation to a later point in time. + */ + initializeOnMount?: boolean; /** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */ Input?: ComponentContextValue['Input']; /** Custom component to render link previews in message input **/ @@ -312,6 +319,7 @@ const ChannelInner = < dragAndDropWindow = false, emojiData = defaultEmojiData, enrichURLForPreviewConfig, + initializeOnMount = true, LoadingErrorIndicator = DefaultLoadingErrorIndicator, LoadingIndicator = DefaultLoadingIndicator, maxNumberOfFiles, @@ -478,7 +486,7 @@ const ChannelInner = < }; (async () => { - if (!channel.initialized) { + if (!channel.initialized && initializeOnMount) { try { // if active channel has been set without id, we will create a temporary channel id from its member IDs // to keep track of the /query request in progress. This is the same approach of generating temporary id @@ -533,7 +541,7 @@ const ChannelInner = < client.off('user.deleted', handleEvent); notificationTimeouts.forEach(clearTimeout); }; - }, [channel.cid, doMarkReadRequest, channelConfig?.read_events]); + }, [channel.cid, doMarkReadRequest, channelConfig?.read_events, initializeOnMount]); useEffect(() => { if (!state.thread) return; diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js index 6cd9484ce..1d6bf303c 100644 --- a/src/components/Channel/__tests__/Channel.test.js +++ b/src/components/Channel/__tests__/Channel.test.js @@ -380,13 +380,33 @@ describe('Channel', () => { jest.spyOn(channel, 'countUnread').mockImplementationOnce(() => 1); const doMarkReadRequest = jest.fn(); - renderComponent({ - doMarkReadRequest, + await act(() => { + renderComponent({ + doMarkReadRequest, + }); }); await waitFor(() => expect(doMarkReadRequest).toHaveBeenCalledTimes(1)); }); + it('should not query the channel from the backend when initializeOnMount is disabled', async () => { + const watchSpy = jest.spyOn(channel, 'watch').mockImplementationOnce(); + await act(() => { + renderComponent({ + initializeOnMount: false, + }); + }); + await waitFor(() => expect(watchSpy).not.toHaveBeenCalled()); + }); + + it('should query the channel from the backend when initializeOnMount is enabled (the default)', async () => { + const watchSpy = jest.spyOn(channel, 'watch').mockImplementationOnce(); + await act(() => { + renderComponent(); + }); + await waitFor(() => expect(watchSpy).toHaveBeenCalledTimes(1)); + }); + describe('Children that consume the contexts set in Channel', () => { it('should expose the emoji config', async () => { let context; diff --git a/src/components/ChannelList/__tests__/ChannelList.test.js b/src/components/ChannelList/__tests__/ChannelList.test.js index 361434231..44f7d40e5 100644 --- a/src/components/ChannelList/__tests__/ChannelList.test.js +++ b/src/components/ChannelList/__tests__/ChannelList.test.js @@ -324,6 +324,35 @@ describe('ChannelList', () => { expect(results).toHaveNoViolations(); }); + it('should show unique channels', async () => { + useMockedApis(chatClientUthred, [queryChannelsApi([testChannel1, testChannel2])]); + const ChannelPreview = (props) =>
; + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument(); + expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument(); + expect(screen.getAllByRole('listitem')).toHaveLength(2); + }); + + useMockedApis(chatClientUthred, [queryChannelsApi([testChannel1, testChannel3])]); + + await act(() => { + fireEvent.click(screen.getByTestId('load-more-button')); + }); + + await waitFor(() => { + expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument(); + expect(screen.getByTestId(testChannel2.channel.id)).toBeInTheDocument(); + expect(screen.getByTestId(testChannel3.channel.id)).toBeInTheDocument(); + expect(screen.getAllByRole('listitem')).toHaveLength(3); + }); + }); + describe('Default and custom active channel', () => { let setActiveChannel; const watchersConfig = { limit: 20, offset: 0 }; diff --git a/src/components/ChannelList/hooks/usePaginatedChannels.ts b/src/components/ChannelList/hooks/usePaginatedChannels.ts index 4f9dc5f3d..6654fe4b4 100644 --- a/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -1,4 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; +import uniqBy from 'lodash.uniqby'; import { MAX_QUERY_CHANNELS_LIMIT } from '../utils'; @@ -52,7 +53,9 @@ export const usePaginatedChannels = < const channelQueryResponse = await client.queryChannels(filters, sort || {}, newOptions); const newChannels = - queryType === 'reload' ? channelQueryResponse : [...channels, ...channelQueryResponse]; + queryType === 'reload' + ? channelQueryResponse + : uniqBy([...channels, ...channelQueryResponse], 'cid'); setChannels(newChannels); setHasNextPage(channelQueryResponse.length >= newOptions.limit); diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 3a21b37b4..74cc72461 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -124,6 +124,7 @@ const MessageSimpleWithContext = < string | string[]; /** If true, expands the text input vertically for new lines */ grow?: boolean; + /** Allows to hide MessageInput's send button. */ + hideSendButton?: boolean; /** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */ Input?: React.ComponentType>; /** Max number of rows the underlying `textarea` component is allowed to grow */ diff --git a/src/components/MessageInput/MessageInputFlat.tsx b/src/components/MessageInput/MessageInputFlat.tsx index 2dceb86c8..82559d915 100644 --- a/src/components/MessageInput/MessageInputFlat.tsx +++ b/src/components/MessageInput/MessageInputFlat.tsx @@ -81,6 +81,7 @@ const MessageInputV1 = < cooldownRemaining, emojiPickerIsOpen, handleSubmit, + hideSendButton, isUploadEnabled, maxFilesLeft, numberOfUploads, @@ -164,7 +165,7 @@ const MessageInputV1 = <
)} - {!cooldownRemaining && } + {!(cooldownRemaining || hideSendButton) && } @@ -188,6 +189,7 @@ const MessageInputV2 = < emojiPickerIsOpen, findAndEnqueueURLsToEnrich, handleSubmit, + hideSendButton, isUploadEnabled, linkPreviews, maxFilesLeft, @@ -304,8 +306,7 @@ const MessageInputV2 = < - {/* hide SendButton if this component is rendered in the edit message form */} - {!message && ( + {!hideSendButton && ( <> {cooldownRemaining ? ( - {!cooldownRemaining && } + {!(cooldownRemaining || hideSendButton) && } diff --git a/src/components/MessageInput/__tests__/LinkPreviewList.test.js b/src/components/MessageInput/__tests__/LinkPreviewList.test.js index 1173460c0..0ae3d342b 100644 --- a/src/components/MessageInput/__tests__/LinkPreviewList.test.js +++ b/src/components/MessageInput/__tests__/LinkPreviewList.test.js @@ -13,12 +13,7 @@ import { useMockedApis, } from '../../../mock-builders'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { - ChatProvider, - MessageProvider, - useChatContext, - useMessageInputContext, -} from '../../../context'; +import { ChatProvider, MessageProvider, useChatContext } from '../../../context'; import React, { useEffect } from 'react'; import { Chat } from '../../Chat'; import { Channel } from '../../Channel'; @@ -26,7 +21,6 @@ import { MessageActionsBox } from '../../MessageActions'; import { MessageInput } from '../MessageInput'; import '@testing-library/jest-dom'; -import { SendButton } from '../icons'; // Mock out lodash debounce implementation, so it calls the debounced method immediately jest.mock('lodash.debounce', () => @@ -107,17 +101,6 @@ const makeRenderFn = (InputComponent) => async ({ messageContextOverrides = {}, messageActionsBoxProps = {}, } = {}) => { - // circumvents not so good decision to render SendButton conditionally - const InputContainer = () => { - const { handleSubmit, message } = useMessageInputContext(); - return ( - <> - - {!!message && } - - ); - }; - let renderResult; await act(() => { renderResult = render( @@ -131,7 +114,7 @@ const makeRenderFn = (InputComponent) => async ({ getMessageActions={defaultMessageContextValue.getMessageActions} /> - + , diff --git a/src/components/MessageInput/__tests__/MessageInput.test.js b/src/components/MessageInput/__tests__/MessageInput.test.js index d98065277..0b90630eb 100644 --- a/src/components/MessageInput/__tests__/MessageInput.test.js +++ b/src/components/MessageInput/__tests__/MessageInput.test.js @@ -16,7 +16,7 @@ import { MessageActionsBox } from '../../MessageActions'; import { MessageProvider } from '../../../context/MessageContext'; import { useMessageInputContext } from '../../../context/MessageInputContext'; -import { useChatContext } from '../../../context/ChatContext'; +import { ChatProvider, useChatContext } from '../../../context/ChatContext'; import { dispatchMessageDeletedEvent, dispatchMessageUpdatedEvent, @@ -1117,12 +1117,52 @@ function axeNoViolations(container) { }); [ - { InputComponent: MessageInputSmall, name: 'MessageInputSmall' }, - { InputComponent: MessageInputFlat, name: 'MessageInputFlat' }, -].forEach(({ InputComponent, name: componentName }) => { + { InputComponent: MessageInputSmall, name: 'MessageInputSmall', themeVersion: '1' }, + { InputComponent: MessageInputSmall, name: 'MessageInputSmall', themeVersion: '2' }, + { InputComponent: MessageInputFlat, name: 'MessageInputFlat', themeVersion: '1' }, + { InputComponent: MessageInputFlat, name: 'MessageInputFlat', themeVersion: '2' }, +].forEach(({ InputComponent, name: componentName, themeVersion }) => { + const makeRenderFn = (InputComponent) => async ({ + channelProps = {}, + chatContextOverrides = {}, + messageInputProps = {}, + messageContextOverrides = {}, + messageActionsBoxProps = {}, + } = {}) => { + let renderResult; + await act(() => { + renderResult = render( + + {/**/} + + + + + + + , + ); + }); + return renderResult; + }; const renderComponent = makeRenderFn(InputComponent); - describe(`${componentName}`, () => { + describe(`${componentName}${themeVersion ? `(theme: ${themeVersion})` : ''}:`, () => { beforeEach(async () => { chatClient = await getTestClientWithUser({ id: user1.id }); useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannelData)]); @@ -1131,17 +1171,39 @@ function axeNoViolations(container) { afterEach(tearDown); - const render = async () => { + const render = async ({ + chatContextOverrides = {}, + messageContextOverrides = {}, + messageInputProps = {}, + } = {}) => { const message = componentName === 'MessageInputSmall' ? threadMessage : defaultMessageContextValue.message; await renderComponent({ - messageContextOverrides: { message }, + chatContextOverrides: { themeVersion, ...chatContextOverrides }, + messageContextOverrides: { message, ...messageContextOverrides }, + messageInputProps, }); return message; }; + const renderWithActiveCooldown = async ({ messageInputProps = {} } = {}) => { + channel = chatClient.channel('messaging', mockedChannelData.channel.id); + channel.data.cooldown = 30; + channel.initialized = true; + const lastSentSecondsAhead = 5; + await render({ + chatContextOverrides: { + channel, + latestMessageDatesByChannels: { + [channel.cid]: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), + }, + }, + messageInputProps, + }); + }; + const initQuotedMessagePreview = async (message) => { await waitFor(() => expect(screen.queryByText(message.text)).not.toBeInTheDocument()); @@ -1154,7 +1216,9 @@ function axeNoViolations(container) { }; const quotedMessagePreviewIsDisplayedCorrectly = async (message) => { - await waitFor(() => expect(screen.queryByText(/reply to message/i)).toBeInTheDocument()); + await waitFor(() => + expect(screen.queryByTestId('quoted-message-preview')).toBeInTheDocument(), + ); await waitFor(() => expect(screen.getByText(message.text)).toBeInTheDocument()); }; @@ -1174,17 +1238,19 @@ function axeNoViolations(container) { const message = await render(); await initQuotedMessagePreview(message); message.text = nanoid(); - act(() => { + await act(() => { dispatchMessageUpdatedEvent(chatClient, message, channel); }); await quotedMessagePreviewIsDisplayedCorrectly(message); }); it('is closed on close button click', async () => { + // skip trying to cancel reply for theme version 2 as that is not supported + if (themeVersion === '2') return; const message = await render(); await initQuotedMessagePreview(message); const closeBtn = screen.getByRole('button', { name: /cancel reply/i }); - act(() => { + await act(() => { fireEvent.click(closeBtn); }); quotedMessagePreviewIsNotDisplayed(message); @@ -1193,11 +1259,53 @@ function axeNoViolations(container) { it('is closed on original message delete', async () => { const message = await render(); await initQuotedMessagePreview(message); - act(() => { + await act(() => { dispatchMessageDeletedEvent(chatClient, message, channel); }); quotedMessagePreviewIsNotDisplayed(message); }); }); + + describe('send button', () => { + const SEND_BTN_TEST_ID = 'send-button'; + + it('should be renderer for empty input', async () => { + await render(); + expect(screen.getByTestId(SEND_BTN_TEST_ID)).toBeInTheDocument(); + }); + + it('should be renderer when editing a message', async () => { + await render({ messageInputProps: { message: generateMessage() } }); + expect(screen.getByTestId(SEND_BTN_TEST_ID)).toBeInTheDocument(); + }); + + it('should not be renderer during active cooldown period', async () => { + await renderWithActiveCooldown(); + expect(screen.queryByTestId(SEND_BTN_TEST_ID)).not.toBeInTheDocument(); + }); + + it('should not be renderer if explicitly hidden', async () => { + await render({ messageInputProps: { hideSendButton: true } }); + expect(screen.queryByTestId(SEND_BTN_TEST_ID)).not.toBeInTheDocument(); + }); + }); + + describe('cooldown timer', () => { + const COOLDOWN_TIMER_TEST_ID = 'cooldown-timer'; + + it('should be renderer during active cool-down period', async () => { + await renderWithActiveCooldown(); + expect(screen.getByTestId(COOLDOWN_TIMER_TEST_ID)).toBeInTheDocument(); + }); + + it('should not be renderer if send button explicitly hidden only for MessageInputFlat theme 2', async () => { + await renderWithActiveCooldown({ messageInputProps: { hideSendButton: true } }); + if (componentName === 'MessageInputSmall' || themeVersion === '1') { + expect(screen.queryByTestId(COOLDOWN_TIMER_TEST_ID)).toBeInTheDocument(); + } else { + expect(screen.queryByTestId(COOLDOWN_TIMER_TEST_ID)).not.toBeInTheDocument(); + } + }); + }); }); }); diff --git a/src/components/MessageInput/hooks/__tests__/useCooldownTimer.test.js b/src/components/MessageInput/hooks/__tests__/useCooldownTimer.test.js index d2abb7c79..85508be10 100644 --- a/src/components/MessageInput/hooks/__tests__/useCooldownTimer.test.js +++ b/src/components/MessageInput/hooks/__tests__/useCooldownTimer.test.js @@ -32,7 +32,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAhead = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), + [cid]: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); @@ -44,7 +44,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAgo = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), + [cid]: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); @@ -63,7 +63,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAhead = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), + [cid]: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); @@ -75,7 +75,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAgo = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), + [cid]: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); @@ -84,7 +84,7 @@ describe('useCooldownTimer', () => { it('should set remaining cooldown time to 0 if skip-slow-mode is among own_capabilities', async () => { const channel = { cid, data: { cooldown, own_capabilities: ['skip-slow-mode'] } }; - const chatContext = { latestMessageDatesByChannels: { cid: new Date() } }; + const chatContext = { latestMessageDatesByChannels: { [cid]: new Date() } }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); expect(result.current.cooldownRemaining).toBe(0); }); @@ -98,7 +98,7 @@ describe('useCooldownTimer', () => { it('should set remaining cooldown time to 0 if previous messages sent earlier than channel.cooldown', async () => { const channel = { cid, data: { cooldown } }; - const chatContext = { latestMessageDatesByChannels: { cid: new Date('1970-1-1') } }; + const chatContext = { latestMessageDatesByChannels: { [cid]: new Date('1970-1-1') } }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); expect(result.current.cooldownRemaining).toBe(0); }); @@ -108,7 +108,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAgo = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), + [cid]: new Date(new Date().getTime() - lastSentSecondsAgo * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); @@ -120,7 +120,7 @@ describe('useCooldownTimer', () => { const lastSentSecondsAhead = 5; const chatContext = { latestMessageDatesByChannels: { - cid: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), + [cid]: new Date(new Date().getTime() + lastSentSecondsAhead * 1000), }, }; const { result } = await renderUseCooldownTimerHook({ channel, chatContext }); diff --git a/src/components/MessageInput/hooks/useCreateMessageInputContext.ts b/src/components/MessageInput/hooks/useCreateMessageInputContext.ts index a4e672da8..253f960de 100644 --- a/src/components/MessageInput/hooks/useCreateMessageInputContext.ts +++ b/src/components/MessageInput/hooks/useCreateMessageInputContext.ts @@ -37,6 +37,7 @@ export const useCreateMessageInputContext = < handleChange, handleEmojiKeyDown, handleSubmit, + hideSendButton, imageOrder, imageUploads, insertText, @@ -116,6 +117,7 @@ export const useCreateMessageInputContext = < handleChange, handleEmojiKeyDown, handleSubmit, + hideSendButton, imageOrder, imageUploads, insertText, @@ -161,6 +163,7 @@ export const useCreateMessageInputContext = < emojiPickerIsOpen, fileUploadsValue, findAndEnqueueURLsToEnrich, + hideSendButton, imageUploadsValue, isUploadEnabled, linkPreviewsValue, diff --git a/yarn.lock b/yarn.lock index e46a861bd..a283a271d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13426,10 +13426,10 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" -stream-chat@^8.12.0: - version "8.12.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.12.0.tgz#5b661242f24577fe7b4fd56555bbc47d4f652c48" - integrity sha512-JpH3QICvQ17m4zliOTB1ADuhEdkJSvfDUFJfj4F5ykkM7qvhz9FP/3STnx3W6ltC/ed9xhSYbVmBnA5HuWlEUw== +stream-chat@^8.12.4: + version "8.12.4" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.12.4.tgz#3c2044ceb74b4be8bd06191963723d7d47014ec1" + integrity sha512-YaNuQNqiJcF82+VKAGNSV8wv0+Th73kHIV4Owiu2Bvhwqt7398Ertr+bxW4WXkBtwKoAeOZOc0oacrUKWQAuKQ== dependencies: "@babel/runtime" "^7.16.3" "@types/jsonwebtoken" "~9.0.0"