From 67a302f9f8e2bb2d4091afe504ac0fb9ec82f00c Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Wed, 29 Jan 2025 10:13:24 +0100 Subject: [PATCH] Prettier + ESLint fixes (and some manual fixes) --- e2e/fixtures/data/attachment.mjs | 2 +- eslint.config.mjs | 5 +- .../AIStateIndicator/AIStateIndicator.tsx | 9 +- .../AIStateIndicator/hooks/useAIState.ts | 2 +- src/components/Attachment/Attachment.tsx | 19 +- .../Attachment/AttachmentActions.tsx | 4 +- .../Attachment/AttachmentContainer.tsx | 40 +- src/components/Attachment/Audio.tsx | 4 +- src/components/Attachment/Card.tsx | 37 +- src/components/Attachment/FileAttachment.tsx | 9 +- .../Attachment/UnsupportedAttachment.tsx | 9 +- src/components/Attachment/VoiceRecording.tsx | 34 +- .../Attachment/__tests__/Attachment.test.js | 20 +- .../__tests__/AttachmentActions.test.js | 1 - .../Attachment/__tests__/Audio.test.js | 27 +- .../Attachment/__tests__/Card.test.js | 3 +- .../__tests__/VoiceRecording.test.js | 20 +- .../__tests__/WaveProgressBar.test.js | 41 +- .../__tests__/audioSampling.test.js | 8 +- .../Attachment/attachment-sizing.tsx | 19 +- src/components/Attachment/audioSampling.ts | 30 +- .../components/FileSizeIndicator.tsx | 5 +- .../Attachment/components/ProgressBar.tsx | 5 +- .../Attachment/components/WaveProgressBar.tsx | 3 +- .../Attachment/hooks/useAudioController.ts | 8 +- src/components/Attachment/utils.tsx | 49 +- src/components/AutoCompleteTextarea/Item.jsx | 5 +- src/components/AutoCompleteTextarea/List.jsx | 14 +- .../AutoCompleteTextarea/Textarea.jsx | 28 +- src/components/AutoCompleteTextarea/utils.js | 10 +- src/components/Avatar/Avatar.tsx | 9 +- src/components/Avatar/ChannelAvatar.tsx | 8 +- src/components/Avatar/GroupAvatar.tsx | 5 +- .../Avatar/__tests__/Avatar.test.js | 14 +- src/components/Channel/Channel.tsx | 505 ++++++++++-------- .../Channel/__tests__/Channel.test.js | 265 ++++++--- src/components/Channel/channelState.ts | 336 ++++++------ .../hooks/useChannelContainerClasses.ts | 7 +- .../hooks/useCreateChannelStateContext.ts | 26 +- .../Channel/hooks/useCreateTypingContext.ts | 2 +- .../Channel/hooks/useEditMessageHandler.ts | 18 +- .../Channel/hooks/useMentionsHandlers.ts | 24 +- src/components/Channel/utils.ts | 51 +- .../ChannelHeader/ChannelHeader.tsx | 11 +- .../__tests__/ChannelHeader.test.js | 50 +- src/components/ChannelList/ChannelList.tsx | 64 ++- .../ChannelList/ChannelListMessenger.tsx | 4 +- .../ChannelList/__tests__/ChannelList.test.js | 106 +++- .../hooks/useChannelDeletedListener.ts | 2 +- .../hooks/useChannelHiddenListener.ts | 2 +- .../ChannelList/hooks/useChannelListShape.ts | 174 +++--- .../hooks/useChannelMembershipState.ts | 11 +- .../hooks/useChannelTruncatedListener.ts | 2 +- .../hooks/useChannelUpdatedListener.ts | 9 +- .../hooks/useChannelVisibleListener.ts | 2 +- .../hooks/useConnectionRecoveredListener.ts | 2 +- .../hooks/useMessageNewListener.ts | 13 +- .../useNotificationAddedToChannelListener.ts | 6 +- .../useNotificationMessageNewListener.ts | 6 +- ...eNotificationRemovedFromChannelListener.ts | 6 +- .../ChannelList/hooks/usePaginatedChannels.ts | 33 +- .../hooks/useSelectedChannelState.ts | 2 +- .../hooks/useUserPresenceChangedListener.ts | 2 +- src/components/ChannelList/utils.ts | 29 +- .../ChannelPreview/ChannelPreview.tsx | 17 +- .../ChannelPreviewMessenger.tsx | 12 +- .../__tests__/ChannelPreview.test.js | 36 +- .../__tests__/ChannelPreviewMessenger.test.js | 1 - .../ChannelPreview/__tests__/utils.test.js | 18 +- .../useMessageDeliveryStatus.test.js | 9 +- .../hooks/useChannelPreviewInfo.ts | 6 +- .../ChannelPreview/hooks/useIsChannelMuted.ts | 2 +- .../hooks/useMessageDeliveryStatus.ts | 19 +- src/components/ChannelPreview/icons.tsx | 1 - src/components/ChannelPreview/utils.tsx | 29 +- .../ChannelSearch/ChannelSearch.tsx | 21 +- src/components/ChannelSearch/SearchBar.tsx | 10 +- .../ChannelSearch/SearchResults.tsx | 41 +- .../__tests__/ChannelSearch.test.js | 60 ++- .../ChannelSearch/__tests__/SearchBar.test.js | 2 +- .../__tests__/SearchResults.test.js | 6 +- .../ChannelSearch/hooks/useChannelSearch.ts | 53 +- src/components/ChannelSearch/icons.tsx | 8 +- src/components/ChannelSearch/index.ts | 5 +- src/components/ChannelSearch/utils.ts | 7 +- src/components/Chat/Chat.tsx | 4 +- .../Chat/hooks/useChannelsQueryState.ts | 3 +- src/components/Chat/hooks/useChat.ts | 20 +- .../Chat/hooks/useCreateChatClient.ts | 8 +- .../Chat/hooks/useCreateChatContext.ts | 2 +- .../ChatAutoComplete/ChatAutoComplete.tsx | 99 ++-- .../__tests__/ChatAutocomplete.test.js | 13 +- src/components/ChatView/ChatView.tsx | 10 +- src/components/CommandItem/CommandItem.tsx | 4 +- .../CommandItem/__tests__/CommandItem.test.js | 2 +- .../DateSeparator/DateSeparator.tsx | 4 +- .../__tests__/DateSeparator.test.js | 8 +- src/components/Dialog/DialogManager.ts | 15 +- src/components/Dialog/DialogMenu.tsx | 6 +- src/components/Dialog/FormDialog.tsx | 16 +- src/components/Dialog/PromptDialog.tsx | 7 +- .../Dialog/__tests__/DialogsManager.test.js | 8 +- .../DragAndDrop/DragAndDropContainer.tsx | 13 +- src/components/EmoticonItem/EmoticonItem.tsx | 4 +- .../__tests__/EmoticonItem.test.js | 2 +- .../__tests__/EmptyStateIndicator.test.js | 2 +- .../EventComponent/EventComponent.tsx | 10 +- .../__tests__/EventComponent.test.js | 17 +- src/components/Form/SwitchField.tsx | 8 +- src/components/Gallery/Gallery.tsx | 13 +- src/components/Gallery/Image.tsx | 19 +- src/components/Gallery/ModalGallery.tsx | 14 +- .../Gallery/__tests__/Gallery.test.js | 4 +- .../Gallery/__tests__/Image.test.js | 10 +- .../Gallery/__tests__/ModalGallery.test.js | 4 +- .../InfiniteScroll.tsx | 14 +- .../InfiniteScrollPaginator.tsx | 15 +- .../__tests__/InfiniteScroll.test.js | 19 +- .../hooks/useCursorPaginator.ts | 5 +- src/components/LoadMore/LoadMorePaginator.tsx | 14 +- .../LoadMore/__tests__/LoadMoreButton.test.js | 8 +- .../Loading/LoadingErrorIndicator.tsx | 4 +- src/components/Loading/LoadingIndicator.tsx | 7 +- .../Loading/__tests__/LoadingChannels.test.js | 2 +- .../__tests__/LoadingErrorIndicator.test.js | 6 +- .../__tests__/LoadingIndicator.test.js | 2 +- src/components/MML/__tests__/MML.test.js | 2 +- .../AudioRecordingInProgress.tsx | 3 +- .../AudioRecorder/AudioRecordingPreview.tsx | 15 +- .../__tests__/AudioRecorder.test.js | 39 +- .../__tests__/AudioRecordingPreview.test.js | 3 +- .../classes/AmplitudeRecorder.ts | 15 +- .../classes/BrowserPermission.ts | 4 +- .../classes/MediaRecorderController.ts | 21 +- .../__tests__/AmplitudeRecorder.test.js | 13 +- .../__tests__/MediaRecorderController.test.js | 243 +++++---- .../hooks/__tests__/useMediaRecorder.test.js | 22 +- .../MediaRecorder/hooks/useMediaRecorder.ts | 15 +- src/components/MediaRecorder/transcode/wav.ts | 17 +- src/components/Message/FixedHeightMessage.tsx | 33 +- src/components/Message/Message.tsx | 18 +- src/components/Message/MessageDeleted.tsx | 4 +- .../Message/MessageEditedTimestamp.tsx | 10 +- src/components/Message/MessageErrorText.tsx | 12 +- src/components/Message/MessageOptions.tsx | 8 +- src/components/Message/MessageSimple.tsx | 33 +- src/components/Message/MessageStatus.tsx | 24 +- src/components/Message/MessageText.tsx | 31 +- src/components/Message/MessageTimestamp.tsx | 7 +- src/components/Message/QuotedMessage.tsx | 20 +- .../Message/StreamedMessageText.tsx | 14 +- src/components/Message/Timestamp.tsx | 19 +- .../__tests__/FixedHeightMessage.test.js | 5 +- .../Message/__tests__/Message.test.js | 28 +- .../Message/__tests__/MessageDeleted.test.js | 4 +- .../Message/__tests__/MessageOptions.test.js | 67 ++- .../MessageRepliesCountButton.test.js | 13 +- .../Message/__tests__/MessageSimple.test.js | 48 +- .../Message/__tests__/MessageStatus.test.js | 14 +- .../Message/__tests__/MessageText.test.js | 37 +- .../__tests__/MessageTimestamp.test.js | 15 +- .../Message/__tests__/utils.test.js | 19 +- .../hooks/__tests__/useEditHandler.test.js | 6 +- .../hooks/__tests__/useFlagHandler.test.js | 4 +- .../hooks/__tests__/useMuteHandler.test.js | 4 +- .../__tests__/useOpenThreadHandler.test.js | 5 +- .../__tests__/useReactionHandler.test.js | 4 +- .../hooks/__tests__/useReactionsFetcher.js | 19 +- .../hooks/__tests__/useUserRole.test.js | 89 +-- .../__tests__/userMarkUnreadHandler.test.js | 14 +- .../Message/hooks/useActionHandler.ts | 12 +- .../Message/hooks/useDeleteHandler.ts | 9 +- .../Message/hooks/useFlagHandler.ts | 7 +- .../Message/hooks/useMarkUnreadHandler.ts | 7 +- .../Message/hooks/useMentionsHandler.ts | 18 +- .../Message/hooks/useMessageTextStreaming.ts | 6 +- .../Message/hooks/useMuteHandler.ts | 18 +- .../Message/hooks/useOpenThreadHandler.ts | 11 +- src/components/Message/hooks/usePinHandler.ts | 23 +- .../Message/hooks/useReactionHandler.ts | 32 +- .../Message/hooks/useReactionsFetcher.ts | 6 +- .../Message/hooks/useRetryHandler.ts | 12 +- .../Message/hooks/useUserHandler.ts | 4 +- src/components/Message/hooks/useUserRole.ts | 15 +- src/components/Message/icons.tsx | 2 +- .../renderText/__tests__/renderText.test.js | 37 +- .../renderText/componentRenderers/Mention.tsx | 4 +- .../rehypePlugins/mentionsMarkdownPlugin.ts | 108 ++-- .../remarkPlugins/keepLineBreaksPlugin.ts | 5 +- .../Message/renderText/renderText.tsx | 37 +- src/components/Message/types.ts | 30 +- src/components/Message/utils.tsx | 36 +- .../CustomMessageActionsList.tsx | 4 +- .../MessageActions/MessageActions.tsx | 11 +- .../MessageActions/MessageActionsBox.tsx | 44 +- .../__tests__/MessageActions.test.js | 11 +- .../__tests__/MessageActionsBox.test.js | 25 +- .../hooks/useMessageActionsBoxPopper.ts | 30 +- .../MessageBounce/MessageBouncePrompt.tsx | 7 +- .../AttachmentPreviewList.tsx | 11 +- .../FileAttachmentPreview.tsx | 44 +- .../ImageAttachmentPreview.tsx | 4 +- .../UnsupportedAttachmentPreview.tsx | 30 +- .../VoiceRecordingPreview.tsx | 10 +- .../AttachmentPreviewList/types.ts | 2 +- .../MessageInput/AttachmentSelector.tsx | 43 +- .../MessageInput/DefaultTriggerProvider.tsx | 31 +- .../MessageInput/DropzoneProvider.tsx | 23 +- .../MessageInput/EditMessageForm.tsx | 15 +- .../MessageInput/LinkPreviewList.tsx | 20 +- src/components/MessageInput/MessageInput.tsx | 26 +- .../MessageInput/MessageInputFlat.tsx | 16 +- .../MessageInput/QuotedMessagePreview.tsx | 25 +- src/components/MessageInput/SendButton.tsx | 4 +- .../MessageInput/StopAIGenerationButton.tsx | 5 +- .../__tests__/AttachmentPreviewList.test.js | 77 +-- .../__tests__/AttachmentSelector.test.js | 46 +- .../__tests__/CooldownTimer.test.js | 4 +- .../__tests__/LinkPreviewList.test.js | 199 ++++--- .../__tests__/MessageInput.test.js | 207 ++++--- .../__tests__/useMessageInputState.test.js | 468 ++++++++-------- .../MessageInput/hooks/useAttachments.ts | 47 +- .../MessageInput/hooks/useCommandTrigger.ts | 24 +- .../MessageInput/hooks/useCooldownTimer.tsx | 14 +- .../hooks/useCreateMessageInputContext.ts | 2 +- .../MessageInput/hooks/useLinkPreviews.ts | 17 +- .../hooks/useMessageInputState.ts | 79 +-- .../MessageInput/hooks/useMessageInputText.ts | 11 +- .../MessageInput/hooks/useSubmitHandler.ts | 41 +- .../MessageInput/hooks/useUserTrigger.ts | 13 +- src/components/MessageInput/hooks/utils.ts | 22 +- src/components/MessageInput/icons.tsx | 16 +- src/components/MessageInput/types.ts | 68 +-- .../MessageList/ConnectionStatus.tsx | 6 +- .../MessageList/CustomNotification.tsx | 4 +- .../MessageList/GiphyPreviewMessage.tsx | 4 +- src/components/MessageList/MessageList.tsx | 59 +- .../MessageList/MessageListMainPanel.tsx | 3 +- .../MessageList/MessageListNotifications.tsx | 4 +- .../MessageList/ScrollToBottomButton.tsx | 5 +- .../MessageList/UnreadMessagesSeparator.tsx | 5 +- .../MessageList/VirtualizedMessageList.tsx | 114 ++-- .../VirtualizedMessageListComponents.tsx | 26 +- .../__tests__/CustomNotification.test.js | 10 +- .../MessageList/__tests__/MessageList.test.js | 67 ++- .../__tests__/MessageNotification.test.js | 2 +- .../__tests__/ScrollToBottomButton.test.js | 41 +- .../VirtualizedMessageListComponents.test.js | 67 ++- .../MessageList/__tests__/utils.test.js | 105 +++- .../hooks/MessageList/useEnrichedMessages.ts | 5 +- .../MessageList/useMessageListElements.tsx | 20 +- .../useMessageListScrollManager.ts | 7 +- .../MessageList/useScrollLocationLogic.tsx | 14 +- .../useUnreadMessagesNotification.ts | 24 +- .../VirtualizedMessageList/useGiphyPreview.ts | 7 +- .../useMessageSetKey.ts | 8 +- .../useNewMessageNotification.ts | 5 +- .../usePrependMessagesCount.ts | 17 +- .../useScrollToBottomOnNewMessage.ts | 7 +- .../useShouldForceScrollToBottom.ts | 2 +- ...seUnreadMessagesNotificationVirtualized.ts | 12 +- .../hooks/__tests__/useGiphyPreview.test.js | 4 +- .../hooks/__tests__/useMarkRead.test.js | 38 +- .../useMessageListScrollManager.test.js | 3 +- .../__tests__/usePrependMessagesCount.test.js | 13 +- .../MessageList/hooks/useLastReadData.ts | 4 +- .../MessageList/hooks/useMarkRead.ts | 14 +- src/components/MessageList/renderMessages.tsx | 26 +- src/components/MessageList/utils.ts | 67 ++- src/components/Modal/Modal.tsx | 28 +- src/components/Modal/ModalHeader.tsx | 4 +- src/components/Modal/__tests__/Modal.test.js | 2 +- src/components/Poll/Poll.tsx | 8 +- .../Poll/PollActions/AddCommentForm.tsx | 4 +- .../Poll/PollActions/EndPollDialog.tsx | 2 +- .../Poll/PollActions/PollActions.tsx | 55 +- .../Poll/PollActions/PollAnswerList.tsx | 13 +- .../Poll/PollActions/PollOptionsFullList.tsx | 4 +- .../PollResults/PollOptionVotesList.tsx | 17 +- .../PollResults/PollOptionWithLatestVotes.tsx | 23 +- .../PollResults/PollOptionWithVotesHeader.tsx | 13 +- .../PollActions/PollResults/PollResults.tsx | 14 +- .../PollActions/SuggestPollOptionForm.tsx | 6 +- src/components/Poll/PollContent.tsx | 10 +- .../PollCreationDialog/OptionFieldSet.tsx | 25 +- .../PollCreationDialog/PollCreationDialog.tsx | 27 +- .../PollCreationDialogControls.tsx | 13 +- src/components/Poll/PollHeader.tsx | 12 +- src/components/Poll/PollOptionList.tsx | 11 +- src/components/Poll/PollOptionSelector.tsx | 34 +- src/components/Poll/PollVote.tsx | 16 +- src/components/Poll/QuotedPoll.tsx | 9 +- src/components/Poll/__tests__/Poll.test.js | 10 +- .../Poll/__tests__/PollActions.test.js | 23 +- .../Poll/__tests__/PollCreationDialog.test.js | 47 +- .../Poll/__tests__/PollHeader.test.js | 20 +- .../Poll/__tests__/PollOptionList.test.js | 28 +- .../__tests__/SuggestPollOptionForm.test.js | 4 +- .../Poll/hooks/useManagePollVotesRealtime.ts | 20 +- .../Poll/hooks/usePollAnswerPagination.ts | 22 +- .../hooks/usePollOptionVotesPagination.ts | 23 +- .../ReactFileUtilities/FileIcon/FileIcon.tsx | 9 +- .../FileIcon/FileIconSet.tsx | 84 ++- .../ReactFileUtilities/FileIcon/iconMap.ts | 5 +- .../ReactFileUtilities/FileIcon/mimeTypes.ts | 10 +- .../ReactFileUtilities/ImageDropzone.tsx | 14 +- .../ReactFileUtilities/UploadButton.tsx | 14 +- src/components/ReactFileUtilities/utils.ts | 12 +- src/components/Reactions/ReactionSelector.tsx | 18 +- .../Reactions/ReactionSelectorWithButton.tsx | 14 +- src/components/Reactions/ReactionsList.tsx | 28 +- .../Reactions/ReactionsListModal.tsx | 52 +- .../Reactions/SimpleReactionsList.tsx | 18 +- src/components/Reactions/SpriteImage.tsx | 8 +- .../__tests__/ReactionSelector.test.js | 4 +- .../Reactions/__tests__/ReactionsList.test.js | 34 +- .../Reactions/hooks/useFetchReactions.ts | 9 +- .../Reactions/hooks/useProcessReactions.tsx | 15 +- src/components/Reactions/reactionOptions.tsx | 26 +- src/components/Reactions/types.ts | 9 +- src/components/Reactions/utils/utils.ts | 4 +- .../SafeAnchor/__tests__/SafeAnchor.test.js | 4 +- src/components/Thread/Thread.tsx | 24 +- src/components/Thread/ThreadHead.tsx | 7 +- src/components/Thread/ThreadHeader.tsx | 4 +- .../Thread/__tests__/Thread.test.js | 40 +- .../Thread/__tests__/ThreadStart.test.js | 6 +- src/components/Threads/ThreadContext.tsx | 5 +- .../Threads/ThreadList/ThreadList.tsx | 2 +- .../Threads/ThreadList/ThreadListItem.tsx | 5 +- .../Threads/ThreadList/ThreadListItemUI.tsx | 20 +- src/components/Threads/icons.tsx | 1 - src/components/Tooltip/Tooltip.tsx | 2 +- .../TypingIndicator/TypingIndicator.tsx | 5 +- .../__tests__/TypingIndicator.test.js | 18 +- .../UserItem/__tests__/UserItem.test.js | 2 +- src/components/Window/Window.tsx | 4 +- .../Window/__tests__/Window.test.js | 27 +- src/context/AttachmentSelectorContext.tsx | 4 +- src/context/ChannelActionContext.tsx | 34 +- src/context/ChannelListContext.tsx | 14 +- src/context/ChannelStateContext.tsx | 22 +- src/context/ChatContext.tsx | 16 +- src/context/ComponentContext.tsx | 35 +- src/context/DialogManagerContext.tsx | 5 +- src/context/MessageBounceContext.tsx | 24 +- src/context/MessageContext.tsx | 25 +- src/context/MessageInputContext.tsx | 6 +- src/context/MessageListContext.tsx | 4 +- src/context/PollContext.tsx | 10 +- src/context/TranslationContext.tsx | 4 +- src/context/TypingContext.tsx | 14 +- src/context/VirtualizedMessageListContext.tsx | 4 +- src/context/WithComponents.tsx | 6 +- src/context/utils/getDisplayName.ts | 5 +- .../MessageActions/MessageActions.tsx | 18 +- src/experimental/MessageActions/defaults.tsx | 41 +- .../hooks/useBaseMessageActionSetFilter.ts | 3 +- src/i18n/Streami18n.ts | 23 +- src/i18n/__tests__/Streami18n.test.js | 57 +- src/i18n/__tests__/utils.test.js | 34 +- src/i18n/utils.ts | 98 ++-- src/mock-builders/__test__/translator.test.js | 13 +- src/mock-builders/api/index.js | 5 +- src/mock-builders/generator/attachment.js | 151 ++---- src/mock-builders/generator/channel.js | 4 +- src/mock-builders/index.js | 1 - src/mock-builders/utils.js | 17 +- src/plugins/Emojis/EmojiPicker.tsx | 4 +- src/plugins/encoders/mp3.ts | 10 +- src/store/hooks/useStateStore.ts | 6 +- src/stories/add-message.stories.tsx | 6 +- src/stories/attachment-sizing.stories.tsx | 10 +- src/stories/edit-message.stories.tsx | 8 +- src/stories/hello.stories.tsx | 1 - src/stories/jump-to-message.stories.tsx | 1 - src/stories/mark-read.stories.tsx | 6 +- .../message-status-readby-tooltip.stories.tsx | 11 +- .../navigate-long-message-lists.stories.tsx | 10 +- src/stories/pin-message.stories.tsx | 4 +- .../toggle-message-actions.stories.tsx | 10 +- src/stories/utils.tsx | 3 +- src/types/types.ts | 6 +- src/utils/__tests__/getChannel.test.js | 5 +- src/utils/getChannel.ts | 12 +- 385 files changed, 5768 insertions(+), 3330 deletions(-) diff --git a/e2e/fixtures/data/attachment.mjs b/e2e/fixtures/data/attachment.mjs index 5e468d85f..f89dbaa54 100644 --- a/e2e/fixtures/data/attachment.mjs +++ b/e2e/fixtures/data/attachment.mjs @@ -1,4 +1,4 @@ -/* eslint-disable sort-keys */ + const smallImageAttachment = [ { type: 'image', diff --git a/eslint.config.mjs b/eslint.config.mjs index 994bb3a3c..80c0b1af9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -57,10 +57,7 @@ export default tseslint.config( 'object-shorthand': 'warn', 'prefer-const': 'warn', 'require-await': 'error', - "sort-destructure-keys/sort-destructure-keys": [ - "error", - { caseSensitive: false }, - ], + 'sort-destructure-keys/sort-destructure-keys': ['error', { caseSensitive: false }], 'sort-imports': [ 'error', { diff --git a/src/components/AIStateIndicator/AIStateIndicator.tsx b/src/components/AIStateIndicator/AIStateIndicator.tsx index 2bbe572f1..977bd240a 100644 --- a/src/components/AIStateIndicator/AIStateIndicator.tsx +++ b/src/components/AIStateIndicator/AIStateIndicator.tsx @@ -8,20 +8,19 @@ import { useChannelStateContext, useTranslationContext } from '../../context'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type AIStateIndicatorProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { channel?: Channel; }; export const AIStateIndicator = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ channel: channelFromProps, }: AIStateIndicatorProps) => { const { t } = useTranslationContext(); - const { channel: channelFromContext } = useChannelStateContext( - 'AIStateIndicator', - ); + const { channel: channelFromContext } = + useChannelStateContext('AIStateIndicator'); const channel = channelFromProps || channelFromContext; const { aiState } = useAIState(channel); const allowedStates = { diff --git a/src/components/AIStateIndicator/hooks/useAIState.ts b/src/components/AIStateIndicator/hooks/useAIState.ts index 54e955a6f..33d744fda 100644 --- a/src/components/AIStateIndicator/hooks/useAIState.ts +++ b/src/components/AIStateIndicator/hooks/useAIState.ts @@ -18,7 +18,7 @@ export const AIStates = { * @returns {{ aiState: AIState }} The current AI state for the given channel. */ export const useAIState = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel?: Channel, ): { aiState: AIState } => { diff --git a/src/components/Attachment/Attachment.tsx b/src/components/Attachment/Attachment.tsx index f1c32c43a..93e70adcc 100644 --- a/src/components/Attachment/Attachment.tsx +++ b/src/components/Attachment/Attachment.tsx @@ -55,7 +55,7 @@ export const ATTACHMENT_GROUPS_ORDER = [ ] as const; export type AttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** The message attachments to render, see [attachment structure](https://getstream.io/chat/docs/javascript/message_format/?language=javascript) **/ attachments: StreamAttachment[]; @@ -87,14 +87,17 @@ export type AttachmentProps< * A component used for rendering message attachments. By default, the component supports: AttachmentActions, Audio, Card, File, Gallery, Image, and Video */ export const Attachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: AttachmentProps, ) => { const { attachments } = props; - // eslint-disable-next-line react-hooks/exhaustive-deps - const groupedAttachments = useMemo(() => renderGroupedAttachments(props), [attachments]); + const groupedAttachments = useMemo( + () => renderGroupedAttachments(props), + // eslint-disable-next-line react-hooks/exhaustive-deps + [attachments], + ); return (
@@ -107,13 +110,13 @@ export const Attachment = < }; const renderGroupedAttachments = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachments, ...rest }: AttachmentProps): GroupedRenderedAttachment => { - const uploadedImages: StreamAttachment[] = attachments.filter((attachment) => - isUploadedImage(attachment), + const uploadedImages: StreamAttachment[] = attachments.filter( + (attachment) => isUploadedImage(attachment), ); const containers = attachments @@ -169,7 +172,7 @@ const renderGroupedAttachments = < }; const getAttachmentType = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: AttachmentProps['attachments'][number], ): keyof typeof CONTAINER_MAP => { diff --git a/src/components/Attachment/AttachmentActions.tsx b/src/components/Attachment/AttachmentActions.tsx index 72fef6982..ec323edfb 100644 --- a/src/components/Attachment/AttachmentActions.tsx +++ b/src/components/Attachment/AttachmentActions.tsx @@ -7,7 +7,7 @@ import type { ActionHandlerReturnType } from '../Message/hooks/useActionHandler' import type { DefaultStreamChatGenerics } from '../../types/types'; export type AttachmentActionsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Attachment & { /** A list of actions */ actions: Action[]; @@ -20,7 +20,7 @@ export type AttachmentActionsProps< }; const UnMemoizedAttachmentActions = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: AttachmentActionsProps, ) => { diff --git a/src/components/Attachment/AttachmentContainer.tsx b/src/components/Attachment/AttachmentContainer.tsx index 94e73d179..cb003a423 100644 --- a/src/components/Attachment/AttachmentContainer.tsx +++ b/src/components/Attachment/AttachmentContainer.tsx @@ -30,13 +30,13 @@ import type { import type { Attachment } from 'stream-chat'; export type AttachmentContainerProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { attachment: Attachment | GalleryAttachment; componentType: AttachmentComponentType; }; export const AttachmentWithinContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, children, @@ -50,8 +50,8 @@ export const AttachmentWithinContainer = < componentType === 'card' && !attachment?.image_url && !attachment?.thumb_url ? 'no-image' : attachment?.actions?.length - ? 'actions' - : ''; + ? 'actions' + : ''; } const classNames = clsx( @@ -59,7 +59,8 @@ export const AttachmentWithinContainer = < { [`str-chat__message-attachment--${componentType}`]: componentType, [`str-chat__message-attachment--${attachment?.type}`]: attachment?.type, - [`str-chat__message-attachment--${componentType}--${extra}`]: componentType && extra, + [`str-chat__message-attachment--${componentType}--${extra}`]: + componentType && extra, 'str-chat__message-attachment--svg-image': isSvgAttachment(attachment), 'str-chat__message-attachment-with-actions': extra === 'actions', }, @@ -69,7 +70,7 @@ export const AttachmentWithinContainer = < }; export const AttachmentActionsContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ actionHandler, attachment, @@ -108,7 +109,7 @@ function getCssDimensionsVariables(url: string) { } export const GalleryContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, Gallery = DefaultGallery, @@ -150,7 +151,7 @@ export const GalleryContainer = < }; export const ImageContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: RenderAttachmentProps, ) => { @@ -194,7 +195,7 @@ export const ImageContainer = < }; export const CardContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: RenderAttachmentProps, ) => { @@ -220,7 +221,7 @@ export const CardContainer = < }; export const FileContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, File = DefaultFile, @@ -234,7 +235,7 @@ export const FileContainer = < ); }; export const AudioContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, Audio = DefaultAudio, @@ -247,11 +248,11 @@ export const AudioContainer = < ); export const VoiceRecordingContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, - VoiceRecording = DefaultVoiceRecording, isQuoted, + VoiceRecording = DefaultVoiceRecording, }: RenderAttachmentProps) => (
@@ -261,18 +262,17 @@ export const VoiceRecordingContainer = < ); export const MediaContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: RenderAttachmentProps, ) => { const { attachment, Media = ReactPlayer } = props; const componentType = 'media'; - const { shouldGenerateVideoThumbnail, videoAttachmentSizeHandler } = useChannelStateContext(); + const { shouldGenerateVideoThumbnail, videoAttachmentSizeHandler } = + useChannelStateContext(); const videoElement = useRef(null); - const [ - attachmentConfiguration, - setAttachmentConfiguration, - ] = useState(); + const [attachmentConfiguration, setAttachmentConfiguration] = + useState(); useLayoutEffect(() => { if (videoElement.current && videoAttachmentSizeHandler) { @@ -319,7 +319,7 @@ export const MediaContainer = < }; export const UnsupportedAttachmentContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, UnsupportedAttachment = DefaultUnsupportedAttachment, diff --git a/src/components/Attachment/Audio.tsx b/src/components/Attachment/Audio.tsx index baff2d7ef..c9b76a100 100644 --- a/src/components/Attachment/Audio.tsx +++ b/src/components/Attachment/Audio.tsx @@ -8,14 +8,14 @@ import { useAudioController } from './hooks/useAudioController'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type AudioProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { // fixme: rename og to attachment og: Attachment; }; const UnMemoizedAudio = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: AudioProps, ) => { diff --git a/src/components/Attachment/Card.tsx b/src/components/Attachment/Card.tsx index 93d7e39aa..b69605593 100644 --- a/src/components/Attachment/Card.tsx +++ b/src/components/Attachment/Card.tsx @@ -41,8 +41,14 @@ const UnableToRenderCard = ({ type }: { type?: CardProps['type'] }) => { ); }; -const SourceLink = ({ author_name, url }: Pick & { url: string }) => ( -
+const SourceLink = ({ + author_name, + url, +}: Pick & { url: string }) => ( +
{ let visual = null; if (asset_url && type === 'video') { visual = ( - + ); } else if (image) { visual = ( @@ -104,7 +116,9 @@ const CardContent = (props: CardContentProps) => { ) : (
{url && } - {title &&
{title}
} + {title && ( +
{title}
+ )} {text &&
{text}
}
)} @@ -139,9 +153,13 @@ export const CardAudio = ({ )}
{url && } - {title &&
{title}
} + {title && ( +
{title}
+ )} {text && ( -
{text}
+
+ {text} +
)}
@@ -158,7 +176,8 @@ const UnMemoizedCard = (props: CardProps) => { const dimensions: { height?: string; width?: string } = {}; if (type === 'giphy' && typeof giphy !== 'undefined') { - const giphyVersion = giphy[giphyVersionName as keyof NonNullable]; + const giphyVersion = + giphy[giphyVersionName as keyof NonNullable]; image = giphyVersion.url; dimensions.height = giphyVersion.height; dimensions.width = giphyVersion.width; @@ -169,7 +188,9 @@ const UnMemoizedCard = (props: CardProps) => { } return ( -
+
diff --git a/src/components/Attachment/FileAttachment.tsx b/src/components/Attachment/FileAttachment.tsx index 0e66e74a7..7380e11af 100644 --- a/src/components/Attachment/FileAttachment.tsx +++ b/src/components/Attachment/FileAttachment.tsx @@ -7,13 +7,13 @@ import { DownloadButton, FileSizeIndicator } from './components'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type FileAttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { attachment: Attachment; }; const UnMemoizedFileAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, }: FileAttachmentProps) => ( @@ -21,7 +21,10 @@ const UnMemoizedFileAttachment = <
-
+
{attachment.title}
diff --git a/src/components/Attachment/UnsupportedAttachment.tsx b/src/components/Attachment/UnsupportedAttachment.tsx index e738ec8fe..eb81a2bec 100644 --- a/src/components/Attachment/UnsupportedAttachment.tsx +++ b/src/components/Attachment/UnsupportedAttachment.tsx @@ -5,19 +5,22 @@ import type { Attachment } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type UnsupportedAttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { attachment: Attachment; }; export const UnsupportedAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ attachment, }: UnsupportedAttachmentProps) => { const { t } = useTranslationContext('UnsupportedAttachment'); return ( -
+
= Pick, 'attachment'> & { /** An array of fractional numeric values of playback speed to override the defaults (1.0, 1.5, 2.0) */ playbackRates?: number[]; }; -export const VoiceRecordingPlayer = ({ attachment, playbackRates }: VoiceRecordingPlayerProps) => { +export const VoiceRecordingPlayer = ({ + attachment, + playbackRates, +}: VoiceRecordingPlayerProps) => { const { t } = useTranslationContext('VoiceRecordingPlayer'); const { asset_url, @@ -66,10 +74,17 @@ export const VoiceRecordingPlayer = ({ attachment, playbackRates }: VoiceRecordi {attachment.duration ? ( displayDuration(displayedDuration) ) : ( - + )}
- +
@@ -86,7 +101,7 @@ export const VoiceRecordingPlayer = ({ attachment, playbackRates }: VoiceRecordi }; export type QuotedVoiceRecordingProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'attachment'>; export const QuotedVoiceRecording = ({ attachment }: QuotedVoiceRecordingProps) => { @@ -109,7 +124,10 @@ export const QuotedVoiceRecording = ({ attachment }: QuotedVoiceRecordingProps) {attachment.duration ? ( displayDuration(attachment.duration) ) : ( - + )}
@@ -120,7 +138,7 @@ export const QuotedVoiceRecording = ({ attachment }: QuotedVoiceRecordingProps) }; export type VoiceRecordingProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** The attachment object from the message's attachment list. */ attachment: Attachment; diff --git a/src/components/Attachment/__tests__/Attachment.test.js b/src/components/Attachment/__tests__/Attachment.test.js index ee24ee014..29a289bc9 100644 --- a/src/components/Attachment/__tests__/Attachment.test.js +++ b/src/components/Attachment/__tests__/Attachment.test.js @@ -28,7 +28,9 @@ const Media = (props) =>
{props.customTestId const AttachmentActions = () =>
; const Image = (props) =>
{props.customTestId}
; const File = (props) =>
{props.customTestId}
; -const Gallery = (props) =>
{props.customTestId}
; +const Gallery = (props) => ( +
{props.customTestId}
+); const ATTACHMENTS = { scraped: { @@ -102,12 +104,15 @@ describe('attachment', () => { ${cases.file.attachments} | ${cases.file.case} | ${cases.file.renderedComponent} | ${cases.file.testId} ${cases.gallery.attachments} | ${cases.gallery.case} | ${cases.gallery.renderedComponent} | ${cases.gallery.testId} ${cases.image.attachments} | ${cases.image.case} | ${cases.image.renderedComponent} | ${cases.image.testId} - `('should render $renderedComponent component for $case', async ({ attachments, testId }) => { - renderComponent({ attachments }); - await waitFor(() => { - expect(screen.getByTestId(testId)).toBeInTheDocument(); - }); - }); + `( + 'should render $renderedComponent component for $case', + async ({ attachments, testId }) => { + renderComponent({ attachments }); + await waitFor(() => { + expect(screen.getByTestId(testId)).toBeInTheDocument(); + }); + }, + ); it.each(SUPPORTED_VIDEO_FORMATS.map((f) => [f]))( 'should render Media component for video of %s mime-type attachment', @@ -213,7 +218,6 @@ describe('attachment', () => { ], }); await waitFor(() => { - /* eslint-disable jest-dom/prefer-in-document */ const Card = queryAllByTestId('card-attachment'); expect(Card).toHaveLength(3); diff --git a/src/components/Attachment/__tests__/AttachmentActions.test.js b/src/components/Attachment/__tests__/AttachmentActions.test.js index 9fb11d6fc..d3074d2e0 100644 --- a/src/components/Attachment/__tests__/AttachmentActions.test.js +++ b/src/components/Attachment/__tests__/AttachmentActions.test.js @@ -48,7 +48,6 @@ describe('AttachmentActions', () => { fireEvent.click(getByTestId(actions[1].name)); await waitFor(() => { - // eslint-disable-next-line jest/prefer-called-with expect(actionHandler).toHaveBeenCalledTimes(2); }); }); diff --git a/src/components/Attachment/__tests__/Audio.test.js b/src/components/Attachment/__tests__/Audio.test.js index ff5388325..b88b20109 100644 --- a/src/components/Attachment/__tests__/Audio.test.js +++ b/src/components/Attachment/__tests__/Audio.test.js @@ -15,7 +15,7 @@ const originalConsoleError = console.error; jest.spyOn(console, 'error').mockImplementationOnce((...errorOrTextorArg) => { const msg = Array.isArray(errorOrTextorArg) ? errorOrTextorArg[0] - : errorOrTextorArg.message ?? errorOrTextorArg; + : (errorOrTextorArg.message ?? errorOrTextorArg); if (msg.match('Not implemented')) return; originalConsoleError(...errorOrTextorArg); }); @@ -70,7 +70,9 @@ describe('Audio', () => { .spyOn(HTMLDivElement.prototype, 'getBoundingClientRect') .mockImplementationOnce(() => ({ width: 120, x: 0 })); - jest.spyOn(HTMLAudioElement.prototype, 'currentTime', 'set').mockImplementationOnce(() => {}); + jest + .spyOn(HTMLAudioElement.prototype, 'currentTime', 'set') + .mockImplementationOnce(() => {}); jest.spyOn(HTMLAudioElement.prototype, 'duration', 'get').mockReturnValue(120); act(() => { @@ -171,7 +173,10 @@ describe('Audio', () => { jest.advanceTimersByTime(2000); await waitFor(() => { expect(audioPauseMock).toHaveBeenCalledWith(); - expect(addNotificationSpy).toHaveBeenCalledWith('Failed to play the recording', 'error'); + expect(addNotificationSpy).toHaveBeenCalledWith( + 'Failed to play the recording', + 'error', + ); }); jest.useRealTimers(); @@ -185,8 +190,12 @@ describe('Audio', () => { og: AUDIO, }); const audio = container.querySelector('audio'); - const audioPlayMock = jest.spyOn(audio, 'play').mockRejectedValueOnce(new Error(errorText)); - const audioCanPlayTypeMock = jest.spyOn(audio, 'canPlayType').mockReturnValue('maybe'); + const audioPlayMock = jest + .spyOn(audio, 'play') + .mockRejectedValueOnce(new Error(errorText)); + const audioCanPlayTypeMock = jest + .spyOn(audio, 'canPlayType') + .mockReturnValue('maybe'); expect(await playButton()).toBeInTheDocument(); expect(await pauseButton()).not.toBeInTheDocument(); @@ -230,8 +239,12 @@ describe('Audio', () => { it('should show the correct progress', async () => { const { container } = renderComponent({ og: AUDIO }); - jest.spyOn(HTMLAudioElement.prototype, 'duration', 'get').mockImplementationOnce(() => 100); - jest.spyOn(HTMLAudioElement.prototype, 'currentTime', 'get').mockImplementationOnce(() => 50); + jest + .spyOn(HTMLAudioElement.prototype, 'duration', 'get') + .mockImplementationOnce(() => 100); + jest + .spyOn(HTMLAudioElement.prototype, 'currentTime', 'get') + .mockImplementationOnce(() => 50); const audioElement = container.querySelector('audio'); fireEvent.timeUpdate(audioElement); diff --git a/src/components/Attachment/__tests__/Card.test.js b/src/components/Attachment/__tests__/Card.test.js index 826f0a87d..5582dd599 100644 --- a/src/components/Attachment/__tests__/Card.test.js +++ b/src/components/Attachment/__tests__/Card.test.js @@ -142,7 +142,8 @@ describe('Card', () => { thumb_url: undefined, }, props: 'asset and neither og image URL is available', - render: 'content part with title and text only and without the header part of the Card', + render: + 'content part with title and text only and without the header part of the Card', }, ); } else if (type === 'video') { diff --git a/src/components/Attachment/__tests__/VoiceRecording.test.js b/src/components/Attachment/__tests__/VoiceRecording.test.js index ac7704642..fb145257f 100644 --- a/src/components/Attachment/__tests__/VoiceRecording.test.js +++ b/src/components/Attachment/__tests__/VoiceRecording.test.js @@ -16,7 +16,9 @@ const attachment = generateVoiceRecordingAttachment(); window.ResizeObserver = ResizeObserverMock; -jest.spyOn(HTMLDivElement.prototype, 'getBoundingClientRect').mockReturnValue({ width: 120 }); +jest + .spyOn(HTMLDivElement.prototype, 'getBoundingClientRect') + .mockReturnValue({ width: 120 }); const clickPlay = async () => { await act(async () => { @@ -57,7 +59,9 @@ describe('VoiceRecordingPlayer', () => { afterAll(jest.restoreAllMocks); it('should not render the component if asset_url is missing', () => { - const { container } = renderComponent({ attachment: { ...attachment, asset_url: undefined } }); + const { container } = renderComponent({ + attachment: { ...attachment, asset_url: undefined }, + }); expect(container).toBeEmptyDOMElement(); }); it('should render title if present', () => { @@ -65,7 +69,9 @@ describe('VoiceRecordingPlayer', () => { expect(getByTestId('voice-recording-title')).toHaveTextContent(attachment.title); }); it('should render fallback title if attachment title not present', () => { - const { getByTestId } = renderComponent({ attachment: { ...attachment, title: undefined } }); + const { getByTestId } = renderComponent({ + attachment: { ...attachment, title: undefined }, + }); expect(getByTestId('voice-recording-title')).toHaveTextContent(FALLBACK_TITLE); }); @@ -129,8 +135,12 @@ describe('VoiceRecordingPlayer', () => { it('should show the correct progress', async () => { const { container } = renderComponent({ attachment }); - jest.spyOn(HTMLAudioElement.prototype, 'duration', 'get').mockImplementationOnce(() => 100); - jest.spyOn(HTMLAudioElement.prototype, 'currentTime', 'get').mockImplementationOnce(() => 50); + jest + .spyOn(HTMLAudioElement.prototype, 'duration', 'get') + .mockImplementationOnce(() => 100); + jest + .spyOn(HTMLAudioElement.prototype, 'currentTime', 'get') + .mockImplementationOnce(() => 50); const audioElement = container.querySelector('audio'); fireEvent.timeUpdate(audioElement); diff --git a/src/components/Attachment/__tests__/WaveProgressBar.test.js b/src/components/Attachment/__tests__/WaveProgressBar.test.js index 91be6ab09..85dcc78af 100644 --- a/src/components/Attachment/__tests__/WaveProgressBar.test.js +++ b/src/components/Attachment/__tests__/WaveProgressBar.test.js @@ -28,21 +28,28 @@ describe('WaveProgressBar', () => { it('is not rendered if no space available', () => { getBoundingClientRect.mockReturnValueOnce({ width: 0 }); - render(); + render( + , + ); expect(screen.queryByTestId(BAR_ROOT_TEST_ID)).not.toBeInTheDocument(); }); it('renders with default number of bars', () => { render(); const root = screen.getByTestId(BAR_ROOT_TEST_ID); - expect(root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width')).toBe( - '1px', - ); + expect( + root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width'), + ).toBe('1px'); const bars = screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID); expect( bars.every( (b) => - b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === '2px', + b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === + '2px', ), ).toBeTruthy(); expect(screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID)).toHaveLength(40); @@ -58,14 +65,15 @@ describe('WaveProgressBar', () => { />, ); const root = screen.getByTestId(BAR_ROOT_TEST_ID); - expect(root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width')).toBe( - '5px', - ); + expect( + root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width'), + ).toBe('5px'); const bars = screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID); expect( bars.every( (b) => - b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === '3px', + b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === + '3px', ), ).toBeTruthy(); expect(bars).toHaveLength(15); @@ -80,14 +88,15 @@ describe('WaveProgressBar', () => { activeObserver.cb([{ contentRect: { width: 21 } }]); }); const root = screen.getByTestId(BAR_ROOT_TEST_ID); - expect(root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width')).toBe( - '1px', - ); + expect( + root.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-gap-width'), + ).toBe('1px'); const bars = screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID); expect( bars.every( (b) => - b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === '2px', + b.style.getPropertyValue('--str-chat__voice-recording-amplitude-bar-width') === + '2px', ), ).toBeTruthy(); expect(screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID)).toHaveLength(7); @@ -102,7 +111,11 @@ describe('WaveProgressBar', () => { it('is rendered with zero progress by default if waveform data is available', () => { const { container } = render( - , + , ); expect(container).toMatchSnapshot(); expect(screen.getByTestId(PROGRESS_INDICATOR_TEST_ID)).toBeInTheDocument(); diff --git a/src/components/Attachment/__tests__/audioSampling.test.js b/src/components/Attachment/__tests__/audioSampling.test.js index f018ad5a0..8d677f689 100644 --- a/src/components/Attachment/__tests__/audioSampling.test.js +++ b/src/components/Attachment/__tests__/audioSampling.test.js @@ -15,7 +15,9 @@ describe('amplitude sampling', () => { }); it('should return original values if the original sample size equals the target', () => { - expect(upSample(originalSample, originalSample.length)).toHaveLength(originalSample.length); + expect(upSample(originalSample, originalSample.length)).toHaveLength( + originalSample.length, + ); }); it('should fill each bucket to reach the target sample size', () => { @@ -35,7 +37,9 @@ describe('amplitude sampling', () => { }); it('should return original values if the original sample size equals the target', () => { - expect(downSample(originalSample, originalSample.length)).toHaveLength(originalSample.length); + expect(downSample(originalSample, originalSample.length)).toHaveLength( + originalSample.length, + ); }); it('should return a mean of original values if the target output size is 1', () => { diff --git a/src/components/Attachment/attachment-sizing.tsx b/src/components/Attachment/attachment-sizing.tsx index 440961d6f..2806d67e8 100644 --- a/src/components/Attachment/attachment-sizing.tsx +++ b/src/components/Attachment/attachment-sizing.tsx @@ -1,7 +1,10 @@ import type { Attachment } from 'stream-chat'; import * as linkify from 'linkifyjs'; -export const getImageAttachmentConfiguration = (attachment: Attachment, element: HTMLElement) => { +export const getImageAttachmentConfiguration = ( + attachment: Attachment, + element: HTMLElement, +) => { let newUrl = undefined; const urlToTest = attachment.image_url || attachment.thumb_url || ''; @@ -60,7 +63,10 @@ const getSizingRestrictions = (url: URL, htmlElement: HTMLElement) => { const cssSizeRestriction = getCSSSizeRestrictions(htmlElement); let resizeDimensions: { height: number; width: number } | undefined; - if ((cssSizeRestriction.maxHeight || cssSizeRestriction.height) && cssSizeRestriction.maxWidth) { + if ( + (cssSizeRestriction.maxHeight || cssSizeRestriction.height) && + cssSizeRestriction.maxWidth + ) { resizeDimensions = getResizeDimensions( originalHeight, originalWidth, @@ -87,7 +93,9 @@ const getResizeDimensions = ( const getCSSSizeRestrictions = (htmlElement: HTMLElement) => { const computedStylesheet = getComputedStyle(htmlElement); - const height = getValueRepresentationOfCSSProperty(computedStylesheet.getPropertyValue('height')); + const height = getValueRepresentationOfCSSProperty( + computedStylesheet.getPropertyValue('height'), + ); const maxHeight = getValueRepresentationOfCSSProperty( computedStylesheet.getPropertyValue('max-height'), ); @@ -112,7 +120,10 @@ const getValueRepresentationOfCSSProperty = (property: string) => { return isNaN(number) ? undefined : number; }; -const addResizingParamsToUrl = (resizeDimensions: { height: number; width: number }, url: URL) => { +const addResizingParamsToUrl = ( + resizeDimensions: { height: number; width: number }, + url: URL, +) => { url.searchParams.set('h', resizeDimensions.height.toString()); url.searchParams.set('w', resizeDimensions.width.toString()); }; diff --git a/src/components/Attachment/audioSampling.ts b/src/components/Attachment/audioSampling.ts index ff8b7638d..d9ece97cf 100644 --- a/src/components/Attachment/audioSampling.ts +++ b/src/components/Attachment/audioSampling.ts @@ -4,8 +4,8 @@ export const resampleWaveformData = (waveformData: number[], amplitudesCount: nu waveformData.length === amplitudesCount ? waveformData : waveformData.length > amplitudesCount - ? downSample(waveformData, amplitudesCount) - : upSample(waveformData, amplitudesCount); + ? downSample(waveformData, amplitudesCount) + : upSample(waveformData, amplitudesCount); /** * The downSample function uses the Largest-Triangle-Three-Buckets (LTTB) algorithm. @@ -42,14 +42,21 @@ export function downSample(data: number[], targetOutputSize: number): number[] { currentPointIndex < nextBucketStartIndex; currentPointIndex++ ) { - const countUnitsBetweenAtoB = Math.abs(currentPointIndex - currentBucketStartIndex) + 1; + const countUnitsBetweenAtoB = + Math.abs(currentPointIndex - currentBucketStartIndex) + 1; const countUnitsBetweenBtoC = countUnitsBetweenAtoC - countUnitsBetweenAtoB; const currentPointValue = data[currentPointIndex]; triangleArea = triangleAreaHeron( - triangleBase(Math.abs(previousBucketRefPoint - currentPointValue), countUnitsBetweenAtoB), + triangleBase( + Math.abs(previousBucketRefPoint - currentPointValue), + countUnitsBetweenAtoB, + ), triangleBase(Math.abs(currentPointValue - nextBucketMean), countUnitsBetweenBtoC), - triangleBase(Math.abs(previousBucketRefPoint - nextBucketMean), countUnitsBetweenAtoC), + triangleBase( + Math.abs(previousBucketRefPoint - nextBucketMean), + countUnitsBetweenAtoC, + ), ); if (triangleArea > maxArea) { @@ -72,8 +79,13 @@ const triangleAreaHeron = (a: number, b: number, c: number) => { return Math.sqrt(s * (s - a) * (s - b) * (s - c)); }; const triangleBase = (a: number, b: number) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); -const mean = (values: number[]) => values.reduce((acc, value) => acc + value, 0) / values.length; -const getNextBucketMean = (data: number[], currentBucketIndex: number, bucketSize: number) => { +const mean = (values: number[]) => + values.reduce((acc, value) => acc + value, 0) / values.length; +const getNextBucketMean = ( + data: number[], + currentBucketIndex: number, + bucketSize: number, +) => { const nextBucketStartIndex = Math.floor(currentBucketIndex * bucketSize) + 1; let nextNextBucketStartIndex = Math.floor((currentBucketIndex + 1) * bucketSize) + 1; nextNextBucketStartIndex = @@ -88,7 +100,9 @@ export const upSample = (values: number[], targetSize: number) => { } if (values.length > targetSize) { - console.warn('Requested to extend the waveformData that is longer than the target list size'); + console.warn( + 'Requested to extend the waveformData that is longer than the target list size', + ); return values; } diff --git a/src/components/Attachment/components/FileSizeIndicator.tsx b/src/components/Attachment/components/FileSizeIndicator.tsx index a9f26e55e..5fdc0da42 100644 --- a/src/components/Attachment/components/FileSizeIndicator.tsx +++ b/src/components/Attachment/components/FileSizeIndicator.tsx @@ -11,7 +11,10 @@ type FileSizeIndicatorProps = { maximumFractionDigits?: number; }; -export const FileSizeIndicator = ({ fileSize, maximumFractionDigits }: FileSizeIndicatorProps) => { +export const FileSizeIndicator = ({ + fileSize, + maximumFractionDigits, +}: FileSizeIndicatorProps) => { if (!(fileSize && Number.isFinite(Number(fileSize)))) return null; return ( diff --git a/src/components/Attachment/components/ProgressBar.tsx b/src/components/Attachment/components/ProgressBar.tsx index 1e5d5fd10..20a17b3d1 100644 --- a/src/components/Attachment/components/ProgressBar.tsx +++ b/src/components/Attachment/components/ProgressBar.tsx @@ -8,7 +8,10 @@ export type ProgressBarProps = { export const ProgressBar = ({ className, onClick, progress }: ProgressBarProps) => (
; export type GalleryAttachment< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { images: Attachment[]; type: 'gallery'; }; export type RenderAttachmentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Omit, 'attachments'> & { attachment: Attachment; }; export type RenderGalleryProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Omit, 'attachments'> & { attachment: GalleryAttachment; }; export const isLocalAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: UnknownType, ): attachment is LocalAttachment => !!(attachment.localMetadata as LocalAttachment)?.id; export const isScrapedContent = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment, ) => attachment.og_scrape_url || attachment.title_link; export const isUploadedImage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment, ) => attachment.type === 'image' && !isScrapedContent(attachment); export const isLocalImageAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is LocalImageAttachment => isUploadedImage(attachment) && isLocalAttachment(attachment); export const isGalleryAttachmentType = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( output: Attachment | GalleryAttachment, ): output is GalleryAttachment => Array.isArray(output.images); export const isAudioAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ) => attachment.type === 'audio'; export const isLocalAudioAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is LocalAudioAttachment => isAudioAttachment(attachment) && isLocalAttachment(attachment); export const isVoiceRecordingAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is VoiceRecordingAttachment => attachment.type === 'voiceRecording'; export const isLocalVoiceRecordingAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is LocalVoiceRecordingAttachment => isVoiceRecordingAttachment(attachment) && isLocalAttachment(attachment); export const isFileAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ) => @@ -109,22 +114,23 @@ export const isFileAttachment = < ); export const isLocalFileAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is LocalFileAttachment => isFileAttachment(attachment) && isLocalAttachment(attachment); export const isMediaAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ) => - (attachment.mime_type && SUPPORTED_VIDEO_FORMATS.indexOf(attachment.mime_type) !== -1) || + (attachment.mime_type && + SUPPORTED_VIDEO_FORMATS.indexOf(attachment.mime_type) !== -1) || attachment.type === 'video'; export const isLocalMediaAttachment = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( attachment: Attachment | LocalAttachment, ): attachment is LocalVideoAttachment => @@ -135,7 +141,10 @@ export const isSvgAttachment = (attachment: Attachment) => { return filename.toLowerCase().endsWith('.svg'); }; -export const divMod = (num: number, divisor: number) => [Math.floor(num / divisor), num % divisor]; +export const divMod = (num: number, divisor: number) => [ + Math.floor(num / divisor), + num % divisor, +]; export const displayDuration = (totalSeconds?: number) => { if (!totalSeconds || totalSeconds < 0) return '00:00'; diff --git a/src/components/AutoCompleteTextarea/Item.jsx b/src/components/AutoCompleteTextarea/Item.jsx index 19cf35b04..4945d682d 100644 --- a/src/components/AutoCompleteTextarea/Item.jsx +++ b/src/components/AutoCompleteTextarea/Item.jsx @@ -13,7 +13,10 @@ export const Item = React.forwardRef(function Item(props, innerRef) { } = props; const handleSelect = useCallback(() => onSelectHandler(item), [item, onSelectHandler]); - const handleClick = useCallback((event) => onClickHandler(event, item), [item, onClickHandler]); + const handleClick = useCallback( + (event) => onClickHandler(event, item), + [item, onClickHandler], + ); return (
  • - values.findIndex((value) => (value.id ? value.id === item.id : value.name === item.name)), + values.findIndex((value) => + value.id ? value.id === item.id : value.name === item.name, + ), [values], ); @@ -98,7 +100,10 @@ export const List = ({ }); } - if ((event.key === 'Enter' || event.key === 'Tab') && selectedItemIndex !== undefined) { + if ( + (event.key === 'Enter' || event.key === 'Tab') && + selectedItemIndex !== undefined + ) { handleClick(event, values[selectedItemIndex]); } @@ -134,7 +139,10 @@ export const List = ({ [propValue, selectionEnd, currentTrigger], ); - const restructuredValues = useMemo(() => values.map(restructureItem), [values, restructureItem]); + const restructuredValues = useMemo( + () => values.map(restructureItem), + [values, restructureItem], + ); return (
      diff --git a/src/components/AutoCompleteTextarea/Textarea.jsx b/src/components/AutoCompleteTextarea/Textarea.jsx index 0dfd5a2b8..65356bc32 100644 --- a/src/components/AutoCompleteTextarea/Textarea.jsx +++ b/src/components/AutoCompleteTextarea/Textarea.jsx @@ -179,7 +179,11 @@ export class ReactTextareaAutocomplete extends React.Component { showCommandsList, showMentionsList, } = this.props; - const { currentTrigger: stateTrigger, selectionEnd, value: textareaValue } = this.state; + const { + currentTrigger: stateTrigger, + selectionEnd, + value: textareaValue, + } = this.state; const currentTrigger = showCommandsList ? '/' : showMentionsList ? '@' : stateTrigger; @@ -194,7 +198,9 @@ export class ReactTextareaAutocomplete extends React.Component { return startToken + token.length; default: if (!Number.isInteger(position)) { - throw new Error('RTA: caretPosition should be "start", "next", "end" or number.'); + throw new Error( + 'RTA: caretPosition should be "start", "next", "end" or number.', + ); } return position; @@ -204,13 +210,14 @@ export class ReactTextareaAutocomplete extends React.Component { const textToModify = showCommandsList ? '/' : showMentionsList - ? '@' - : textareaValue.slice(0, selectionEnd); + ? '@' + : textareaValue.slice(0, selectionEnd); const startOfTokenPosition = textToModify.lastIndexOf(currentTrigger); // we add space after emoji is selected if a caret position is next - const newTokenString = newToken.caretPosition === 'next' ? `${newToken.text} ` : newToken.text; + const newTokenString = + newToken.caretPosition === 'next' ? `${newToken.text} ` : newToken.text; const newCaretPosition = computeCaretPosition( newToken.caretPosition, @@ -450,7 +457,6 @@ export class ReactTextareaAutocomplete extends React.Component { 'value', ]; - // eslint-disable-next-line for (const prop in props) { if (notSafe.includes(prop)) delete props[prop]; } @@ -467,7 +473,8 @@ export class ReactTextareaAutocomplete extends React.Component { }; _changeHandler = (e) => { - const { minChar, movePopupAsYouType, onCaretPositionChange, onChange, trigger } = this.props; + const { minChar, movePopupAsYouType, onCaretPositionChange, onChange, trigger } = + this.props; const { left, top } = this.state; const textarea = e.target; @@ -499,7 +506,8 @@ export class ReactTextareaAutocomplete extends React.Component { lastToken = tokenMatch && tokenMatch[tokenMatch.length - 1].trim(); - currentTrigger = (lastToken && Object.keys(trigger).find((a) => a === lastToken[0])) || null; + currentTrigger = + (lastToken && Object.keys(trigger).find((a) => a === lastToken[0])) || null; } /* @@ -636,7 +644,9 @@ export class ReactTextareaAutocomplete extends React.Component { triggerProps.component = showCommandsList ? CommandItem : UserItem; triggerProps.currentTrigger = showCommandsList ? '/' : '@'; - triggerProps.getTextToReplace = this._getTextToReplace(showCommandsList ? '/' : '@'); + triggerProps.getTextToReplace = this._getTextToReplace( + showCommandsList ? '/' : '@', + ); triggerProps.getSelectedItem = this._getItemOnSelect(showCommandsList ? '/' : '@'); triggerProps.selectionEnd = 1; triggerProps.value = showCommandsList ? '/' : '@'; diff --git a/src/components/AutoCompleteTextarea/utils.js b/src/components/AutoCompleteTextarea/utils.js index 97e323b8d..54a32d801 100644 --- a/src/components/AutoCompleteTextarea/utils.js +++ b/src/components/AutoCompleteTextarea/utils.js @@ -11,11 +11,13 @@ export function defaultScrollToItem(container, item) { const actualScrollTop = container.scrollTop; const itemOffsetTop = item.offsetTop; - if (itemOffsetTop < actualScrollTop + containerHight && actualScrollTop < itemOffsetTop) { + if ( + itemOffsetTop < actualScrollTop + containerHight && + actualScrollTop < itemOffsetTop + ) { return; } - // eslint-disable-next-line container.scrollTop = itemOffsetTop; } @@ -34,7 +36,9 @@ export const triggerPropsCheck = ({ trigger }) => { const [triggerChar, settings] = triggers[i]; if (typeof triggerChar !== 'string' || triggerChar.length !== 1) { - return Error('Invalid prop trigger. Keys of the object has to be string / one character.'); + return Error( + 'Invalid prop trigger. Keys of the object has to be string / one character.', + ); } // $FlowFixMe diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index 4c8ab2005..e64fb3efc 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -9,7 +9,7 @@ import { getWholeChar } from '../../utils'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type AvatarProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** Custom root element class that will be merged with the default class */ className?: string; @@ -29,7 +29,7 @@ export type AvatarProps< * A round avatar image with fallback to username's first letter */ export const Avatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: AvatarProps, ) => { @@ -75,7 +75,10 @@ export const Avatar = < ) : ( <> {!!initials.length && ( -
      +
      {initials}
      )} diff --git a/src/components/Avatar/ChannelAvatar.tsx b/src/components/Avatar/ChannelAvatar.tsx index eab6b088f..25e64fb1c 100644 --- a/src/components/Avatar/ChannelAvatar.tsx +++ b/src/components/Avatar/ChannelAvatar.tsx @@ -3,11 +3,11 @@ import { Avatar, AvatarProps, GroupAvatar, GroupAvatarProps } from './index'; import type { DefaultStreamChatGenerics } from '../../types'; export type ChannelAvatarProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Partial & AvatarProps; export const ChannelAvatar = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ groupChannelDisplayInfo, image, @@ -16,7 +16,9 @@ export const ChannelAvatar = < ...sharedProps }: ChannelAvatarProps) => { if (groupChannelDisplayInfo) { - return ; + return ( + + ); } return ; }; diff --git a/src/components/Avatar/GroupAvatar.tsx b/src/components/Avatar/GroupAvatar.tsx index 89a127d88..a188f6b9e 100644 --- a/src/components/Avatar/GroupAvatar.tsx +++ b/src/components/Avatar/GroupAvatar.tsx @@ -3,7 +3,10 @@ import React from 'react'; import { Avatar, AvatarProps } from './Avatar'; import { GroupChannelDisplayInfo } from '../ChannelPreview'; -export type GroupAvatarProps = Pick & { +export type GroupAvatarProps = Pick< + AvatarProps, + 'className' | 'onClick' | 'onMouseOver' +> & { /** Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load. */ groupChannelDisplayInfo: GroupChannelDisplayInfo; }; diff --git a/src/components/Avatar/__tests__/Avatar.test.js b/src/components/Avatar/__tests__/Avatar.test.js index 4e0805a4b..1c82df14c 100644 --- a/src/components/Avatar/__tests__/Avatar.test.js +++ b/src/components/Avatar/__tests__/Avatar.test.js @@ -9,7 +9,7 @@ const AVATAR_ROOT_TEST_ID = 'avatar'; const AVATAR_FALLBACK_TEST_ID = 'avatar-fallback'; const AVATAR_IMG_TEST_ID = 'avatar-img'; -afterEach(cleanup); // eslint-disable-line +afterEach(cleanup); describe('Avatar', () => { it('should render component with default props', () => { @@ -61,7 +61,9 @@ describe('Avatar', () => { it('should render initials as alt and title', () => { const name = 'Cherry Blossom'; - const { getByAltText, getByTitle } = render(); + const { getByAltText, getByTitle } = render( + , + ); expect(getByTitle(name)).toBeInTheDocument(); expect(getByAltText(name[0])).toBeInTheDocument(); @@ -94,7 +96,9 @@ describe('Avatar', () => { }); it('should render fallback initials on img error', () => { - const { getByTestId, queryByTestId } = render(); + const { getByTestId, queryByTestId } = render( + , + ); const img = getByTestId(AVATAR_IMG_TEST_ID); expect(img).toBeInTheDocument(); @@ -105,7 +109,9 @@ describe('Avatar', () => { }); it('should render new img on props change for errored img', () => { - const { getByTestId, queryByTestId, rerender } = render(); + const { getByTestId, queryByTestId, rerender } = render( + , + ); fireEvent.error(getByTestId(AVATAR_IMG_TEST_ID)); expect(queryByTestId(AVATAR_IMG_TEST_ID)).not.toBeInTheDocument(); diff --git a/src/components/Channel/Channel.tsx b/src/components/Channel/Channel.tsx index c7af064cb..0ef286dec 100644 --- a/src/components/Channel/Channel.tsx +++ b/src/components/Channel/Channel.tsx @@ -100,7 +100,7 @@ import { useThreadContext } from '../Threads'; import { CHANNEL_CONTAINER_ID } from './constants'; type ChannelPropsForwardedToComponentContext< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick< ComponentContextValue, | 'Attachment' @@ -166,7 +166,7 @@ type ChannelPropsForwardedToComponentContext< >; const isUserResponseArray = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( output: string[] | UserResponse[], ): output is UserResponse[] => @@ -174,7 +174,7 @@ const isUserResponseArray = < export type ChannelProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - V extends CustomTrigger = CustomTrigger + V extends CustomTrigger = CustomTrigger, > = ChannelPropsForwardedToComponentContext & { /** List of accepted file types */ acceptedFiles?: string[]; @@ -255,7 +255,7 @@ export type ChannelProps< }; const ChannelContainer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ children, className: additionalClassName, @@ -275,7 +275,7 @@ const ChannelContainer = < const UnMemoizedChannel = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - V extends CustomTrigger = CustomTrigger + V extends CustomTrigger = CustomTrigger, >( props: PropsWithChildren>, ) => { @@ -286,9 +286,8 @@ const UnMemoizedChannel = < LoadingIndicator = DefaultLoadingIndicator, } = props; - const { channel: contextChannel, channelsQueryState } = useChatContext( - 'Channel', - ); + const { channel: contextChannel, channelsQueryState } = + useChatContext('Channel'); const channel = propsChannel || contextChannel; @@ -317,7 +316,7 @@ const UnMemoizedChannel = < const ChannelInner = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - V extends CustomTrigger = CustomTrigger + V extends CustomTrigger = CustomTrigger, >( props: PropsWithChildren< ChannelProps & { @@ -360,12 +359,8 @@ const ChannelInner = < [propChannelQueryOptions], ); - const { - client, - customClasses, - latestMessageDatesByChannels, - mutes, - } = useChatContext('Channel'); + const { client, customClasses, latestMessageDatesByChannels, mutes } = + useChatContext('Channel'); const { t } = useTranslationContext('Channel'); const chatContainerClass = getChatContainerClass(customClasses?.chatContainer); const windowsEmojiClass = useImageFlagEmojisOnWindowsClass(); @@ -374,7 +369,8 @@ const ChannelInner = < const [channelConfig, setChannelConfig] = useState(channel.getConfig()); const [notifications, setNotifications] = useState([]); const [quotedMessage, setQuotedMessage] = useState>(); - const [channelUnreadUiState, _setChannelUnreadUiState] = useState(); + const [channelUnreadUiState, _setChannelUnreadUiState] = + useState(); const notificationTimeouts = useRef>([]); @@ -457,7 +453,14 @@ const ChannelInner = < 500, { leading: true, trailing: false }, ), - [activeUnreadHandler, channel, channelConfig, doMarkReadRequest, setChannelUnreadUiState, t], + [ + activeUnreadHandler, + channel, + channelConfig, + doMarkReadRequest, + setChannelUnreadUiState, + t, + ], ); const handleEvent = async (event: Event) => { @@ -469,7 +472,8 @@ const ChannelInner = < }); } - if (event.type === 'user.watching.start' || event.type === 'user.watching.stop') return; + if (event.type === 'user.watching.start' || event.type === 'user.watching.stop') + return; if (event.type === 'typing.start' || event.type === 'typing.stop') { return dispatch({ channel, type: 'setTyping' }); @@ -480,10 +484,15 @@ const ChannelInner = < } if (event.type === 'message.new') { - const mainChannelUpdated = !event.message?.parent_id || event.message?.show_in_channel; + const mainChannelUpdated = + !event.message?.parent_id || event.message?.show_in_channel; if (mainChannelUpdated) { - if (document.hidden && channelConfig?.read_events && !channel.muteStatus().muted) { + if ( + document.hidden && + channelConfig?.read_events && + !channel.muteStatus().muted + ) { const unread = channel.countUnread(lastRead.current); if (activeUnreadHandler) { @@ -560,7 +569,8 @@ const ChannelInner = < if (typeof member === 'string') { userId = member; } else if (typeof member === 'object') { - const { user, user_id } = member as ChannelMemberResponse; + const { user, user_id } = + member as ChannelMemberResponse; userId = user_id || user?.id; } if (userId) { @@ -658,13 +668,21 @@ const ChannelInner = < ); const loadMore = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => { - if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasPrev) + if ( + !online.current || + !window.navigator.onLine || + !channel.state.messagePagination.hasPrev + ) return 0; // prevent duplicate loading events... const oldestMessage = state?.messages?.[0]; - if (state.loadingMore || state.loadingMoreNewer || oldestMessage?.status !== 'received') { + if ( + state.loadingMore || + state.loadingMoreNewer || + oldestMessage?.status !== 'received' + ) { return 0; } @@ -691,7 +709,11 @@ const ChannelInner = < }; const loadMoreNewer = async (limit = DEFAULT_NEXT_CHANNEL_PAGE_SIZE) => { - if (!online.current || !window.navigator.onLine || !channel.state.messagePagination.hasNext) + if ( + !online.current || + !window.navigator.onLine || + !channel.state.messagePagination.hasNext + ) return 0; const newestMessage = state?.messages?.[state?.messages?.length - 1]; @@ -722,170 +744,195 @@ const ChannelInner = < return queryResponse.messages.length; }; - const clearHighlightedMessageTimeoutId = useRef | null>(null); + const clearHighlightedMessageTimeoutId = useRef | null>( + null, + ); - const jumpToMessage: ChannelActionContextValue['jumpToMessage'] = useCallback( - async ( - messageId, - messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, - highlightDuration = DEFAULT_HIGHLIGHT_DURATION, - ) => { - dispatch({ loadingMore: true, type: 'setLoadingMore' }); - await channel.state.loadMessageIntoState(messageId, undefined, messageLimit); + const jumpToMessage: ChannelActionContextValue['jumpToMessage'] = + useCallback( + async ( + messageId, + messageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, + highlightDuration = DEFAULT_HIGHLIGHT_DURATION, + ) => { + dispatch({ loadingMore: true, type: 'setLoadingMore' }); + await channel.state.loadMessageIntoState(messageId, undefined, messageLimit); + + loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); + dispatch({ + hasMoreNewer: channel.state.messagePagination.hasNext, + highlightedMessageId: messageId, + type: 'jumpToMessageFinished', + }); + + if (clearHighlightedMessageTimeoutId.current) { + clearTimeout(clearHighlightedMessageTimeoutId.current); + } + clearHighlightedMessageTimeoutId.current = setTimeout(() => { + clearHighlightedMessageTimeoutId.current = null; + dispatch({ type: 'clearHighlightedMessage' }); + }, highlightDuration); + }, + [channel, loadMoreFinished], + ); + + const jumpToLatestMessage: ChannelActionContextValue['jumpToLatestMessage'] = + useCallback(async () => { + await channel.state.loadMessageIntoState('latest'); loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); dispatch({ - hasMoreNewer: channel.state.messagePagination.hasNext, - highlightedMessageId: messageId, - type: 'jumpToMessageFinished', + type: 'jumpToLatestMessage', }); + }, [channel, loadMoreFinished]); - if (clearHighlightedMessageTimeoutId.current) { - clearTimeout(clearHighlightedMessageTimeoutId.current); - } + const jumpToFirstUnreadMessage: ChannelActionContextValue['jumpToFirstUnreadMessage'] = + useCallback( + async ( + queryMessageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, + highlightDuration = DEFAULT_HIGHLIGHT_DURATION, + ) => { + if (!channelUnreadUiState?.unread_messages) return; + let lastReadMessageId = channelUnreadUiState?.last_read_message_id; + let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id; + let isInCurrentMessageSet = false; + + if (firstUnreadMessageId) { + const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages); + isInCurrentMessageSet = result.index !== -1; + } else if (lastReadMessageId) { + const result = findInMsgSetById(lastReadMessageId, channel.state.messages); + isInCurrentMessageSet = !!result.target; + firstUnreadMessageId = + result.index > -1 ? channel.state.messages[result.index + 1]?.id : undefined; + } else { + const lastReadTimestamp = channelUnreadUiState.last_read.getTime(); + const { index: lastReadMessageIndex, target: lastReadMessage } = + findInMsgSetByDate( + channelUnreadUiState.last_read, + channel.state.messages, + true, + ); + + if (lastReadMessage) { + firstUnreadMessageId = channel.state.messages[lastReadMessageIndex + 1]?.id; + isInCurrentMessageSet = !!firstUnreadMessageId; + lastReadMessageId = lastReadMessage.id; + } else { + dispatch({ loadingMore: true, type: 'setLoadingMore' }); + let messages; + try { + messages = ( + await channel.query( + { + messages: { + created_at_around: channelUnreadUiState.last_read.toISOString(), + limit: queryMessageLimit, + }, + }, + 'new', + ) + ).messages; + } catch (e) { + addNotification(t('Failed to jump to the first unread message'), 'error'); + loadMoreFinished( + channel.state.messagePagination.hasPrev, + channel.state.messages, + ); + return; + } - clearHighlightedMessageTimeoutId.current = setTimeout(() => { - clearHighlightedMessageTimeoutId.current = null; - dispatch({ type: 'clearHighlightedMessage' }); - }, highlightDuration); - }, - [channel, loadMoreFinished], - ); + const firstMessageWithCreationDate = messages.find((msg) => msg.created_at); + if (!firstMessageWithCreationDate) { + addNotification(t('Failed to jump to the first unread message'), 'error'); + loadMoreFinished( + channel.state.messagePagination.hasPrev, + channel.state.messages, + ); + return; + } + const firstMessageTimestamp = new Date( + firstMessageWithCreationDate.created_at as string, + ).getTime(); + if (lastReadTimestamp < firstMessageTimestamp) { + // whole channel is unread + firstUnreadMessageId = firstMessageWithCreationDate.id; + } else { + const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages); + lastReadMessageId = result.target?.id; + } + loadMoreFinished( + channel.state.messagePagination.hasPrev, + channel.state.messages, + ); + } + } - const jumpToLatestMessage: ChannelActionContextValue['jumpToLatestMessage'] = useCallback(async () => { - await channel.state.loadMessageIntoState('latest'); - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); - dispatch({ - type: 'jumpToLatestMessage', - }); - }, [channel, loadMoreFinished]); + if (!firstUnreadMessageId && !lastReadMessageId) { + addNotification(t('Failed to jump to the first unread message'), 'error'); + return; + } - const jumpToFirstUnreadMessage: ChannelActionContextValue['jumpToFirstUnreadMessage'] = useCallback( - async ( - queryMessageLimit = DEFAULT_JUMP_TO_PAGE_SIZE, - highlightDuration = DEFAULT_HIGHLIGHT_DURATION, - ) => { - if (!channelUnreadUiState?.unread_messages) return; - let lastReadMessageId = channelUnreadUiState?.last_read_message_id; - let firstUnreadMessageId = channelUnreadUiState?.first_unread_message_id; - let isInCurrentMessageSet = false; - - if (firstUnreadMessageId) { - const result = findInMsgSetById(firstUnreadMessageId, channel.state.messages); - isInCurrentMessageSet = result.index !== -1; - } else if (lastReadMessageId) { - const result = findInMsgSetById(lastReadMessageId, channel.state.messages); - isInCurrentMessageSet = !!result.target; - firstUnreadMessageId = - result.index > -1 ? channel.state.messages[result.index + 1]?.id : undefined; - } else { - const lastReadTimestamp = channelUnreadUiState.last_read.getTime(); - const { index: lastReadMessageIndex, target: lastReadMessage } = findInMsgSetByDate( - channelUnreadUiState.last_read, - channel.state.messages, - true, - ); - - if (lastReadMessage) { - firstUnreadMessageId = channel.state.messages[lastReadMessageIndex + 1]?.id; - isInCurrentMessageSet = !!firstUnreadMessageId; - lastReadMessageId = lastReadMessage.id; - } else { + if (!isInCurrentMessageSet) { dispatch({ loadingMore: true, type: 'setLoadingMore' }); - let messages; try { - messages = ( - await channel.query( - { - messages: { - created_at_around: channelUnreadUiState.last_read.toISOString(), - limit: queryMessageLimit, - }, - }, - 'new', - ) - ).messages; + const targetId = (firstUnreadMessageId ?? lastReadMessageId) as string; + await channel.state.loadMessageIntoState( + targetId, + undefined, + queryMessageLimit, + ); + /** + * if the index of the last read message on the page is beyond the half of the page, + * we have arrived to the oldest page of the channel + */ + const indexOfTarget = channel.state.messages.findIndex( + (message) => message.id === targetId, + ) as number; + loadMoreFinished( + channel.state.messagePagination.hasPrev, + channel.state.messages, + ); + firstUnreadMessageId = + firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id; } catch (e) { addNotification(t('Failed to jump to the first unread message'), 'error'); - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); - return; - } - - const firstMessageWithCreationDate = messages.find((msg) => msg.created_at); - if (!firstMessageWithCreationDate) { - addNotification(t('Failed to jump to the first unread message'), 'error'); - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); + loadMoreFinished( + channel.state.messagePagination.hasPrev, + channel.state.messages, + ); return; } - const firstMessageTimestamp = new Date( - firstMessageWithCreationDate.created_at as string, - ).getTime(); - if (lastReadTimestamp < firstMessageTimestamp) { - // whole channel is unread - firstUnreadMessageId = firstMessageWithCreationDate.id; - } else { - const result = findInMsgSetByDate(channelUnreadUiState.last_read, messages); - lastReadMessageId = result.target?.id; - } - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); } - } - if (!firstUnreadMessageId && !lastReadMessageId) { - addNotification(t('Failed to jump to the first unread message'), 'error'); - return; - } - - if (!isInCurrentMessageSet) { - dispatch({ loadingMore: true, type: 'setLoadingMore' }); - try { - const targetId = (firstUnreadMessageId ?? lastReadMessageId) as string; - await channel.state.loadMessageIntoState(targetId, undefined, queryMessageLimit); - /** - * if the index of the last read message on the page is beyond the half of the page, - * we have arrived to the oldest page of the channel - */ - const indexOfTarget = channel.state.messages.findIndex( - (message) => message.id === targetId, - ) as number; - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); - firstUnreadMessageId = - firstUnreadMessageId ?? channel.state.messages[indexOfTarget + 1]?.id; - } catch (e) { + if (!firstUnreadMessageId) { addNotification(t('Failed to jump to the first unread message'), 'error'); - loadMoreFinished(channel.state.messagePagination.hasPrev, channel.state.messages); return; } - } + if (!channelUnreadUiState.first_unread_message_id) + _setChannelUnreadUiState({ + ...channelUnreadUiState, + first_unread_message_id: firstUnreadMessageId, + last_read_message_id: lastReadMessageId, + }); - if (!firstUnreadMessageId) { - addNotification(t('Failed to jump to the first unread message'), 'error'); - return; - } - if (!channelUnreadUiState.first_unread_message_id) - _setChannelUnreadUiState({ - ...channelUnreadUiState, - first_unread_message_id: firstUnreadMessageId, - last_read_message_id: lastReadMessageId, + dispatch({ + hasMoreNewer: channel.state.messagePagination.hasNext, + highlightedMessageId: firstUnreadMessageId, + type: 'jumpToMessageFinished', }); - dispatch({ - hasMoreNewer: channel.state.messagePagination.hasNext, - highlightedMessageId: firstUnreadMessageId, - type: 'jumpToMessageFinished', - }); - - if (clearHighlightedMessageTimeoutId.current) { - clearTimeout(clearHighlightedMessageTimeoutId.current); - } + if (clearHighlightedMessageTimeoutId.current) { + clearTimeout(clearHighlightedMessageTimeoutId.current); + } - clearHighlightedMessageTimeoutId.current = setTimeout(() => { - clearHighlightedMessageTimeoutId.current = null; - dispatch({ type: 'clearHighlightedMessage' }); - }, highlightDuration); - }, - [addNotification, channel, loadMoreFinished, t, channelUnreadUiState], - ); + clearHighlightedMessageTimeoutId.current = setTimeout(() => { + clearHighlightedMessageTimeoutId.current = null; + dispatch({ type: 'clearHighlightedMessage' }); + }, highlightDuration); + }, + [addNotification, channel, loadMoreFinished, t, channelUnreadUiState], + ); const deleteMessage = useCallback( async ( @@ -911,7 +958,10 @@ const ChannelInner = < updatedMessage: MessageToSend | StreamMessage, ) => { // add the message to the local channel state - channel.state.addMessageSorted(updatedMessage as MessageResponse, true); + channel.state.addMessageSorted( + updatedMessage as MessageResponse, + true, + ); dispatch({ channel, @@ -937,7 +987,8 @@ const ChannelInner = < id, mentioned_users: mentions, parent_id, - quoted_message_id: parent_id === quotedMessage?.parent_id ? quotedMessage?.id : undefined, + quoted_message_id: + parent_id === quotedMessage?.parent_id ? quotedMessage?.id : undefined, text, ...customMessageData, } as Message; @@ -960,7 +1011,9 @@ const ChannelInner = < } } - const responseTimestamp = new Date(messageResponse?.message?.updated_at || 0).getTime(); + const responseTimestamp = new Date( + messageResponse?.message?.updated_at || 0, + ).getTime(); const existingMessageTimestamp = existingMessage?.updated_at?.getTime() || 0; const responseIsTheNewest = responseTimestamp > existingMessageTimestamp; @@ -977,13 +1030,14 @@ const ChannelInner = < }); } - if (quotedMessage && parent_id === quotedMessage?.parent_id) setQuotedMessage(undefined); + if (quotedMessage && parent_id === quotedMessage?.parent_id) + setQuotedMessage(undefined); } catch (error) { // error response isn't usable so needs to be stringified then parsed const stringError = JSON.stringify(error); - const parsedError = (stringError - ? JSON.parse(stringError) - : {}) as ErrorFromResponse; + const parsedError = ( + stringError ? JSON.parse(stringError) : {} + ) as ErrorFromResponse; // Handle the case where the message already exists // (typically, when retrying to send a message). @@ -1067,7 +1121,9 @@ const ChannelInner = < if (message.attachments) { // remove scraped attachments added during the message composition in MessageInput to prevent sync issues - message.attachments = message.attachments.filter((attachment) => !attachment.og_scrape_url); + message.attachments = message.attachments.filter( + (attachment) => !attachment.og_scrape_url, + ); } await doSendMessage(message); @@ -1110,7 +1166,9 @@ const ChannelInner = < debounce( ( threadHasMore: boolean, - threadMessages: Array['formatMessage']>>, + threadMessages: Array< + ReturnType['formatMessage']> + >, ) => { dispatch({ threadHasMore, @@ -1144,7 +1202,10 @@ const ChannelInner = < limit, }); - const threadHasMoreMessages = hasMoreMessagesProbably(queryResponse.messages.length, limit); + const threadHasMoreMessages = hasMoreMessagesProbably( + queryResponse.messages.length, + limit, + ); const newThreadMessages = channel.state.threads[parentId] || []; // next set loadingMore to false so we can start asking for more data @@ -1172,7 +1233,8 @@ const ChannelInner = < enrichURLForPreview: props.enrichURLForPreview, findURLFn: enrichURLForPreviewConfig?.findURLFn, giphyVersion: props.giphyVersion || 'fixed_height', - imageAttachmentSizeHandler: props.imageAttachmentSizeHandler || getImageAttachmentConfiguration, + imageAttachmentSizeHandler: + props.imageAttachmentSizeHandler || getImageAttachmentConfiguration, maxNumberOfFiles, multipleUploads, mutes, @@ -1180,51 +1242,53 @@ const ChannelInner = < onLinkPreviewDismissed: enrichURLForPreviewConfig?.onLinkPreviewDismissed, quotedMessage, shouldGenerateVideoThumbnail: props.shouldGenerateVideoThumbnail || true, - videoAttachmentSizeHandler: props.videoAttachmentSizeHandler || getVideoAttachmentConfiguration, + videoAttachmentSizeHandler: + props.videoAttachmentSizeHandler || getVideoAttachmentConfiguration, watcher_count: state.watcherCount, }); - const channelActionContextValue: ChannelActionContextValue = useMemo( - () => ({ - addNotification, - closeThread, - deleteMessage, - dispatch, - editMessage, - jumpToFirstUnreadMessage, - jumpToLatestMessage, - jumpToMessage, - loadMore, - loadMoreNewer, - loadMoreThread, - markRead, - onMentionsClick: onMentionsHoverOrClick, - onMentionsHover: onMentionsHoverOrClick, - openThread, - removeMessage, - retrySendMessage, - sendMessage, - setChannelUnreadUiState, - setQuotedMessage, - skipMessageDataMemoization, - updateMessage, - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [ - channel.cid, - deleteMessage, - enrichURLForPreviewConfig?.findURLFn, - enrichURLForPreviewConfig?.onLinkPreviewDismissed, - loadMore, - loadMoreNewer, - markRead, - quotedMessage, - jumpToFirstUnreadMessage, - jumpToMessage, - jumpToLatestMessage, - setChannelUnreadUiState, - ], - ); + const channelActionContextValue: ChannelActionContextValue = + useMemo( + () => ({ + addNotification, + closeThread, + deleteMessage, + dispatch, + editMessage, + jumpToFirstUnreadMessage, + jumpToLatestMessage, + jumpToMessage, + loadMore, + loadMoreNewer, + loadMoreThread, + markRead, + onMentionsClick: onMentionsHoverOrClick, + onMentionsHover: onMentionsHoverOrClick, + openThread, + removeMessage, + retrySendMessage, + sendMessage, + setChannelUnreadUiState, + setQuotedMessage, + skipMessageDataMemoization, + updateMessage, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [ + channel.cid, + deleteMessage, + enrichURLForPreviewConfig?.findURLFn, + enrichURLForPreviewConfig?.onLinkPreviewDismissed, + loadMore, + loadMoreNewer, + markRead, + quotedMessage, + jumpToFirstUnreadMessage, + jumpToMessage, + jumpToLatestMessage, + setChannelUnreadUiState, + ], + ); // @ts-expect-error message type mismatch const componentContextValue: Partial = useMemo( @@ -1232,7 +1296,8 @@ const ChannelInner = < Attachment: props.Attachment, AttachmentPreviewList: props.AttachmentPreviewList, AttachmentSelector: props.AttachmentSelector, - AttachmentSelectorInitiationButtonContents: props.AttachmentSelectorInitiationButtonContents, + AttachmentSelectorInitiationButtonContents: + props.AttachmentSelectorInitiationButtonContents, AudioRecorder: props.AudioRecorder, AutocompleteSuggestionItem: props.AutocompleteSuggestionItem, AutocompleteSuggestionList: props.AutocompleteSuggestionList, @@ -1388,7 +1453,9 @@ const ChannelInner = <
      {dragAndDropWindow && ( - {children} + + {children} + )} {!dragAndDropWindow && <>{children}}
      diff --git a/src/components/Channel/__tests__/Channel.test.js b/src/components/Channel/__tests__/Channel.test.js index 95603ea10..f107077fc 100644 --- a/src/components/Channel/__tests__/Channel.test.js +++ b/src/components/Channel/__tests__/Channel.test.js @@ -97,7 +97,11 @@ const messages = Array.from({ length: 25 }, (_, i) => const pinnedMessages = [generateMessage({ pinned: true, user })]; const renderComponent = async (props = {}, callback = () => {}) => { - const { channel: channelFromProps, chatClient: chatClientFromProps, ...channelProps } = props; + const { + channel: channelFromProps, + chatClient: chatClientFromProps, + ...channelProps + } = props; let result; await act(() => { result = render( @@ -133,7 +137,8 @@ describe('Channel', () => { const { messages: channelMessages } = useChannelStateContext(); return channelMessages.map( - ({ id, status, text }) => status !== 'failed' &&
      {text}
      , + ({ id, status, text }) => + status !== 'failed' &&
      {text}
      , ); }; @@ -259,7 +264,9 @@ describe('Channel', () => { }, }} > -
      {loadingText}
      }>{childrenContent}
      +
      {loadingText}
      }> + {childrenContent} +
      , ); await waitFor(() => expect(screen.getByText(loadingText)).toBeInTheDocument()); @@ -315,7 +322,9 @@ describe('Channel', () => { it('should set hasMore state to false if the initial channel query returns less messages than the default initial page size', async () => { const { channel, chatClient } = await initClient(); - useMockedApis(chatClient, [queryChannelWithNewMessages([generateMessage()], channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages([generateMessage()], channel), + ]); let hasMore; await renderComponent({ channel, chatClient }, ({ hasMore: contextHasMore }) => { hasMore = contextHasMore; @@ -365,7 +374,9 @@ describe('Channel', () => { it('should set hasMore state to false if the initial channel query returns less messages than the custom query channels options message limit', async () => { const { channel, chatClient } = await initClient(); - useMockedApis(chatClient, [queryChannelWithNewMessages([generateMessage()], channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages([generateMessage()], channel), + ]); let hasMore; const channelQueryOptions = { messages: { limit: 10 }, @@ -386,7 +397,10 @@ describe('Channel', () => { const { channel, chatClient } = await initClient(); const equalCount = 10; useMockedApis(chatClient, [ - queryChannelWithNewMessages(Array.from({ length: equalCount }, generateMessage), channel), + queryChannelWithNewMessages( + Array.from({ length: equalCount }, generateMessage), + channel, + ), ]); let hasMore; const channelQueryOptions = { @@ -478,7 +492,10 @@ describe('Channel', () => { await renderComponent({ channel, chatClient }); await waitFor(() => - expect(clientOnSpy).toHaveBeenCalledWith('connection.recovered', expect.any(Function)), + expect(clientOnSpy).toHaveBeenCalledWith( + 'connection.recovered', + expect.any(Function), + ), ); }); @@ -636,22 +653,25 @@ describe('Channel', () => { const threadMessage = messages[0]; let threadHasAlreadyBeenOpened = false; - await renderComponent({ channel, chatClient }, ({ closeThread, openThread, thread }) => { - if (!thread) { - // if there is no open thread - if (!threadHasAlreadyBeenOpened) { - // and we haven't opened one before, open a thread - openThread(threadMessage, { preventDefault: () => null }); - threadHasAlreadyBeenOpened = true; + await renderComponent( + { channel, chatClient }, + ({ closeThread, openThread, thread }) => { + if (!thread) { + // if there is no open thread + if (!threadHasAlreadyBeenOpened) { + // and we haven't opened one before, open a thread + openThread(threadMessage, { preventDefault: () => null }); + threadHasAlreadyBeenOpened = true; + } else { + // if we opened it ourselves before, it means the thread was successfully closed + threadHasClosed = true; + } } else { - // if we opened it ourselves before, it means the thread was successfully closed - threadHasClosed = true; + // if a thread is open, close it. + closeThread({ preventDefault: () => null }); } - } else { - // if a thread is open, close it. - closeThread({ preventDefault: () => null }); - } - }); + }, + ); await waitFor(() => expect(threadHasClosed).toBe(true)); }); @@ -734,7 +754,9 @@ describe('Channel', () => { ({ loadMore, messages: contextMessages }) => { if (!contextMessages.find((message) => message.id === newMessages[0].id)) { // Our new message is not yet passed as part of channel context. Call loadMore and mock API response to include it. - useMockedApis(chatClient, [queryChannelWithNewMessages(newMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(newMessages, channel), + ]); loadMore(limit); } else { // If message has been added, update checker so we can verify it happened. @@ -767,7 +789,9 @@ describe('Channel', () => { ({ hasMore, loadMore, messages: contextMessages }) => { if (!contextMessages.find((message) => message.id === newMessages[0].id)) { // Our new message is not yet passed as part of channel context. Call loadMore and mock API response to include it. - useMockedApis(chatClient, [queryChannelWithNewMessages(newMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(newMessages, channel), + ]); loadMore(limit); } else { // If message has been added, set our checker variable, so we can verify if hasMore is false. @@ -790,7 +814,9 @@ describe('Channel', () => { ({ hasMore, loadMore, messages: contextMessages }) => { if (!contextMessages.some((message) => message.id === newMessages[0].id)) { // Our new messages are not yet passed as part of channel context. Call loadMore and mock API response to include it. - useMockedApis(chatClient, [queryChannelWithNewMessages(newMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(newMessages, channel), + ]); loadMore(limit); } else { // If message has been added, set our checker variable so we can verify if hasMore is true. @@ -819,7 +845,9 @@ describe('Channel', () => { it('should not load the second page, if the previous query has returned less then default limit messages', async () => { const { channel, chatClient } = await initClient(); const firstPageOfMessages = [generateMessage()]; - useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageOfMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(firstPageOfMessages, channel), + ]); let queryNextPageSpy; let contextMessageCount; await renderComponent( @@ -835,7 +863,12 @@ describe('Channel', () => { expect(queryNextPageSpy).not.toHaveBeenCalled(); expect(chatClient.axiosInstance.post).toHaveBeenCalledTimes(1); expect(chatClient.axiosInstance.post.mock.calls[0][1]).toMatchObject( - expect.objectContaining({ data: {}, presence: false, state: true, watch: false }), + expect.objectContaining({ + data: {}, + presence: false, + state: true, + watch: false, + }), ); expect(contextMessageCount).toBe(firstPageOfMessages.length); }); @@ -849,7 +882,9 @@ describe('Channel', () => { const secondPageMessages = Array.from({ length: 15 }, (_, i) => generateMessage({ created_at: new Date((i + 1) * 100000) }), ); - useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(firstPageMessages, channel), + ]); let queryNextPageSpy; let contextMessageCount; await renderComponent( @@ -857,7 +892,9 @@ describe('Channel', () => { ({ loadMore, messages: contextMessages }) => { queryNextPageSpy = jest.spyOn(channel, 'query'); contextMessageCount = contextMessages.length; - useMockedApis(chatClient, [queryChannelWithNewMessages(secondPageMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(secondPageMessages, channel), + ]); loadMore(); }, ); @@ -879,7 +916,9 @@ describe('Channel', () => { watchers: { limit: 100 }, }), ); - expect(contextMessageCount).toBe(firstPageMessages.length + secondPageMessages.length); + expect(contextMessageCount).toBe( + firstPageMessages.length + secondPageMessages.length, + ); }); }); it('should not load the second page, if the previous query has returned less then custom limit messages', async () => { @@ -888,7 +927,9 @@ describe('Channel', () => { messages: { limit: 10 }, }; const firstPageOfMessages = [generateMessage()]; - useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageOfMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(firstPageOfMessages, channel), + ]); let queryNextPageSpy; let contextMessageCount; await renderComponent( @@ -927,7 +968,9 @@ describe('Channel', () => { const secondPageMessages = Array.from({ length: equalCount - 1 }, (_, i) => generateMessage({ created_at: new Date((i + 1) * 100000) }), ); - useMockedApis(chatClient, [queryChannelWithNewMessages(firstPageMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(firstPageMessages, channel), + ]); let queryNextPageSpy; let contextMessageCount; @@ -936,7 +979,9 @@ describe('Channel', () => { ({ loadMore, messages: contextMessages }) => { queryNextPageSpy = jest.spyOn(channel, 'query'); contextMessageCount = contextMessages.length; - useMockedApis(chatClient, [queryChannelWithNewMessages(secondPageMessages, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(secondPageMessages, channel), + ]); loadMore(channelQueryOptions.messages.limit); }, ); @@ -964,7 +1009,9 @@ describe('Channel', () => { watchers: { limit: channelQueryOptions.messages.limit }, }), ); - expect(contextMessageCount).toBe(firstPageMessages.length + secondPageMessages.length); + expect(contextMessageCount).toBe( + firstPageMessages.length + secondPageMessages.length, + ); }); }); }); @@ -974,18 +1021,27 @@ describe('Channel', () => { const last_read = new Date(1000); const last_read_message_id = 'X'; const first_unread_message_id = 'Y'; - const lastReadMessage = generateMessage({ created_at: last_read, id: last_read_message_id }); + const lastReadMessage = generateMessage({ + created_at: last_read, + id: last_read_message_id, + }); const firstUnreadMessage = generateMessage({ id: first_unread_message_id }); const currentMessageSetLastReadLoadedFirstUnreadNotLoaded = [ generateMessage({ created_at: new Date(100) }), lastReadMessage, ]; - const currentMessageSetLastReadFirstUnreadLoaded = [lastReadMessage, firstUnreadMessage]; + const currentMessageSetLastReadFirstUnreadLoaded = [ + lastReadMessage, + firstUnreadMessage, + ]; const currentMessageSetLastReadNotLoadedFirstUnreadLoaded = [ firstUnreadMessage, generateMessage(), ]; - const currentMessageSetFirstUnreadLastReadNotLoaded = [generateMessage(), generateMessage()]; + const currentMessageSetFirstUnreadLastReadNotLoaded = [ + generateMessage(), + generateMessage(), + ]; const errorNotificationText = 'Failed to jump to the first unread message'; const ownReadStateBase = { last_read, @@ -1046,7 +1102,10 @@ describe('Channel', () => { let highlightedMessageId; await renderComponent( { channel, chatClient }, - ({ highlightedMessageId: highlightedMessageIdContext, jumpToFirstUnreadMessage }) => { + ({ + highlightedMessageId: highlightedMessageIdContext, + jumpToFirstUnreadMessage, + }) => { if (hasJumped) { highlightedMessageId = highlightedMessageIdContext; return; @@ -1341,7 +1400,9 @@ describe('Channel', () => { return; } if (!channelUnreadUiState) return; - useMockedApis(chatClient, [queryChannelWithNewMessages(jumpToPage, channel)]); + useMockedApis(chatClient, [ + queryChannelWithNewMessages(jumpToPage, channel), + ]); jumpToFirstUnreadMessage(jumpToPage.length); hasJumped = true; }, @@ -1384,7 +1445,9 @@ describe('Channel', () => { children: , }, ({ sendMessage }) => { - jest.spyOn(channel, 'sendMessage').mockImplementationOnce(() => new Promise(() => {})); + jest + .spyOn(channel, 'sendMessage') + .mockImplementationOnce(() => new Promise(() => {})); if (!hasSent) sendMessage({ text: messageText }); hasSent = true; }, @@ -1507,7 +1570,9 @@ describe('Channel', () => { await renderComponent({ channel, chatClient }, ({ deleteMessage }) => { deleteMessage(message); }); - await waitFor(() => expect(clientDeleteMessageSpy).toHaveBeenCalledWith(message.id)); + await waitFor(() => + expect(clientDeleteMessageSpy).toHaveBeenCalledWith(message.id), + ); }); it('should throw error instead of calling custom doDeleteMessageRequest function', async () => { @@ -1564,7 +1629,11 @@ describe('Channel', () => { editMessage(updatedMessage); }); await waitFor(() => - expect(clientUpdateMessageSpy).toHaveBeenCalledWith(updatedMessage, undefined, undefined), + expect(clientUpdateMessageSpy).toHaveBeenCalledWith( + updatedMessage, + undefined, + undefined, + ), ); }); @@ -1580,7 +1649,11 @@ describe('Channel', () => { ); await waitFor(() => - expect(doUpdateMessageRequest).toHaveBeenCalledWith(channel.cid, messages[0], undefined), + expect(doUpdateMessageRequest).toHaveBeenCalledWith( + channel.cid, + messages[0], + undefined, + ), ); }); @@ -1614,10 +1687,15 @@ describe('Channel', () => { { channel, chatClient, children: }, ({ messages: contextMessages, retrySendMessage, sendMessage }) => { if (!hasSent) { - jest.spyOn(channel, 'sendMessage').mockImplementationOnce(() => Promise.reject()); + jest + .spyOn(channel, 'sendMessage') + .mockImplementationOnce(() => Promise.reject()); sendMessage(messageObject); hasSent = true; - } else if (!hasRetried && contextMessages.some(({ status }) => status === 'failed')) { + } else if ( + !hasRetried && + contextMessages.some(({ status }) => status === 'failed') + ) { // retry useMockedApis(chatClient, [sendMessageApi(messageObject)]); retrySendMessage(messageObject); @@ -1648,7 +1726,10 @@ describe('Channel', () => { if (!hasSent) { sendMessage(messageObject); hasSent = true; - } else if (!hasRetried && contextMessages.some(({ status }) => status === 'failed')) { + } else if ( + !hasRetried && + contextMessages.some(({ status }) => status === 'failed') + ) { // retry useMockedApis(chatClient, [sendMessageApi(generateMessage(messageObject))]); retrySendMessage(messageObject); @@ -1704,7 +1785,12 @@ describe('Channel', () => { }; }; - const createChannelEventDispatcher = (body, client, channel, type = 'message.new') => + const createChannelEventDispatcher = ( + body, + client, + channel, + type = 'message.new', + ) => createOneTimeEventDispatcher( { type, @@ -1717,7 +1803,11 @@ describe('Channel', () => { it('should eventually pass down a message when a message.new event is triggered on the channel', async () => { const { channel, chatClient } = await initClient(); const message = generateMessage({ user }); - const dispatchMessageEvent = createChannelEventDispatcher({ message }, chatClient, channel); + const dispatchMessageEvent = createChannelEventDispatcher( + { message }, + chatClient, + channel, + ); const { findByText } = await renderComponent( { @@ -1788,8 +1878,12 @@ describe('Channel', () => { ); await waitFor(async () => { - expect(await queryByText(oldText, undefined, { timeout: 100 })).not.toBeInTheDocument(); - expect(await queryByText(newText, undefined, { timeout: 100 })).toBeInTheDocument(); + expect( + await queryByText(oldText, undefined, { timeout: 100 }), + ).not.toBeInTheDocument(); + expect( + await queryByText(newText, undefined, { timeout: 100 }), + ).toBeInTheDocument(); }); }); @@ -1820,8 +1914,12 @@ describe('Channel', () => { ); await waitFor(async () => { - expect(await queryByText(oldText, undefined, { timeout: 100 })).not.toBeInTheDocument(); - expect(await queryByText(newText, undefined, { timeout: 100 })).toBeInTheDocument(); + expect( + await queryByText(oldText, undefined, { timeout: 100 }), + ).not.toBeInTheDocument(); + expect( + await queryByText(newText, undefined, { timeout: 100 }), + ).toBeInTheDocument(); }); }); @@ -1830,7 +1928,11 @@ describe('Channel', () => { const markReadSpy = jest.spyOn(channel, 'markRead'); const message = generateMessage({ user: generateUser() }); - const dispatchMessageEvent = createChannelEventDispatcher({ message }, chatClient, channel); + const dispatchMessageEvent = createChannelEventDispatcher( + { message }, + chatClient, + channel, + ); await renderComponent({ channel, chatClient }, () => { dispatchMessageEvent(); @@ -1844,7 +1946,11 @@ describe('Channel', () => { const markReadSpy = jest.spyOn(channel, 'markRead'); const message = generateMessage({ user: generateUser() }); - const dispatchMessageEvent = createChannelEventDispatcher({ message }, chatClient, channel); + const dispatchMessageEvent = createChannelEventDispatcher( + { message }, + chatClient, + channel, + ); await renderComponent({ channel, chatClient }, () => { dispatchMessageEvent(); @@ -1862,7 +1968,11 @@ describe('Channel', () => { }); jest.spyOn(channel, 'countUnread').mockImplementation(() => unreadAmount); const message = generateMessage({ user: generateUser() }); - const dispatchMessageEvent = createChannelEventDispatcher({ message }, chatClient, channel); + const dispatchMessageEvent = createChannelEventDispatcher( + { message }, + chatClient, + channel, + ); await renderComponent({ channel, chatClient }, () => { dispatchMessageEvent(); @@ -1912,18 +2022,21 @@ describe('Channel', () => { channel, ); let newThreadMessageWasAdded = false; - await renderComponent({ channel, chatClient }, ({ openThread, thread, threadMessages }) => { - if (!thread) { - // first, open thread - openThread(threadMessage, { preventDefault: () => null }); - } else if (!threadMessages.some(({ id }) => id === newThreadMessage.id)) { - // then, add new thread message - // FIXME: dispatch event needs to be queued on event loop now - setTimeout(() => dispatchNewThreadMessageEvent(), 0); - } else { - newThreadMessageWasAdded = true; - } - }); + await renderComponent( + { channel, chatClient }, + ({ openThread, thread, threadMessages }) => { + if (!thread) { + // first, open thread + openThread(threadMessage, { preventDefault: () => null }); + } else if (!threadMessages.some(({ id }) => id === newThreadMessage.id)) { + // then, add new thread message + // FIXME: dispatch event needs to be queued on event loop now + setTimeout(() => dispatchNewThreadMessageEvent(), 0); + } else { + newThreadMessageWasAdded = true; + } + }, + ); await waitFor(() => expect(newThreadMessageWasAdded).toBe(true)); }); @@ -1938,9 +2051,11 @@ describe('Channel', () => { name: 'MessageList', }, { - callback: (message) => ({ openThread, thread }) => { - if (!thread) openThread(message, { preventDefault: () => null }); - }, + callback: + (message) => + ({ openThread, thread }) => { + if (!thread) openThread(message, { preventDefault: () => null }); + }, component: Thread, getFirstMessageAvatar: () => { // the first avatar is that of the ThreadHeader @@ -1958,7 +2073,11 @@ describe('Channel', () => { const dispatchUserUpdatedEvent = createChannelEventDispatcher( { type: 'user.updated', - user: { ...user, ...updatedAttribute, updated_at: new Date().toISOString() }, + user: { + ...user, + ...updatedAttribute, + updated_at: new Date().toISOString(), + }, }, chatClient, channel, @@ -2068,7 +2187,11 @@ describe('Channel', () => { }; await act(async () => { - await renderComponent({ channel: activeChannel, chatClient, children: }); + await renderComponent({ + channel: activeChannel, + chatClient, + children: , + }); }); expect(screen.queryByText(UNREAD_TEXT)).toBeInTheDocument(); diff --git a/src/components/Channel/channelState.ts b/src/components/Channel/channelState.ts index 686722efd..b0d77ea3a 100644 --- a/src/components/Channel/channelState.ts +++ b/src/components/Channel/channelState.ts @@ -1,11 +1,15 @@ -import type { Channel, MessageResponse, ChannelState as StreamChannelState } from 'stream-chat'; +import type { + Channel, + MessageResponse, + ChannelState as StreamChannelState, +} from 'stream-chat'; import type { ChannelState, StreamMessage } from '../../context/ChannelStateContext'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type ChannelStateReducerAction< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = | { type: 'closeThread'; @@ -44,7 +48,9 @@ export type ChannelStateReducerAction< } | { threadHasMore: boolean; - threadMessages: Array['formatMessage']>>; + threadMessages: Array< + ReturnType['formatMessage']> + >; type: 'loadMoreThreadFinished'; } | { @@ -84,183 +90,189 @@ export type ChannelStateReducerAction< type: 'jumpToLatestMessage'; }; -export const makeChannelReducer = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics ->() => ( - state: ChannelState, - action: ChannelStateReducerAction, -) => { - switch (action.type) { - case 'closeThread': { - return { - ...state, - thread: null, - threadLoadingMore: false, - threadMessages: [], - }; - } +export const makeChannelReducer = + () => + ( + state: ChannelState, + action: ChannelStateReducerAction, + ) => { + switch (action.type) { + case 'closeThread': { + return { + ...state, + thread: null, + threadLoadingMore: false, + threadMessages: [], + }; + } - case 'copyMessagesFromChannel': { - const { channel, parentId } = action; - return { - ...state, - messages: [...channel.state.messages], - pinnedMessages: [...channel.state.pinnedMessages], - // copying messages from channel happens with new message - this resets the suppressAutoscroll - suppressAutoscroll: false, - threadMessages: parentId - ? { ...channel.state.threads }[parentId] || [] - : state.threadMessages, - }; - } + case 'copyMessagesFromChannel': { + const { channel, parentId } = action; + return { + ...state, + messages: [...channel.state.messages], + pinnedMessages: [...channel.state.pinnedMessages], + // copying messages from channel happens with new message - this resets the suppressAutoscroll + suppressAutoscroll: false, + threadMessages: parentId + ? { ...channel.state.threads }[parentId] || [] + : state.threadMessages, + }; + } - case 'copyStateFromChannelOnEvent': { - const { channel } = action; - return { - ...state, - members: { ...channel.state.members }, - messages: [...channel.state.messages], - pinnedMessages: [...channel.state.pinnedMessages], - read: { ...channel.state.read }, - watcherCount: channel.state.watcher_count, - watchers: { ...channel.state.watchers }, - }; - } + case 'copyStateFromChannelOnEvent': { + const { channel } = action; + return { + ...state, + members: { ...channel.state.members }, + messages: [...channel.state.messages], + pinnedMessages: [...channel.state.pinnedMessages], + read: { ...channel.state.read }, + watcherCount: channel.state.watcher_count, + watchers: { ...channel.state.watchers }, + }; + } - case 'initStateFromChannel': { - const { channel, hasMore } = action; - return { - ...state, - hasMore, - loading: false, - members: { ...channel.state.members }, - messages: [...channel.state.messages], - pinnedMessages: [...channel.state.pinnedMessages], - read: { ...channel.state.read }, - watcherCount: channel.state.watcher_count, - watchers: { ...channel.state.watchers }, - }; - } + case 'initStateFromChannel': { + const { channel, hasMore } = action; + return { + ...state, + hasMore, + loading: false, + members: { ...channel.state.members }, + messages: [...channel.state.messages], + pinnedMessages: [...channel.state.pinnedMessages], + read: { ...channel.state.read }, + watcherCount: channel.state.watcher_count, + watchers: { ...channel.state.watchers }, + }; + } - case 'jumpToLatestMessage': { - return { - ...state, - hasMoreNewer: false, - highlightedMessageId: undefined, - loading: false, - suppressAutoscroll: false, - }; - } + case 'jumpToLatestMessage': { + return { + ...state, + hasMoreNewer: false, + highlightedMessageId: undefined, + loading: false, + suppressAutoscroll: false, + }; + } - case 'jumpToMessageFinished': { - return { - ...state, - hasMoreNewer: action.hasMoreNewer, - highlightedMessageId: action.highlightedMessageId, - }; - } + case 'jumpToMessageFinished': { + return { + ...state, + hasMoreNewer: action.hasMoreNewer, + highlightedMessageId: action.highlightedMessageId, + }; + } - case 'clearHighlightedMessage': { - return { - ...state, - highlightedMessageId: undefined, - }; - } + case 'clearHighlightedMessage': { + return { + ...state, + highlightedMessageId: undefined, + }; + } - case 'loadMoreFinished': { - const { hasMore, messages } = action; - return { - ...state, - hasMore, - loadingMore: false, - messages, - suppressAutoscroll: false, - }; - } + case 'loadMoreFinished': { + const { hasMore, messages } = action; + return { + ...state, + hasMore, + loadingMore: false, + messages, + suppressAutoscroll: false, + }; + } - case 'loadMoreNewerFinished': { - const { hasMoreNewer, messages } = action; - return { - ...state, - hasMoreNewer, - loadingMoreNewer: false, - messages, - }; - } + case 'loadMoreNewerFinished': { + const { hasMoreNewer, messages } = action; + return { + ...state, + hasMoreNewer, + loadingMoreNewer: false, + messages, + }; + } - case 'loadMoreThreadFinished': { - const { threadHasMore, threadMessages } = action; - return { - ...state, - threadHasMore, - threadLoadingMore: false, - threadMessages, - }; - } + case 'loadMoreThreadFinished': { + const { threadHasMore, threadMessages } = action; + return { + ...state, + threadHasMore, + threadLoadingMore: false, + threadMessages, + }; + } - case 'openThread': { - const { channel, message } = action; - return { - ...state, - thread: message, - threadHasMore: true, - threadMessages: message.id ? { ...channel.state.threads }[message.id] || [] : [], - threadSuppressAutoscroll: false, - }; - } + case 'openThread': { + const { channel, message } = action; + return { + ...state, + thread: message, + threadHasMore: true, + threadMessages: message.id + ? { ...channel.state.threads }[message.id] || [] + : [], + threadSuppressAutoscroll: false, + }; + } - case 'setError': { - const { error } = action; - return { ...state, error }; - } + case 'setError': { + const { error } = action; + return { ...state, error }; + } - case 'setLoadingMore': { - const { loadingMore } = action; - // suppress the autoscroll behavior - return { ...state, loadingMore, suppressAutoscroll: loadingMore }; - } + case 'setLoadingMore': { + const { loadingMore } = action; + // suppress the autoscroll behavior + return { ...state, loadingMore, suppressAutoscroll: loadingMore }; + } - case 'setLoadingMoreNewer': { - const { loadingMoreNewer } = action; - return { ...state, loadingMoreNewer }; - } + case 'setLoadingMoreNewer': { + const { loadingMoreNewer } = action; + return { ...state, loadingMoreNewer }; + } - case 'setThread': { - const { message } = action; - return { ...state, thread: message }; - } + case 'setThread': { + const { message } = action; + return { ...state, thread: message }; + } - case 'setTyping': { - const { channel } = action; - return { - ...state, - typing: { ...channel.state.typing }, - }; - } + case 'setTyping': { + const { channel } = action; + return { + ...state, + typing: { ...channel.state.typing }, + }; + } - case 'startLoadingThread': { - return { - ...state, - threadLoadingMore: true, - threadSuppressAutoscroll: true, - }; - } + case 'startLoadingThread': { + return { + ...state, + threadLoadingMore: true, + threadSuppressAutoscroll: true, + }; + } - case 'updateThreadOnEvent': { - const { channel, message } = action; - if (!state.thread) return state; - return { - ...state, - thread: - message?.id === state.thread.id ? channel.state.formatMessage(message) : state.thread, - threadMessages: state.thread?.id ? { ...channel.state.threads }[state.thread.id] || [] : [], - }; - } + case 'updateThreadOnEvent': { + const { channel, message } = action; + if (!state.thread) return state; + return { + ...state, + thread: + message?.id === state.thread.id + ? channel.state.formatMessage(message) + : state.thread, + threadMessages: state.thread?.id + ? { ...channel.state.threads }[state.thread.id] || [] + : [], + }; + } - default: - return state; - } -}; + default: + return state; + } + }; export const initialState = { error: null, diff --git a/src/components/Channel/hooks/useChannelContainerClasses.ts b/src/components/Channel/hooks/useChannelContainerClasses.ts index f3d847735..7a04dd808 100644 --- a/src/components/Channel/hooks/useChannelContainerClasses.ts +++ b/src/components/Channel/hooks/useChannelContainerClasses.ts @@ -4,7 +4,7 @@ import { useChatContext } from '../../../context/ChatContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useImageFlagEmojisOnWindowsClass = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >() => { const { useImageFlagEmojisOnWindows } = useChatContext('Channel'); return useImageFlagEmojisOnWindows && navigator.userAgent.match(/Win/) @@ -12,10 +12,11 @@ export const useImageFlagEmojisOnWindowsClass = < : ''; }; -export const getChatContainerClass = (customClass?: string) => customClass ?? 'str-chat__container'; +export const getChatContainerClass = (customClass?: string) => + customClass ?? 'str-chat__container'; export const useChannelContainerClasses = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ customClasses, }: Pick) => { diff --git a/src/components/Channel/hooks/useCreateChannelStateContext.ts b/src/components/Channel/hooks/useCreateChannelStateContext.ts index 114b95cf3..97625703d 100644 --- a/src/components/Channel/hooks/useCreateChannelStateContext.ts +++ b/src/components/Channel/hooks/useCreateChannelStateContext.ts @@ -7,7 +7,7 @@ import type { ChannelStateContextValue } from '../../../context/ChannelStateCont import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useCreateChannelStateContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( value: Omit, 'channelCapabilities'> & { channelCapabilitiesArray: string[]; @@ -19,17 +19,17 @@ export const useCreateChannelStateContext = < channel, channelCapabilitiesArray = [], channelConfig, + channelUnreadUiState, debounceURLEnrichmentMs, dragAndDropWindow, enrichURLForPreview, - giphyVersion, error, findURLFn, + giphyVersion, hasMore, hasMoreNewer, - imageAttachmentSizeHandler, - suppressAutoscroll, highlightedMessageId, + imageAttachmentSizeHandler, loading, loadingMore, maxNumberOfFiles, @@ -44,14 +44,14 @@ export const useCreateChannelStateContext = < read = {}, shouldGenerateVideoThumbnail, skipMessageDataMemoization, + suppressAutoscroll, thread, threadHasMore, threadLoadingMore, threadMessages = [], - channelUnreadUiState, videoAttachmentSizeHandler, - watcherCount, watcher_count, + watcherCount, watchers, } = value; @@ -61,7 +61,9 @@ export const useCreateChannelStateContext = < const notificationsLength = notifications.length; const readUsers = Object.values(read); const readUsersLength = readUsers.length; - const readUsersLastReads = readUsers.map(({ last_read }) => last_read.toISOString()).join(); + const readUsersLastReads = readUsers + .map(({ last_read }) => last_read.toISOString()) + .join(); const threadMessagesLength = threadMessages?.length; const channelCapabilities: Record = {}; @@ -74,7 +76,15 @@ export const useCreateChannelStateContext = < ? messages : messages .map( - ({ deleted_at, latest_reactions, pinned, reply_count, status, updated_at, user }) => + ({ + deleted_at, + latest_reactions, + pinned, + reply_count, + status, + updated_at, + user, + }) => `${deleted_at}${ latest_reactions ? latest_reactions.map(({ type }) => type).join() : '' }${pinned}${reply_count}${status}${ diff --git a/src/components/Channel/hooks/useCreateTypingContext.ts b/src/components/Channel/hooks/useCreateTypingContext.ts index ee55dfe04..726156fee 100644 --- a/src/components/Channel/hooks/useCreateTypingContext.ts +++ b/src/components/Channel/hooks/useCreateTypingContext.ts @@ -4,7 +4,7 @@ import type { TypingContextValue } from '../../../context/TypingContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useCreateTypingContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( value: TypingContextValue, ) => { diff --git a/src/components/Channel/hooks/useEditMessageHandler.ts b/src/components/Channel/hooks/useEditMessageHandler.ts index f5067e887..1776d7494 100644 --- a/src/components/Channel/hooks/useEditMessageHandler.ts +++ b/src/components/Channel/hooks/useEditMessageHandler.ts @@ -2,10 +2,13 @@ import { useChatContext } from '../../../context/ChatContext'; import type { StreamChat, UpdatedMessage } from 'stream-chat'; -import type { DefaultStreamChatGenerics, UpdateMessageOptions } from '../../../types/types'; +import type { + DefaultStreamChatGenerics, + UpdateMessageOptions, +} from '../../../types/types'; type UpdateHandler< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = ( cid: string, updatedMessage: UpdatedMessage, @@ -13,15 +16,20 @@ type UpdateHandler< ) => ReturnType['updateMessage']>; export const useEditMessageHandler = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( doUpdateMessageRequest?: UpdateHandler, ) => { const { channel, client } = useChatContext('useEditMessageHandler'); - return (updatedMessage: UpdatedMessage, options?: UpdateMessageOptions) => { + return ( + updatedMessage: UpdatedMessage, + options?: UpdateMessageOptions, + ) => { if (doUpdateMessageRequest && channel) { - return Promise.resolve(doUpdateMessageRequest(channel.cid, updatedMessage, options)); + return Promise.resolve( + doUpdateMessageRequest(channel.cid, updatedMessage, options), + ); } return client.updateMessage(updatedMessage, undefined, options); }; diff --git a/src/components/Channel/hooks/useMentionsHandlers.ts b/src/components/Channel/hooks/useMentionsHandlers.ts index ea42f6fab..af0cc4f26 100644 --- a/src/components/Channel/hooks/useMentionsHandlers.ts +++ b/src/components/Channel/hooks/useMentionsHandlers.ts @@ -5,18 +5,24 @@ import type { UserResponse } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export type OnMentionAction< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = (event: React.BaseSyntheticEvent, user?: UserResponse) => void; export const useMentionsHandlers = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( onMentionsHover?: OnMentionAction, onMentionsClick?: OnMentionAction, ) => useCallback( - (event: React.BaseSyntheticEvent, mentioned_users: UserResponse[]) => { - if ((!onMentionsHover && !onMentionsClick) || !(event.target instanceof HTMLElement)) { + ( + event: React.BaseSyntheticEvent, + mentioned_users: UserResponse[], + ) => { + if ( + (!onMentionsHover && !onMentionsClick) || + !(event.target instanceof HTMLElement) + ) { return; } @@ -25,7 +31,9 @@ export const useMentionsHandlers = < if (textContent[0] === '@') { const userName = textContent.replace('@', ''); - const user = mentioned_users?.find(({ id, name }) => name === userName || id === userName); + const user = mentioned_users?.find( + ({ id, name }) => name === userName || id === userName, + ); if ( onMentionsHover && @@ -35,7 +43,11 @@ export const useMentionsHandlers = < onMentionsHover(event, user); } - if (onMentionsClick && event.type === 'click' && typeof onMentionsClick === 'function') { + if ( + onMentionsClick && + event.type === 'click' && + typeof onMentionsClick === 'function' + ) { onMentionsClick(event, user); } } diff --git a/src/components/Channel/utils.ts b/src/components/Channel/utils.ts index aa7b6af1b..76af600a1 100644 --- a/src/components/Channel/utils.ts +++ b/src/components/Channel/utils.ts @@ -4,28 +4,30 @@ import type { ChannelState, MessageResponse } from 'stream-chat'; import type { ChannelNotifications } from '../../context/ChannelStateContext'; import type { DefaultStreamChatGenerics } from '../../types'; -export const makeAddNotifications = ( - setNotifications: Dispatch>, - notificationTimeouts: NodeJS.Timeout[], -) => (text: string, type: 'success' | 'error') => { - if (typeof text !== 'string' || (type !== 'success' && type !== 'error')) { - return; - } +export const makeAddNotifications = + ( + setNotifications: Dispatch>, + notificationTimeouts: NodeJS.Timeout[], + ) => + (text: string, type: 'success' | 'error') => { + if (typeof text !== 'string' || (type !== 'success' && type !== 'error')) { + return; + } - const id = nanoid(); + const id = nanoid(); - setNotifications((prevNotifications) => [...prevNotifications, { id, text, type }]); + setNotifications((prevNotifications) => [...prevNotifications, { id, text, type }]); - const timeout = setTimeout( - () => - setNotifications((prevNotifications) => - prevNotifications.filter((notification) => notification.id !== id), - ), - 5000, - ); + const timeout = setTimeout( + () => + setNotifications((prevNotifications) => + prevNotifications.filter((notification) => notification.id !== id), + ), + 5000, + ); - notificationTimeouts.push(timeout); -}; + notificationTimeouts.push(timeout); + }; /** * Utility function for jumpToFirstUnreadMessage @@ -33,7 +35,7 @@ export const makeAddNotifications = ( * @param msgSet */ export const findInMsgSetById = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( targetId: string, msgSet: ReturnType['formatMessage']>[], @@ -59,7 +61,7 @@ export const findInMsgSetById = < * @param exact */ export const findInMsgSetByDate = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( targetDate: Date, msgSet: @@ -73,7 +75,9 @@ export const findInMsgSetByDate = < let right = msgSet.length - 1; while (left <= right) { middle = Math.floor((right + left) / 2); - const middleTimestamp = new Date(msgSet[middle].created_at as string | Date).getTime(); + const middleTimestamp = new Date( + msgSet[middle].created_at as string | Date, + ).getTime(); const middleLeftTimestamp = msgSet[middle - 1]?.created_at && new Date(msgSet[middle - 1].created_at as string | Date).getTime(); @@ -93,7 +97,10 @@ export const findInMsgSetByDate = < else right = middle - 1; } - if (!exact || new Date(msgSet[left].created_at as string | Date).getTime() === targetTimestamp) { + if ( + !exact || + new Date(msgSet[left].created_at as string | Date).getTime() === targetTimestamp + ) { return { index: left, target: msgSet[left] }; } return { index: -1 }; diff --git a/src/components/ChannelHeader/ChannelHeader.tsx b/src/components/ChannelHeader/ChannelHeader.tsx index 05e56d9d9..e76c81a32 100644 --- a/src/components/ChannelHeader/ChannelHeader.tsx +++ b/src/components/ChannelHeader/ChannelHeader.tsx @@ -28,19 +28,20 @@ export type ChannelHeaderProps = { * The ChannelHeader component renders some basic information about a Channel. */ export const ChannelHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: ChannelHeaderProps, ) => { const { Avatar = DefaultAvatar, - MenuIcon = DefaultMenuIcon, image: overrideImage, live, + MenuIcon = DefaultMenuIcon, title: overrideTitle, } = props; - const { channel, watcher_count } = useChannelStateContext('ChannelHeader'); + const { channel, watcher_count } = + useChannelStateContext('ChannelHeader'); const { openMobileNav } = useChatContext('ChannelHeader'); const { t } = useTranslationContext('ChannelHeader'); const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({ @@ -70,7 +71,9 @@ export const ChannelHeader = <

      {displayTitle}{' '} {live && ( - {t('live')} + + {t('live')} + )}

      {subtitle &&

      {subtitle}

      } diff --git a/src/components/ChannelHeader/__tests__/ChannelHeader.test.js b/src/components/ChannelHeader/__tests__/ChannelHeader.test.js index b6d10b749..ab09f9ee1 100644 --- a/src/components/ChannelHeader/__tests__/ChannelHeader.test.js +++ b/src/components/ChannelHeader/__tests__/ChannelHeader.test.js @@ -59,7 +59,7 @@ async function renderComponent(props, channelData, channelType = 'messaging') { return renderComponentBase({ channel, client, props }); } -afterEach(cleanup); // eslint-disable-line +afterEach(cleanup); describe('ChannelHeader', () => { it('should display live label when prop live is true', async () => { @@ -69,7 +69,9 @@ describe('ChannelHeader', () => { ); const results = await axe(container); expect(results).toHaveNoViolations(); - expect(container.querySelector('.str-chat__header-livestream-livelabel')).toBeInTheDocument(); + expect( + container.querySelector('.str-chat__header-livestream-livelabel'), + ).toBeInTheDocument(); }); it("should display avatar with fallback image only if other user's name is available", async () => { @@ -170,7 +172,9 @@ describe('ChannelHeader', () => { const updatedAttribute = { name: 'new-name' }; await renderComponent(); - await waitFor(() => expect(screen.queryByText(updatedAttribute.name)).not.toBeInTheDocument()); + await waitFor(() => + expect(screen.queryByText(updatedAttribute.name)).not.toBeInTheDocument(), + ); act(() => { dispatchUserUpdatedEvent(client, { ...user2, ...updatedAttribute }); }); @@ -190,7 +194,10 @@ describe('ChannelHeader', () => { dispatchUserUpdatedEvent(client, { ...user2, ...updatedAttribute }); }); await waitFor(() => - expect(screen.getByTestId('avatar-img')).toHaveAttribute('src', updatedAttribute.image), + expect(screen.getByTestId('avatar-img')).toHaveAttribute( + 'src', + updatedAttribute.image, + ), ); }); @@ -273,8 +280,14 @@ describe('ChannelHeader', () => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages).toHaveLength(3); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); act(() => { @@ -284,8 +297,14 @@ describe('ChannelHeader', () => { await waitFor(() => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages[0]).toHaveAttribute('src', updatedAttribute.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); }); @@ -305,8 +324,14 @@ describe('ChannelHeader', () => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages).toHaveLength(3); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); act(() => { @@ -316,7 +341,10 @@ describe('ChannelHeader', () => { await waitFor(() => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); expect(avatarImages[2]).toHaveAttribute('src', updatedAttribute.image); }); }); diff --git a/src/components/ChannelList/ChannelList.tsx b/src/components/ChannelList/ChannelList.tsx index dd60c6f42..3b63f2ddc 100644 --- a/src/components/ChannelList/ChannelList.tsx +++ b/src/components/ChannelList/ChannelList.tsx @@ -4,11 +4,17 @@ import clsx from 'clsx'; import { ChannelListMessenger, ChannelListMessengerProps } from './ChannelListMessenger'; import { useConnectionRecoveredListener } from './hooks/useConnectionRecoveredListener'; import { useMobileNavigation } from './hooks/useMobileNavigation'; -import { CustomQueryChannelsFn, usePaginatedChannels } from './hooks/usePaginatedChannels'; +import { + CustomQueryChannelsFn, + usePaginatedChannels, +} from './hooks/usePaginatedChannels'; import { MAX_QUERY_CHANNELS_LIMIT, moveChannelUpwards } from './utils'; import { Avatar as DefaultAvatar } from '../Avatar'; -import { ChannelPreview, ChannelPreviewUIComponentProps } from '../ChannelPreview/ChannelPreview'; +import { + ChannelPreview, + ChannelPreviewUIComponentProps, +} from '../ChannelPreview/ChannelPreview'; import { ChannelSearchProps, ChannelSearch as DefaultChannelSearch, @@ -24,21 +30,33 @@ import { NullComponent } from '../UtilityComponents'; import { ChannelListContextProvider, ChatContextValue } from '../../context'; import { useChatContext } from '../../context/ChatContext'; -import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, Event } from 'stream-chat'; +import type { + Channel, + ChannelFilters, + ChannelOptions, + ChannelSort, + Event, +} from 'stream-chat'; import type { ChannelAvatarProps } from '../Avatar'; import type { TranslationContextValue } from '../../context/TranslationContext'; import type { DefaultStreamChatGenerics, PaginatorProps } from '../../types/types'; -import { useChannelListShape, usePrepareShapeHandlers } from './hooks/useChannelListShape'; +import { + useChannelListShape, + usePrepareShapeHandlers, +} from './hooks/useChannelListShape'; const DEFAULT_FILTERS = {}; const DEFAULT_OPTIONS = {}; const DEFAULT_SORT = {}; export type ChannelListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** Additional props for underlying ChannelSearch component and channel search controller, [available props](https://getstream.io/chat/docs/sdk/react/utility-components/channel_search/#props) */ - additionalChannelSearchProps?: Omit, 'setChannels'>; + additionalChannelSearchProps?: Omit< + ChannelSearchProps, + 'setChannels' + >; /** * When the client receives `message.new`, `notification.message_new`, and `notification.added_to_channel` events, we automatically * push that channel to the top of the list. If the channel doesn't currently exist in the list, we grab the channel from @@ -153,13 +171,15 @@ export type ChannelListProps< watchers?: { limit?: number; offset?: number }; }; -const UnMemoizedChannelList = ( +const UnMemoizedChannelList = < + SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>( props: ChannelListProps, ) => { const { additionalChannelSearchProps, - Avatar = DefaultAvatar, allowNewMessagesFromUnfilteredChannels = true, + Avatar = DefaultAvatar, channelRenderFilterFn, ChannelSearch = DefaultChannelSearch, customActiveChannel, @@ -167,9 +187,9 @@ const UnMemoizedChannelList = >, setChannels: React.Dispatch>>>, ) => { - if (!channels.length || channels.length > (options?.limit || MAX_QUERY_CHANNELS_LIMIT)) { + if ( + !channels.length || + channels.length > (options?.limit || MAX_QUERY_CHANNELS_LIMIT) + ) { return; } if (customActiveChannel) { // FIXME: this is wrong... - let customActiveChannelObject = channels.find((chan) => chan.id === customActiveChannel); + let customActiveChannelObject = channels.find( + (chan) => chan.id === customActiveChannel, + ); if (!customActiveChannelObject) { - //@ts-expect-error - [customActiveChannelObject] = await client.queryChannels({ id: customActiveChannel }); + //@ts-expect-error valid query + [customActiveChannelObject] = await client.queryChannels({ + id: customActiveChannel, + }); } if (customActiveChannelObject) { @@ -277,7 +304,9 @@ const UnMemoizedChannelList = = { /** Whether the channel query request returned an errored response */ error: ErrorFromResponse | null; @@ -28,7 +28,7 @@ export type ChannelListMessengerProps< * A preview list of channels, allowing you to select the channel you want to open */ export const ChannelListMessenger = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: PropsWithChildren>, ) => { diff --git a/src/components/ChannelList/__tests__/ChannelList.test.js b/src/components/ChannelList/__tests__/ChannelList.test.js index a7f048b1c..87c40982f 100644 --- a/src/components/ChannelList/__tests__/ChannelList.test.js +++ b/src/components/ChannelList/__tests__/ChannelList.test.js @@ -60,7 +60,11 @@ const channelsQueryStateMock = { * to those components might end up breaking tests for ChannelList, which will be quite painful * to debug then. */ -const ChannelPreviewComponent = ({ channel, channelUpdateCount, latestMessagePreview }) => ( +const ChannelPreviewComponent = ({ + channel, + channelUpdateCount, + latestMessagePreview, +}) => (
      {channelUpdateCount}
      {channel.data.name}
      @@ -219,7 +223,9 @@ describe('ChannelList', () => { }), filters: {}, }; - const queryChannelsMock = jest.spyOn(client, 'queryChannels').mockImplementationOnce(); + const queryChannelsMock = jest + .spyOn(client, 'queryChannels') + .mockImplementationOnce(); const { rerender } = render( @@ -294,7 +300,7 @@ describe('ChannelList', () => { // Wait for list of channels to load in DOM. await waitFor(() => { expect(getByRole('list')).toBeInTheDocument(); - // eslint-disable-next-line jest-dom/prefer-in-document + expect(queryAllByRole('listitem')).toHaveLength(1); }); const results = await axe(container); @@ -408,7 +414,9 @@ describe('ChannelList', () => { it('when queryChannels api returns no channels, `EmptyStateIndicator` should be rendered', async () => { useMockedApis(chatClient, [queryChannelsApi([])]); - const EmptyStateIndicator = () =>
      ; + const EmptyStateIndicator = () => ( +
      + ); const { container, getByTestId } = render( @@ -430,7 +438,9 @@ describe('ChannelList', () => { it('should show unique channels', async () => { useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2])]); - const ChannelPreview = (props) =>
      ; + const ChannelPreview = (props) => ( +
      + ); render( @@ -620,7 +630,7 @@ describe('ChannelList', () => { let channel; beforeEach(async () => { client = await getTestClientWithUser({ id: user1.id }); - // eslint-disable-next-line react-hooks/rules-of-hooks + useMockedApis(client, [getOrCreateChannelApi(mockedChannels[0])]); channel = client.channel('messaging', mockedChannels[0].id); await channel.watch(); @@ -661,7 +671,9 @@ describe('ChannelList', () => { }); await waitFor(() => { - expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).not.toBeInTheDocument(); + expect( + container.querySelector(SEARCH_RESULT_LIST_SELECTOR), + ).not.toBeInTheDocument(); expect(screen.queryByLabelText('Channel list')).toBeInTheDocument(); }); }); @@ -737,9 +749,13 @@ describe('ChannelList', () => { await waitFor(() => { if (clearSearchOnClickOutside) { - expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).not.toBeInTheDocument(); + expect( + container.querySelector(SEARCH_RESULT_LIST_SELECTOR), + ).not.toBeInTheDocument(); } else { - expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).toBeInTheDocument(); + expect( + container.querySelector(SEARCH_RESULT_LIST_SELECTOR), + ).toBeInTheDocument(); } }); jest.useRealTimers(); @@ -763,7 +779,9 @@ describe('ChannelList', () => { await fireEvent.click(clearButton); }); await waitFor(() => { - expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).not.toBeInTheDocument(); + expect( + container.querySelector(SEARCH_RESULT_LIST_SELECTOR), + ).not.toBeInTheDocument(); expect(container.querySelector(CHANNEL_LIST_SELECTOR)).toBeInTheDocument(); expect(input).toHaveValue(''); expect(input).toHaveFocus(); @@ -789,7 +807,9 @@ describe('ChannelList', () => { await fireEvent.click(returnIcon); }); await waitFor(() => { - expect(container.querySelector(SEARCH_RESULT_LIST_SELECTOR)).not.toBeInTheDocument(); + expect( + container.querySelector(SEARCH_RESULT_LIST_SELECTOR), + ).not.toBeInTheDocument(); expect(input).not.toHaveFocus(); expect(input).toHaveValue(''); expect(returnIcon).not.toBeInTheDocument(); @@ -817,10 +837,14 @@ describe('ChannelList', () => { await waitFor(() => { expect(screen.queryAllByRole('option')).toHaveLength(3); - expect(screen.queryByText(channelNotInTheList.channel.name)).not.toBeInTheDocument(); + expect( + screen.queryByText(channelNotInTheList.channel.name), + ).not.toBeInTheDocument(); }); - useMockedApis(client, [queryChannelsApi([channelNotInTheList, ...mockedChannels])]); + useMockedApis(client, [ + queryChannelsApi([channelNotInTheList, ...mockedChannels]), + ]); const input = screen.queryByTestId('search-input'); await act(() => { input.focus(); @@ -841,7 +865,9 @@ describe('ChannelList', () => { }); await waitFor(() => { - expect(screen.queryByText(channelNotInTheList.channel.name)).toBeInTheDocument(); + expect( + screen.queryByText(channelNotInTheList.channel.name), + ).toBeInTheDocument(); expect(screen.queryByTestId('return-icon')).not.toBeInTheDocument(); }); jest.useRealTimers(); @@ -895,7 +921,9 @@ describe('ChannelList', () => { }; beforeEach(() => { - useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2, testChannel3])]); + useMockedApis(chatClient, [ + queryChannelsApi([testChannel1, testChannel2, testChannel3]), + ]); }); it('should move channel to top of the list', async () => { @@ -918,7 +946,9 @@ describe('ChannelList', () => { const items = getAllByRole('listitem'); // Get the closes listitem to the channel that received new message. - const channelPreview = getByText(newMessage.text).closest(ROLE_LIST_ITEM_SELECTOR); + const channelPreview = getByText(newMessage.text).closest( + ROLE_LIST_ITEM_SELECTOR, + ); expect(channelPreview.isEqualNode(items[0])).toBe(true); const results = await axe(container); expect(results).toHaveNoViolations(); @@ -945,7 +975,9 @@ describe('ChannelList', () => { const items = getAllByRole('listitem'); // Get the closes listitem to the channel that received new message. - const channelPreview = getByText(newMessage.text).closest(ROLE_LIST_ITEM_SELECTOR); + const channelPreview = getByText(newMessage.text).closest( + ROLE_LIST_ITEM_SELECTOR, + ); expect(channelPreview.isEqualNode(items[2])).toBe(true); const results = await axe(container); expect(results).toHaveNoViolations(); @@ -1072,7 +1104,9 @@ describe('ChannelList', () => { useMockedApis(chatClient, [getOrCreateChannelApi(testChannel3)]); - act(() => dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel)); + act(() => + dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel), + ); await waitFor(() => { expect(getByTestId(testChannel3.channel.id)).toBeInTheDocument(); @@ -1100,7 +1134,9 @@ describe('ChannelList', () => { expect(getByRole('list')).toBeInTheDocument(); }); - act(() => dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel)); + act(() => + dispatchNotificationAddedToChannelEvent(chatClient, testChannel3.channel), + ); await waitFor(() => { expect(onAddedToChannel).toHaveBeenCalledTimes(1); @@ -1118,7 +1154,9 @@ describe('ChannelList', () => { }; beforeEach(() => { - useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2, testChannel3])]); + useMockedApis(chatClient, [ + queryChannelsApi([testChannel1, testChannel2, testChannel3]), + ]); }); it('should remove the channel from list by default', async () => { @@ -1133,7 +1171,9 @@ describe('ChannelList', () => { }); const nodeToBeRemoved = getByTestId(testChannel3.channel.id); - act(() => dispatchNotificationRemovedFromChannel(chatClient, testChannel3.channel)); + act(() => + dispatchNotificationRemovedFromChannel(chatClient, testChannel3.channel), + ); await waitFor(() => { expect(nodeToBeRemoved).not.toBeInTheDocument(); @@ -1146,7 +1186,10 @@ describe('ChannelList', () => { const onRemovedFromChannel = jest.fn(); const { container, getByRole } = await render( - + , ); // Wait for list of channels to load in DOM. @@ -1154,7 +1197,9 @@ describe('ChannelList', () => { expect(getByRole('list')).toBeInTheDocument(); }); - act(() => dispatchNotificationRemovedFromChannel(chatClient, testChannel3.channel)); + act(() => + dispatchNotificationRemovedFromChannel(chatClient, testChannel3.channel), + ); await waitFor(() => { expect(onRemovedFromChannel).toHaveBeenCalledTimes(1); @@ -1480,7 +1525,9 @@ describe('ChannelList', () => { act(() => dispatchConnectionRecoveredEvent(chatClient)); await waitFor(() => { - expect(parseInt(getByTestId('channelUpdateCount').textContent, 10)).toBe(updateCount + 1); + expect(parseInt(getByTestId('channelUpdateCount').textContent, 10)).toBe( + updateCount + 1, + ); }); const results = await axe(container); expect(results).toHaveNoViolations(); @@ -1669,7 +1716,8 @@ describe('ChannelList', () => { const channelsToBeLoaded = Array.from({ length: 5 }, generateChannel); const channelsToBeSet = Array.from({ length: 5 }, generateChannel); const channelsToIdString = (channels) => channels.map(({ id }) => id).join(); - const channelsDataToIdString = (channels) => channels.map(({ channel: { id } }) => id).join(); + const channelsDataToIdString = (channels) => + channels.map(({ channel: { id } }) => id).join(); const ChannelListCustom = () => { const { channels, setChannels } = useChannelListContext(); @@ -1694,7 +1742,9 @@ describe('ChannelList', () => { ); await waitFor(() => { - expect(screen.getByText(channelsDataToIdString(channelsToBeLoaded))).toBeInTheDocument(); + expect( + screen.getByText(channelsDataToIdString(channelsToBeLoaded)), + ).toBeInTheDocument(); }); await act(() => { @@ -1705,7 +1755,9 @@ describe('ChannelList', () => { expect( screen.queryByText(channelsDataToIdString(channelsToBeLoaded)), ).not.toBeInTheDocument(); - expect(screen.getByText(channelsDataToIdString(channelsToBeSet))).toBeInTheDocument(); + expect( + screen.getByText(channelsDataToIdString(channelsToBeSet)), + ).toBeInTheDocument(); }); }); }); diff --git a/src/components/ChannelList/hooks/useChannelDeletedListener.ts b/src/components/ChannelList/hooks/useChannelDeletedListener.ts index 9e8db7527..f09b808e9 100644 --- a/src/components/ChannelList/hooks/useChannelDeletedListener.ts +++ b/src/components/ChannelList/hooks/useChannelDeletedListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useChannelDeletedListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( diff --git a/src/components/ChannelList/hooks/useChannelHiddenListener.ts b/src/components/ChannelList/hooks/useChannelHiddenListener.ts index e8b221a7d..4b159dc44 100644 --- a/src/components/ChannelList/hooks/useChannelHiddenListener.ts +++ b/src/components/ChannelList/hooks/useChannelHiddenListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useChannelHiddenListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( diff --git a/src/components/ChannelList/hooks/useChannelListShape.ts b/src/components/ChannelList/hooks/useChannelListShape.ts index 0a70bbb76..8ecfcb5d0 100644 --- a/src/components/ChannelList/hooks/useChannelListShape.ts +++ b/src/components/ChannelList/hooks/useChannelListShape.ts @@ -17,7 +17,9 @@ import { useChatContext } from '../../../context'; import { getChannel } from '../../../utils'; import { ChannelListProps } from '../ChannelList'; -type SetChannels = Dispatch[]>>; +type SetChannels = Dispatch< + SetStateAction[]> +>; type BaseParameters = { event: Event; @@ -37,44 +39,45 @@ type HandleMessageNewParameters = BaseParameters lockChannelOrder: boolean; } & Required, 'filters' | 'sort'>>; -type HandleNotificationMessageNewParameters = BaseParameters & - RepeatedParameters & { - allowNewMessagesFromUnfilteredChannels: boolean; +type HandleNotificationMessageNewParameters = + BaseParameters & + RepeatedParameters & { + allowNewMessagesFromUnfilteredChannels: boolean; + lockChannelOrder: boolean; + } & Required, 'filters' | 'sort'>>; + +type HandleNotificationRemovedFromChannelParameters = + BaseParameters & RepeatedParameters; + +type HandleNotificationAddedToChannelParameters = + BaseParameters & + RepeatedParameters & { + allowNewMessagesFromUnfilteredChannels: boolean; + lockChannelOrder: boolean; + } & Required, 'sort'>>; + +type HandleMemberUpdatedParameters = + BaseParameters & { lockChannelOrder: boolean; - } & Required, 'filters' | 'sort'>>; + } & Required, 'sort' | 'filters'>>; -type HandleNotificationRemovedFromChannelParameters< - SCG extends ExtendableGenerics -> = BaseParameters & RepeatedParameters; - -type HandleNotificationAddedToChannelParameters< - SCG extends ExtendableGenerics -> = BaseParameters & - RepeatedParameters & { - allowNewMessagesFromUnfilteredChannels: boolean; - lockChannelOrder: boolean; - } & Required, 'sort'>>; - -type HandleMemberUpdatedParameters = BaseParameters & { - lockChannelOrder: boolean; -} & Required, 'sort' | 'filters'>>; - -type HandleChannelDeletedParameters = BaseParameters & - RepeatedParameters; +type HandleChannelDeletedParameters = + BaseParameters & RepeatedParameters; type HandleChannelHiddenParameters = BaseParameters & RepeatedParameters; -type HandleChannelVisibleParameters = BaseParameters & - RepeatedParameters; +type HandleChannelVisibleParameters = + BaseParameters & RepeatedParameters; -type HandleChannelTruncatedParameters = BaseParameters & - RepeatedParameters; +type HandleChannelTruncatedParameters = + BaseParameters & RepeatedParameters; -type HandleChannelUpdatedParameters = BaseParameters & - RepeatedParameters; +type HandleChannelUpdatedParameters = + BaseParameters & RepeatedParameters; -type HandleUserPresenceChangedParameters = BaseParameters; +type HandleUserPresenceChangedParameters = + BaseParameters; const shared = ({ customHandler, @@ -218,13 +221,16 @@ export const useChannelListShapeDefaults = () => const channel = await getChannel({ client, id: event.channel.id, - members: event.channel.members?.reduce((newMembers, { user, user_id }) => { - const userId = user_id || user?.id; + members: event.channel.members?.reduce( + (newMembers, { user, user_id }) => { + const userId = user_id || user?.id; - if (userId) newMembers.push(userId); + if (userId) newMembers.push(userId); - return newMembers; - }, []), + return newMembers; + }, + [], + ), type: event.channel.type, }); @@ -251,7 +257,9 @@ export const useChannelListShapeDefaults = () => return customHandler(setChannels, event); } - setChannels((channels) => channels.filter((channel) => channel.cid !== event.channel?.cid)); + setChannels((channels) => + channels.filter((channel) => channel.cid !== event.channel?.cid), + ); }, [], ); @@ -264,7 +272,11 @@ export const useChannelListShapeDefaults = () => setChannels, sort, }: HandleMemberUpdatedParameters) => { - if (!event.member?.user || event.member.user.id !== client.userID || !event.channel_type) { + if ( + !event.member?.user || + event.member.user.id !== client.userID || + !event.channel_type + ) { return; } @@ -338,7 +350,11 @@ export const useChannelListShapeDefaults = () => ); const handleChannelVisible = useCallback( - async ({ customHandler, event, setChannels }: HandleChannelVisibleParameters) => { + async ({ + customHandler, + event, + setChannels, + }: HandleChannelVisibleParameters) => { if (typeof customHandler === 'function') { return customHandler(setChannels, event); } @@ -377,7 +393,9 @@ export const useChannelListShapeDefaults = () => } setChannels((channels) => { - const channelIndex = channels.findIndex((channel) => channel.cid === event.channel?.cid); + const channelIndex = channels.findIndex( + (channel) => channel.cid === event.channel?.cid, + ); if (channelIndex > -1 && event.channel) { const newChannels = channels; @@ -385,7 +403,8 @@ export const useChannelListShapeDefaults = () => ...event.channel, hidden: event.channel?.hidden ?? newChannels[channelIndex].data?.hidden, own_capabilities: - event.channel?.own_capabilities ?? newChannels[channelIndex].data?.own_capabilities, + event.channel?.own_capabilities ?? + newChannels[channelIndex].data?.own_capabilities, }; return [...newChannels]; @@ -452,32 +471,33 @@ export const useChannelListShapeDefaults = () => ); }; -type UseDefaultHandleChannelListShapeParameters = Required< - Pick< - ChannelListProps, - 'allowNewMessagesFromUnfilteredChannels' | 'lockChannelOrder' | 'filters' | 'sort' - > -> & - Pick< - ChannelListProps, - | 'onAddedToChannel' - | 'onChannelDeleted' - | 'onChannelHidden' - | 'onChannelTruncated' - | 'onChannelUpdated' - | 'onChannelVisible' - | 'onMessageNew' - | 'onMessageNewHandler' - | 'onRemovedFromChannel' - > & { - setChannels: SetChannels; - customHandleChannelListShape?: (data: { - // can't use ReturnType> until we upgrade prettier to at least v2.7.0 - defaults: ReturnType; - event: Event; +type UseDefaultHandleChannelListShapeParameters = + Required< + Pick< + ChannelListProps, + 'allowNewMessagesFromUnfilteredChannels' | 'lockChannelOrder' | 'filters' | 'sort' + > + > & + Pick< + ChannelListProps, + | 'onAddedToChannel' + | 'onChannelDeleted' + | 'onChannelHidden' + | 'onChannelTruncated' + | 'onChannelUpdated' + | 'onChannelVisible' + | 'onMessageNew' + | 'onMessageNewHandler' + | 'onRemovedFromChannel' + > & { setChannels: SetChannels; - }) => void; - }; + customHandleChannelListShape?: (data: { + // can't use ReturnType> until we upgrade prettier to at least v2.7.0 + defaults: ReturnType; + event: Event; + setChannels: SetChannels; + }) => void; + }; export const usePrepareShapeHandlers = ({ allowNewMessagesFromUnfilteredChannels, @@ -556,16 +576,32 @@ export const usePrepareShapeHandlers = ({ }); break; case 'channel.hidden': - defaults.handleChannelHidden({ customHandler: onChannelHidden, event, setChannels }); + defaults.handleChannelHidden({ + customHandler: onChannelHidden, + event, + setChannels, + }); break; case 'channel.visible': - defaults.handleChannelVisible({ customHandler: onChannelVisible, event, setChannels }); + defaults.handleChannelVisible({ + customHandler: onChannelVisible, + event, + setChannels, + }); break; case 'channel.truncated': - defaults.handleChannelTruncated({ customHandler: onChannelTruncated, event, setChannels }); + defaults.handleChannelTruncated({ + customHandler: onChannelTruncated, + event, + setChannels, + }); break; case 'channel.updated': - defaults.handleChannelUpdated({ customHandler: onChannelUpdated, event, setChannels }); + defaults.handleChannelUpdated({ + customHandler: onChannelUpdated, + event, + setChannels, + }); break; case 'user.presence.changed': defaults.handleUserPresenceChanged({ event, setChannels }); diff --git a/src/components/ChannelList/hooks/useChannelMembershipState.ts b/src/components/ChannelList/hooks/useChannelMembershipState.ts index fb80c0766..8f303fecd 100644 --- a/src/components/ChannelList/hooks/useChannelMembershipState.ts +++ b/src/components/ChannelList/hooks/useChannelMembershipState.ts @@ -1,4 +1,9 @@ -import type { Channel, ChannelMemberResponse, EventTypes, ExtendableGenerics } from 'stream-chat'; +import type { + Channel, + ChannelMemberResponse, + EventTypes, + ExtendableGenerics, +} from 'stream-chat'; import { useSelectedChannelState } from './useSelectedChannelState'; const selector = (c: Channel) => c.state.membership; @@ -10,6 +15,8 @@ export function useChannelMembershipState( export function useChannelMembershipState( channel?: Channel | undefined, ): ChannelMemberResponse | undefined; -export function useChannelMembershipState(channel?: Channel) { +export function useChannelMembershipState( + channel?: Channel, +) { return useSelectedChannelState({ channel, selector, stateChangeEventKeys: keys }); } diff --git a/src/components/ChannelList/hooks/useChannelTruncatedListener.ts b/src/components/ChannelList/hooks/useChannelTruncatedListener.ts index 77e7c8b5d..295b05447 100644 --- a/src/components/ChannelList/hooks/useChannelTruncatedListener.ts +++ b/src/components/ChannelList/hooks/useChannelTruncatedListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useChannelTruncatedListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( diff --git a/src/components/ChannelList/hooks/useChannelUpdatedListener.ts b/src/components/ChannelList/hooks/useChannelUpdatedListener.ts index aa6fcfda8..9159a8568 100644 --- a/src/components/ChannelList/hooks/useChannelUpdatedListener.ts +++ b/src/components/ChannelList/hooks/useChannelUpdatedListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useChannelUpdatedListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( @@ -21,7 +21,9 @@ export const useChannelUpdatedListener = < useEffect(() => { const handleEvent = (event: Event) => { setChannels((channels) => { - const channelIndex = channels.findIndex((channel) => channel.cid === event.channel?.cid); + const channelIndex = channels.findIndex( + (channel) => channel.cid === event.channel?.cid, + ); if (channelIndex > -1 && event.channel) { const newChannels = channels; @@ -29,7 +31,8 @@ export const useChannelUpdatedListener = < ...event.channel, hidden: event.channel?.hidden ?? newChannels[channelIndex].data?.hidden, own_capabilities: - event.channel?.own_capabilities ?? newChannels[channelIndex].data?.own_capabilities, + event.channel?.own_capabilities ?? + newChannels[channelIndex].data?.own_capabilities, }; return [...newChannels]; diff --git a/src/components/ChannelList/hooks/useChannelVisibleListener.ts b/src/components/ChannelList/hooks/useChannelVisibleListener.ts index cd7120292..8b4cd8c34 100644 --- a/src/components/ChannelList/hooks/useChannelVisibleListener.ts +++ b/src/components/ChannelList/hooks/useChannelVisibleListener.ts @@ -10,7 +10,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useChannelVisibleListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( diff --git a/src/components/ChannelList/hooks/useConnectionRecoveredListener.ts b/src/components/ChannelList/hooks/useConnectionRecoveredListener.ts index d54bcebc5..4e6543893 100644 --- a/src/components/ChannelList/hooks/useConnectionRecoveredListener.ts +++ b/src/components/ChannelList/hooks/useConnectionRecoveredListener.ts @@ -5,7 +5,7 @@ import { useChatContext } from '../../../context/ChatContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useConnectionRecoveredListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( forceUpdate?: () => void, ) => { diff --git a/src/components/ChannelList/hooks/useMessageNewListener.ts b/src/components/ChannelList/hooks/useMessageNewListener.ts index 548d5f22d..a8e1218ba 100644 --- a/src/components/ChannelList/hooks/useMessageNewListener.ts +++ b/src/components/ChannelList/hooks/useMessageNewListener.ts @@ -10,7 +10,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useMessageNewListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( @@ -28,9 +28,14 @@ export const useMessageNewListener = < customHandler(setChannels, event); } else { setChannels((channels) => { - const channelInList = channels.filter((channel) => channel.cid === event.cid).length > 0; - - if (!channelInList && allowNewMessagesFromUnfilteredChannels && event.channel_type) { + const channelInList = + channels.filter((channel) => channel.cid === event.cid).length > 0; + + if ( + !channelInList && + allowNewMessagesFromUnfilteredChannels && + event.channel_type + ) { const channel = client.channel(event.channel_type, event.channel_id); return uniqBy([channel, ...channels], 'cid'); } diff --git a/src/components/ChannelList/hooks/useNotificationAddedToChannelListener.ts b/src/components/ChannelList/hooks/useNotificationAddedToChannelListener.ts index 520d580ef..bc73978ed 100644 --- a/src/components/ChannelList/hooks/useNotificationAddedToChannelListener.ts +++ b/src/components/ChannelList/hooks/useNotificationAddedToChannelListener.ts @@ -10,7 +10,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useNotificationAddedToChannelListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( @@ -19,7 +19,9 @@ export const useNotificationAddedToChannelListener = < ) => void, allowNewMessagesFromUnfilteredChannels = true, ) => { - const { client } = useChatContext('useNotificationAddedToChannelListener'); + const { client } = useChatContext( + 'useNotificationAddedToChannelListener', + ); useEffect(() => { const handleEvent = async (event: Event) => { diff --git a/src/components/ChannelList/hooks/useNotificationMessageNewListener.ts b/src/components/ChannelList/hooks/useNotificationMessageNewListener.ts index 6fd242b7d..274581247 100644 --- a/src/components/ChannelList/hooks/useNotificationMessageNewListener.ts +++ b/src/components/ChannelList/hooks/useNotificationMessageNewListener.ts @@ -10,7 +10,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useNotificationMessageNewListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( @@ -19,7 +19,9 @@ export const useNotificationMessageNewListener = < ) => void, allowNewMessagesFromUnfilteredChannels = true, ) => { - const { client } = useChatContext('useNotificationMessageNewListener'); + const { client } = useChatContext( + 'useNotificationMessageNewListener', + ); useEffect(() => { const handleEvent = async (event: Event) => { diff --git a/src/components/ChannelList/hooks/useNotificationRemovedFromChannelListener.ts b/src/components/ChannelList/hooks/useNotificationRemovedFromChannelListener.ts index 00a5d3db2..478185572 100644 --- a/src/components/ChannelList/hooks/useNotificationRemovedFromChannelListener.ts +++ b/src/components/ChannelList/hooks/useNotificationRemovedFromChannelListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useNotificationRemovedFromChannelListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, customHandler?: ( @@ -24,7 +24,9 @@ export const useNotificationRemovedFromChannelListener = < if (customHandler && typeof customHandler === 'function') { customHandler(setChannels, event); } else { - setChannels((channels) => channels.filter((channel) => channel.cid !== event.channel?.cid)); + setChannels((channels) => + channels.filter((channel) => channel.cid !== event.channel?.cid), + ); } }; diff --git a/src/components/ChannelList/hooks/usePaginatedChannels.ts b/src/components/ChannelList/hooks/usePaginatedChannels.ts index 6a27d0a30..7ee665627 100644 --- a/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -3,7 +3,13 @@ import uniqBy from 'lodash.uniqby'; import { MAX_QUERY_CHANNELS_LIMIT } from '../utils'; -import type { Channel, ChannelFilters, ChannelOptions, ChannelSort, StreamChat } from 'stream-chat'; +import type { + Channel, + ChannelFilters, + ChannelOptions, + ChannelSort, + StreamChat, +} from 'stream-chat'; import { useChatContext } from '../../../context/ChatContext'; @@ -14,10 +20,13 @@ import { DEFAULT_INITIAL_CHANNEL_PAGE_SIZE } from '../../../constants/limits'; const RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 5000; const MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS = 2000; -type AllowedQueryType = Extract; +type AllowedQueryType = Extract< + ChannelsQueryState['queryInProgress'], + 'reload' | 'load-more' +>; export type CustomQueryChannelParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { currentChannels: Array>; queryType: AllowedQueryType; @@ -26,11 +35,11 @@ export type CustomQueryChannelParams< }; export type CustomQueryChannelsFn< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = (params: CustomQueryChannelParams) => Promise; export const usePaginatedChannels = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( client: StreamChat, filters: ChannelFilters, @@ -53,7 +62,7 @@ export const usePaginatedChannels = < const recoveryThrottleInterval = recoveryThrottleIntervalMs < MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS ? MIN_RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS - : recoveryThrottleIntervalMs ?? RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS; + : (recoveryThrottleIntervalMs ?? RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS); // memoize props const filterString = useMemo(() => JSON.stringify(filters), [filters]); const sortString = useMemo(() => JSON.stringify(sort), [sort]); @@ -85,7 +94,11 @@ export const usePaginatedChannels = < ...options, }; - const channelQueryResponse = await client.queryChannels(filters, sort || {}, newOptions); + const channelQueryResponse = await client.queryChannels( + filters, + sort || {}, + newOptions, + ); const newChannels = queryType === 'reload' @@ -115,7 +128,11 @@ export const usePaginatedChannels = < ? now - lastRecoveryTimestamp.current : 0; - if (!isFirstRecovery && timeElapsedSinceLastRecoveryMs < recoveryThrottleInterval && !error) { + if ( + !isFirstRecovery && + timeElapsedSinceLastRecoveryMs < recoveryThrottleInterval && + !error + ) { return; } diff --git a/src/components/ChannelList/hooks/useSelectedChannelState.ts b/src/components/ChannelList/hooks/useSelectedChannelState.ts index bf1e67730..3100e6ffa 100644 --- a/src/components/ChannelList/hooks/useSelectedChannelState.ts +++ b/src/components/ChannelList/hooks/useSelectedChannelState.ts @@ -17,8 +17,8 @@ export function useSelectedChannelState(_: { }): O | undefined; export function useSelectedChannelState({ channel, - stateChangeEventKeys = ['all'], selector, + stateChangeEventKeys = ['all'], }: { selector: (channel: Channel) => O; channel?: Channel; diff --git a/src/components/ChannelList/hooks/useUserPresenceChangedListener.ts b/src/components/ChannelList/hooks/useUserPresenceChangedListener.ts index 771942caa..9d876f994 100644 --- a/src/components/ChannelList/hooks/useUserPresenceChangedListener.ts +++ b/src/components/ChannelList/hooks/useUserPresenceChangedListener.ts @@ -7,7 +7,7 @@ import type { Channel, Event } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useUserPresenceChangedListener = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( setChannels: React.Dispatch>>>, ) => { diff --git a/src/components/ChannelList/utils.ts b/src/components/ChannelList/utils.ts index 5c3830109..2a867d2dd 100644 --- a/src/components/ChannelList/utils.ts +++ b/src/components/ChannelList/utils.ts @@ -1,12 +1,19 @@ import uniqBy from 'lodash.uniqby'; -import type { Channel, ChannelSort, ChannelSortBase, ExtendableGenerics } from 'stream-chat'; +import type { + Channel, + ChannelSort, + ChannelSortBase, + ExtendableGenerics, +} from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../types/types'; import type { ChannelListProps } from './ChannelList'; export const MAX_QUERY_CHANNELS_LIMIT = 30; -type MoveChannelUpParams = { +type MoveChannelUpParams< + SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = { channels: Array>; cid: string; activeChannel?: Channel; @@ -15,7 +22,9 @@ type MoveChannelUpParams({ +export const moveChannelUp = < + SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +>({ activeChannel, channels, cid, @@ -56,7 +65,9 @@ export function findLastPinnedChannelIndex({ return lastPinnedChannelIndex; } -type MoveChannelUpwardsParams = { +type MoveChannelUpwardsParams< + SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = { channels: Array>; channelToMove: Channel; sort: ChannelSort; @@ -68,7 +79,7 @@ type MoveChannelUpwardsParams({ channels, channelToMove, @@ -181,7 +192,9 @@ export const shouldConsiderArchivedChannels = ( /** * Returns `true` only if `pinned_at` property is of type `string` within `membership` object. */ -export const isChannelPinned = (channel: Channel) => { +export const isChannelPinned = ( + channel: Channel, +) => { if (!channel) return false; const membership = channel.state.membership; @@ -192,7 +205,9 @@ export const isChannelPinned = (channel: Channel /** * Returns `true` only if `archived_at` property is of type `string` within `membership` object. */ -export const isChannelArchived = (channel: Channel) => { +export const isChannelArchived = ( + channel: Channel, +) => { if (!channel) return false; const membership = channel.state.membership; diff --git a/src/components/ChannelPreview/ChannelPreview.tsx b/src/components/ChannelPreview/ChannelPreview.tsx index a25bd333e..b6bc89738 100644 --- a/src/components/ChannelPreview/ChannelPreview.tsx +++ b/src/components/ChannelPreview/ChannelPreview.tsx @@ -8,7 +8,10 @@ import { getLatestMessagePreview as defaultGetLatestMessagePreview } from './uti import { ChatContextValue, useChatContext } from '../../context/ChatContext'; import { useTranslationContext } from '../../context/TranslationContext'; -import { MessageDeliveryStatus, useMessageDeliveryStatus } from './hooks/useMessageDeliveryStatus'; +import { + MessageDeliveryStatus, + useMessageDeliveryStatus, +} from './hooks/useMessageDeliveryStatus'; import type { Channel, Event } from 'stream-chat'; @@ -19,7 +22,7 @@ import type { TranslationContextValue } from '../../context/TranslationContext'; import type { DefaultStreamChatGenerics } from '../../types/types'; export type ChannelPreviewUIComponentProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = ChannelPreviewProps & { /** If the component's channel is the active (selected) Channel */ active?: boolean; @@ -42,7 +45,7 @@ export type ChannelPreviewUIComponentProps< }; export type ChannelPreviewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** Comes from either the `channelRenderFilterFn` or `usePaginatedChannels` call from [ChannelList](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelList.tsx) */ channel: Channel; @@ -72,15 +75,15 @@ export type ChannelPreviewProps< }; export const ChannelPreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: ChannelPreviewProps, ) => { const { channel, - Preview = ChannelPreviewMessenger, channelUpdateCount, getLatestMessagePreview = defaultGetLatestMessagePreview, + Preview = ChannelPreviewMessenger, } = props; const { channel: activeChannel, @@ -144,7 +147,9 @@ export const ChannelPreview = < refreshUnreadCount(); const handleEvent = () => { - setLastMessage(channel.state.latestMessages[channel.state.latestMessages.length - 1]); + setLastMessage( + channel.state.latestMessages[channel.state.latestMessages.length - 1], + ); refreshUnreadCount(); }; diff --git a/src/components/ChannelPreview/ChannelPreviewMessenger.tsx b/src/components/ChannelPreview/ChannelPreviewMessenger.tsx index ed93d7585..389c7bd14 100644 --- a/src/components/ChannelPreview/ChannelPreviewMessenger.tsx +++ b/src/components/ChannelPreview/ChannelPreviewMessenger.tsx @@ -8,7 +8,7 @@ import type { DefaultStreamChatGenerics } from '../../types/types'; import type { ChannelPreviewUIComponentProps } from './ChannelPreview'; const UnMemoizedChannelPreviewMessenger = < - SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + SCG extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: ChannelPreviewUIComponentProps, ) => { @@ -27,9 +27,8 @@ const UnMemoizedChannelPreviewMessenger = < watchers, } = props; - const { - ChannelPreviewActionButtons = DefaultChannelPreviewActionButtons, - } = useComponentContext(); + const { ChannelPreviewActionButtons = DefaultChannelPreviewActionButtons } = + useComponentContext(); const channelPreviewButton = useRef(null); @@ -78,7 +77,10 @@ const UnMemoizedChannelPreviewMessenger = < {displayTitle}
      {!!unread && ( -
      +
      {unread}
      )} diff --git a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js b/src/components/ChannelPreview/__tests__/ChannelPreview.test.js index 035ac4bb5..5380279d0 100644 --- a/src/components/ChannelPreview/__tests__/ChannelPreview.test.js +++ b/src/components/ChannelPreview/__tests__/ChannelPreview.test.js @@ -98,7 +98,6 @@ describe('ChannelPreview', () => { [c0, c1] = await client.queryChannels({}, {}); }); - // eslint-disable-next-line jest/expect-expect it('should mark channel as read, when set as active channel', async () => { // Mock the countUnread function on channel, to return 10. c0.countUnread = () => 10; @@ -670,8 +669,14 @@ describe('ChannelPreview', () => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages).toHaveLength(3); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); act(() => { @@ -681,8 +686,14 @@ describe('ChannelPreview', () => { await waitFor(() => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages[0]).toHaveAttribute('src', updatedAttribute.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); }); @@ -702,8 +713,14 @@ describe('ChannelPreview', () => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages).toHaveLength(3); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); - expect(avatarImages[2]).toHaveAttribute('src', channelState.members[2].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); + expect(avatarImages[2]).toHaveAttribute( + 'src', + channelState.members[2].user.image, + ); }); act(() => { @@ -713,7 +730,10 @@ describe('ChannelPreview', () => { await waitFor(() => { const avatarImages = screen.getAllByTestId(AVATAR_IMG_TEST_ID); expect(avatarImages[0]).toHaveAttribute('src', ownUser.image); - expect(avatarImages[1]).toHaveAttribute('src', channelState.members[1].user.image); + expect(avatarImages[1]).toHaveAttribute( + 'src', + channelState.members[1].user.image, + ); expect(avatarImages[2]).toHaveAttribute('src', updatedAttribute.image); }); }); diff --git a/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js b/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js index c523e1edc..6dfcda5d7 100644 --- a/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js +++ b/src/components/ChannelPreview/__tests__/ChannelPreviewMessenger.test.js @@ -74,7 +74,6 @@ describe('ChannelPreviewMessenger', () => { fireEvent.click(getByTestId(PREVIEW_TEST_ID)); await waitFor(() => { - // eslint-disable-next-line jest/prefer-called-with expect(setActiveChannel).toHaveBeenCalledTimes(1); expect(setActiveChannel).toHaveBeenCalledWith(channel, {}); }); diff --git a/src/components/ChannelPreview/__tests__/utils.test.js b/src/components/ChannelPreview/__tests__/utils.test.js index f254cb811..fb2722d33 100644 --- a/src/components/ChannelPreview/__tests__/utils.test.js +++ b/src/components/ChannelPreview/__tests__/utils.test.js @@ -60,7 +60,9 @@ describe('ChannelPreview utils', () => { describe('getDisplayTitle', () => { it('should return channel name, if it exists', async () => { const name = nanoid(); - const channel = await getQueriedChannelInstance(generateChannel({ channel: { name } })); + const channel = await getQueriedChannelInstance( + generateChannel({ channel: { name } }), + ); expect(getDisplayTitle(channel, chatClient.user)).toBe(name); }); @@ -69,7 +71,10 @@ describe('ChannelPreview utils', () => { const otherUser = generateUser(); const channel = await getQueriedChannelInstance( generateChannel({ - members: [generateMember({ user: otherUser }), generateMember({ user: clientUser })], + members: [ + generateMember({ user: otherUser }), + generateMember({ user: clientUser }), + ], }), ); expect(getDisplayTitle(channel, chatClient.user)).toBe(otherUser.name); @@ -79,7 +84,9 @@ describe('ChannelPreview utils', () => { describe('getDisplayImage', () => { it('should return channel image, if it exists', async () => { const image = nanoid(); - const channel = await getQueriedChannelInstance(generateChannel({ channel: { image } })); + const channel = await getQueriedChannelInstance( + generateChannel({ channel: { image } }), + ); expect(getDisplayImage(channel, chatClient.user)).toBe(image); }); @@ -88,7 +95,10 @@ describe('ChannelPreview utils', () => { const otherUser = generateUser(); const channel = await getQueriedChannelInstance( generateChannel({ - members: [generateMember({ user: otherUser }), generateMember({ user: clientUser })], + members: [ + generateMember({ user: otherUser }), + generateMember({ user: clientUser }), + ], }), ); expect(getDisplayImage(channel, chatClient.user)).toBe(otherUser.image); diff --git a/src/components/ChannelPreview/hooks/__tests__/useMessageDeliveryStatus.test.js b/src/components/ChannelPreview/hooks/__tests__/useMessageDeliveryStatus.test.js index 94a44f8b1..c716fa492 100644 --- a/src/components/ChannelPreview/hooks/__tests__/useMessageDeliveryStatus.test.js +++ b/src/components/ChannelPreview/hooks/__tests__/useMessageDeliveryStatus.test.js @@ -1,6 +1,9 @@ import React from 'react'; import { renderHook } from '@testing-library/react'; -import { MessageDeliveryStatus, useMessageDeliveryStatus } from '../useMessageDeliveryStatus'; +import { + MessageDeliveryStatus, + useMessageDeliveryStatus, +} from '../useMessageDeliveryStatus'; import { ChatContext } from '../../../../context'; import { dispatchMessageDeletedEvent, @@ -44,7 +47,9 @@ const renderComponent = ({ channel, client, lastMessage }) => { {children} ); - return renderHook(() => useMessageDeliveryStatus({ channel, lastMessage }), { wrapper }); + return renderHook(() => useMessageDeliveryStatus({ channel, lastMessage }), { + wrapper, + }); }; describe('Message delivery status', () => { diff --git a/src/components/ChannelPreview/hooks/useChannelPreviewInfo.ts b/src/components/ChannelPreview/hooks/useChannelPreviewInfo.ts index 336b7eb76..f1fbb80cc 100644 --- a/src/components/ChannelPreview/hooks/useChannelPreviewInfo.ts +++ b/src/components/ChannelPreview/hooks/useChannelPreviewInfo.ts @@ -6,7 +6,9 @@ import { useChatContext } from '../../../context'; import type { DefaultStreamChatGenerics } from '../../../types/types'; -export type ChannelPreviewInfoParams = { +export type ChannelPreviewInfoParams< + StreamChatGenerics extends DefaultStreamChatGenerics, +> = { channel: Channel; /** Manually set the image to render, defaults to the Channel image */ overrideImage?: string; @@ -15,7 +17,7 @@ export type ChannelPreviewInfoParams( props: ChannelPreviewInfoParams, ) => { diff --git a/src/components/ChannelPreview/hooks/useIsChannelMuted.ts b/src/components/ChannelPreview/hooks/useIsChannelMuted.ts index cda462f77..2e9c47197 100644 --- a/src/components/ChannelPreview/hooks/useIsChannelMuted.ts +++ b/src/components/ChannelPreview/hooks/useIsChannelMuted.ts @@ -7,7 +7,7 @@ import type { Channel } from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useIsChannelMuted = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel: Channel, ) => { diff --git a/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts b/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts index d7c63aa34..8d8e3c3f7 100644 --- a/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts +++ b/src/components/ChannelPreview/hooks/useMessageDeliveryStatus.ts @@ -12,7 +12,7 @@ export enum MessageDeliveryStatus { } type UseMessageStatusParamsChannelPreviewProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { channel: Channel; /** The last message received in a channel */ @@ -20,7 +20,7 @@ type UseMessageStatusParamsChannelPreviewProps< }; export const useMessageDeliveryStatus = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ channel, lastMessage, @@ -45,12 +45,12 @@ export const useMessageDeliveryStatus = < ? new Date(lastMessage.created_at) : lastMessage.created_at; - const channelReadByOthersAfterLastMessageUpdate = Object.values(channel.state.read).some( - ({ last_read: channelLastMarkedReadDate, user }) => { - const ignoreOwnReadStatus = client.user && user.id !== client.user.id; - return ignoreOwnReadStatus && lastMessageCreatedAtDate < channelLastMarkedReadDate; - }, - ); + const channelReadByOthersAfterLastMessageUpdate = Object.values( + channel.state.read, + ).some(({ last_read: channelLastMarkedReadDate, user }) => { + const ignoreOwnReadStatus = client.user && user.id !== client.user.id; + return ignoreOwnReadStatus && lastMessageCreatedAtDate < channelLastMarkedReadDate; + }); setMessageDeliveryStatus( channelReadByOthersAfterLastMessageUpdate @@ -79,7 +79,8 @@ export const useMessageDeliveryStatus = < useEffect(() => { if (!isOwnMessage(lastMessage)) return; const handleMarkRead = (event: Event) => { - if (event.user?.id !== client.user?.id) setMessageDeliveryStatus(MessageDeliveryStatus.READ); + if (event.user?.id !== client.user?.id) + setMessageDeliveryStatus(MessageDeliveryStatus.READ); }; channel.on('message.read', handleMarkRead); diff --git a/src/components/ChannelPreview/icons.tsx b/src/components/ChannelPreview/icons.tsx index 7ef798b1c..04507fdaa 100644 --- a/src/components/ChannelPreview/icons.tsx +++ b/src/components/ChannelPreview/icons.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/display-name */ import React from 'react'; import { ComponentPropsWithoutRef } from 'react'; diff --git a/src/components/ChannelPreview/utils.tsx b/src/components/ChannelPreview/utils.tsx index 4b79ddd0b..492b4293f 100644 --- a/src/components/ChannelPreview/utils.tsx +++ b/src/components/ChannelPreview/utils.tsx @@ -9,17 +9,20 @@ import type { TranslationContextValue } from '../../context/TranslationContext'; import type { DefaultStreamChatGenerics } from '../../types/types'; import { ChatContextValue } from '../../context'; -export const renderPreviewText = (text: string) => {text}; +export const renderPreviewText = (text: string) => ( + {text} +); const getLatestPollVote = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( latestVotesByOption: Record[]>, ) => { let latestVote: PollVote | undefined; for (const optionVotes of Object.values(latestVotesByOption)) { optionVotes.forEach((vote) => { - if (latestVote && new Date(latestVote.updated_at) >= new Date(vote.created_at)) return; + if (latestVote && new Date(latestVote.updated_at) >= new Date(vote.created_at)) + return; latestVote = vote; }); } @@ -28,14 +31,15 @@ const getLatestPollVote = < }; export const getLatestMessagePreview = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel: Channel, t: TranslationContextValue['t'], userLanguage: TranslationContextValue['userLanguage'] = 'en', isMessageAIGenerated?: ChatContextValue['isMessageAIGenerated'], ): string | JSX.Element => { - const latestMessage = channel.state.latestMessages[channel.state.latestMessages.length - 1]; + const latestMessage = + channel.state.latestMessages[channel.state.latestMessages.length - 1]; const previewTextToRender = latestMessage?.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] || @@ -55,7 +59,7 @@ export const getLatestMessagePreview = < const createdBy = poll.created_by?.id === channel.getClient().userID ? t('You') - : poll.created_by?.name ?? t('Poll'); + : (poll.created_by?.name ?? t('Poll')); return t('📊 {{createdBy}} created: {{ pollName}}', { createdBy, pollName: poll.name, @@ -64,7 +68,8 @@ export const getLatestMessagePreview = < const latestVote = getLatestPollVote( poll.latest_votes_by_option as Record[]>, ); - const option = latestVote && poll.options.find((opt) => opt.id === latestVote.option_id); + const option = + latestVote && poll.options.find((opt) => opt.id === latestVote.option_id); if (option && latestVote) { return t('📊 {{votedBy}} voted: {{pollOptionText}}', { @@ -72,7 +77,7 @@ export const getLatestMessagePreview = < votedBy: latestVote?.user?.id === channel.getClient().userID ? t('You') - : latestVote.user?.name ?? t('Poll'), + : (latestVote.user?.name ?? t('Poll')), }); } } @@ -98,7 +103,7 @@ export const getLatestMessagePreview = < export type GroupChannelDisplayInfo = { image?: string; name?: string }[]; export const getGroupChannelDisplayInfo = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel: Channel, ): GroupChannelDisplayInfo | undefined => { @@ -116,7 +121,7 @@ export const getGroupChannelDisplayInfo = < }; const getChannelDisplayInfo = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( info: 'name' | 'image', channel: Channel, @@ -130,14 +135,14 @@ const getChannelDisplayInfo = < }; export const getDisplayTitle = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel: Channel, currentUser?: UserResponse, ) => getChannelDisplayInfo('name', channel, currentUser); export const getDisplayImage = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( channel: Channel, currentUser?: UserResponse, diff --git a/src/components/ChannelSearch/ChannelSearch.tsx b/src/components/ChannelSearch/ChannelSearch.tsx index d6229db6f..24b6a3be3 100644 --- a/src/components/ChannelSearch/ChannelSearch.tsx +++ b/src/components/ChannelSearch/ChannelSearch.tsx @@ -1,7 +1,10 @@ import clsx from 'clsx'; import React from 'react'; -import { ChannelSearchControllerParams, useChannelSearch } from './hooks/useChannelSearch'; +import { + ChannelSearchControllerParams, + useChannelSearch, +} from './hooks/useChannelSearch'; import type { AdditionalSearchBarProps, SearchBarProps } from './SearchBar'; import { SearchBar as DefaultSearchBar } from './SearchBar'; @@ -22,7 +25,7 @@ export type AdditionalChannelSearchProps = { }; export type ChannelSearchProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = AdditionalSearchBarProps & AdditionalSearchInputProps & AdditionalSearchResultsProps & @@ -30,7 +33,7 @@ export type ChannelSearchProps< ChannelSearchControllerParams; const UnMemoizedChannelSearch = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: ChannelSearchProps, ) => { @@ -44,11 +47,11 @@ const UnMemoizedChannelSearch = < SearchBar = DefaultSearchBar, SearchEmpty, SearchInput = DefaultSearchInput, - SearchLoading, SearchInputIcon, + SearchLoading, SearchResultItem, - SearchResultsList, SearchResultsHeader, + SearchResultsList, ...channelSearchParams } = props; @@ -70,7 +73,9 @@ const UnMemoizedChannelSearch = <
      0, }, @@ -118,4 +123,6 @@ const UnMemoizedChannelSearch = < * Clicking on a list item will navigate you into a channel with the selected user. It can be used * on its own or added to the ChannelList component by setting the `showChannelSearch` prop to true. */ -export const ChannelSearch = React.memo(UnMemoizedChannelSearch) as typeof UnMemoizedChannelSearch; +export const ChannelSearch = React.memo( + UnMemoizedChannelSearch, +) as typeof UnMemoizedChannelSearch; diff --git a/src/components/ChannelSearch/SearchBar.tsx b/src/components/ChannelSearch/SearchBar.tsx index 0451a4c38..735ae91a6 100644 --- a/src/components/ChannelSearch/SearchBar.tsx +++ b/src/components/ChannelSearch/SearchBar.tsx @@ -65,7 +65,9 @@ export type AdditionalSearchBarProps = { SearchInputIcon?: React.ComponentType; }; -export type SearchBarProps = AdditionalSearchBarProps & SearchBarController & SearchInputProps; +export type SearchBarProps = AdditionalSearchBarProps & + SearchBarController & + SearchInputProps; // todo: add context menu control logic export const SearchBar = (props: SearchBarProps) => { @@ -142,7 +144,11 @@ export const SearchBar = (props: SearchBarProps) => { const closeAppMenu = useCallback(() => setMenuIsOpen(false), []); return ( -
      +
      {inputIsFocused ? ( { }; export type SearchResultsHeaderProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'results'>; const DefaultSearchResultsHeader = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ results, }: SearchResultsHeaderProps) => { @@ -43,15 +43,18 @@ const DefaultSearchResultsHeader = < }; export type SearchResultsListProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Required< - Pick, 'results' | 'SearchResultItem' | 'selectResult'> + Pick< + SearchResultsProps, + 'results' | 'SearchResultItem' | 'selectResult' + > > & { focusedUser?: number; }; const DefaultSearchResultsList = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: SearchResultsListProps, ) => { @@ -73,7 +76,7 @@ const DefaultSearchResultsList = < }; export type SearchResultItemProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = Pick, 'selectResult'> & { index: number; result: ChannelOrUserResponse; @@ -81,7 +84,7 @@ export type SearchResultItemProps< }; const DefaultSearchResultItem = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: SearchResultItemProps, ) => { @@ -135,7 +138,10 @@ const ResultsContainer = ({ return (
      {children}
      @@ -143,15 +149,17 @@ const ResultsContainer = ({ }; export type SearchResultsController< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { results: Array>; searching: boolean; - selectResult: (result: ChannelOrUserResponse) => Promise | void; + selectResult: ( + result: ChannelOrUserResponse, + ) => Promise | void; }; export type AdditionalSearchResultsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** Display search results as an absolutely positioned popup, defaults to false and shows inline */ popupResults?: boolean; @@ -168,22 +176,23 @@ export type AdditionalSearchResultsProps< }; export type SearchResultsProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics -> = AdditionalSearchResultsProps & SearchResultsController; + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = AdditionalSearchResultsProps & + SearchResultsController; export const SearchResults = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: SearchResultsProps, ) => { const { popupResults, results, - searching, SearchEmpty = DefaultSearchEmpty, - SearchResultsHeader = DefaultSearchResultsHeader, + searching, SearchLoading, SearchResultItem = DefaultSearchResultItem, + SearchResultsHeader = DefaultSearchResultsHeader, SearchResultsList = DefaultSearchResultsList, selectResult, } = props; diff --git a/src/components/ChannelSearch/__tests__/ChannelSearch.test.js b/src/components/ChannelSearch/__tests__/ChannelSearch.test.js index dacbe5048..55fa3bf69 100644 --- a/src/components/ChannelSearch/__tests__/ChannelSearch.test.js +++ b/src/components/ChannelSearch/__tests__/ChannelSearch.test.js @@ -85,7 +85,9 @@ describe('ChannelSearch', () => { it('starts with "searching" flag disabled', async () => { await renderSearch(); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).not.toBeInTheDocument(); }); it('sets "searching" flag on first typing stroke', async () => { @@ -93,7 +95,9 @@ describe('ChannelSearch', () => { await act(() => { typeText(typedText); }); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).toBeInTheDocument(); }); it('removes "searching" flag upon deleting the last character', async () => { @@ -101,11 +105,15 @@ describe('ChannelSearch', () => { await act(() => { typeText(typedText); }); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).toBeInTheDocument(); await act(() => { typeText(''); }); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).not.toBeInTheDocument(); }); it('removes "searching" flag upon setting search results', async () => { @@ -116,14 +124,18 @@ describe('ChannelSearch', () => { await act(() => { typeText(typedText); }); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).toBeInTheDocument(); await act(() => { jest.advanceTimersByTime(1000); }); await waitFor(() => { - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).not.toBeInTheDocument(); }); jest.useRealTimers(); }); @@ -146,7 +158,10 @@ describe('ChannelSearch', () => { expect(client.queryUsers).toHaveBeenCalledWith( expect.objectContaining({ - $or: [{ id: { $autocomplete: typedText } }, { name: { $autocomplete: typedText } }], + $or: [ + { id: { $autocomplete: typedText } }, + { name: { $autocomplete: typedText } }, + ], }), { id: 1 }, { limit }, @@ -169,7 +184,10 @@ describe('ChannelSearch', () => { jest.spyOn(client, 'queryUsers').mockResolvedValue({ users: [...otherUsers, user] }); jest.spyOn(client, 'queryChannels').mockResolvedValue([channelResponseData]); - const { typeText } = await renderSearch({ client, props: { searchForChannels: true } }); + const { typeText } = await renderSearch({ + client, + props: { searchForChannels: true }, + }); await act(() => { typeText(typedText); }); @@ -226,8 +244,12 @@ describe('ChannelSearch', () => { }); expect(client.queryUsers).toHaveBeenCalledTimes(1); - expect(screen.queryByTestId(TEST_ID.CHANNEL_SEARCH_RESULTS_HEADER)).not.toBeInTheDocument(); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.CHANNEL_SEARCH_RESULTS_HEADER), + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).not.toBeInTheDocument(); jest.useRealTimers(); }); @@ -250,8 +272,12 @@ describe('ChannelSearch', () => { }); expect(client.queryUsers).toHaveBeenCalledTimes(1); - expect(screen.queryByTestId(TEST_ID.CHANNEL_SEARCH_RESULTS_HEADER)).not.toBeInTheDocument(); - expect(screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR)).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.CHANNEL_SEARCH_RESULTS_HEADER), + ).not.toBeInTheDocument(); + expect( + screen.queryByTestId(TEST_ID.SEARCH_IN_PROGRESS_INDICATOR), + ).not.toBeInTheDocument(); jest.useRealTimers(); }); @@ -283,7 +309,10 @@ describe('ChannelSearch', () => { expect(client.queryUsers).toHaveBeenCalledTimes(1); expect(client.queryUsers).toHaveBeenCalledWith( expect.objectContaining({ - $or: [{ id: { $autocomplete: textToQuery } }, { name: { $autocomplete: textToQuery } }], + $or: [ + { id: { $autocomplete: textToQuery } }, + { name: { $autocomplete: textToQuery } }, + ], }), { id: 1 }, { limit: 8 }, @@ -323,7 +352,10 @@ describe('ChannelSearch', () => { expect(client.queryUsers).toHaveBeenCalledTimes(1); expect(client.queryUsers).toHaveBeenCalledWith( expect.objectContaining({ - $or: [{ id: { $autocomplete: textToQuery } }, { name: { $autocomplete: textToQuery } }], + $or: [ + { id: { $autocomplete: textToQuery } }, + { name: { $autocomplete: textToQuery } }, + ], }), { id: 1 }, { limit: 8 }, diff --git a/src/components/ChannelSearch/__tests__/SearchBar.test.js b/src/components/ChannelSearch/__tests__/SearchBar.test.js index ad23f7107..f434333ee 100644 --- a/src/components/ChannelSearch/__tests__/SearchBar.test.js +++ b/src/components/ChannelSearch/__tests__/SearchBar.test.js @@ -43,7 +43,7 @@ describe('SearchBar', () => { beforeEach(async () => { const user = generateUser(); client = await getTestClientWithUser({ id: user.id }); - useMockedApis(client, [queryUsersApi([user])]); // eslint-disable-line react-hooks/rules-of-hooks + useMockedApis(client, [queryUsersApi([user])]); }); it.each([ diff --git a/src/components/ChannelSearch/__tests__/SearchResults.test.js b/src/components/ChannelSearch/__tests__/SearchResults.test.js index 47bf988b1..85152b8e0 100644 --- a/src/components/ChannelSearch/__tests__/SearchResults.test.js +++ b/src/components/ChannelSearch/__tests__/SearchResults.test.js @@ -6,7 +6,11 @@ import { SearchResults } from '../SearchResults'; import { ChatProvider } from '../../../context/ChatContext'; -import { createClientWithChannel, generateChannel, generateUser } from '../../../mock-builders'; +import { + createClientWithChannel, + generateChannel, + generateUser, +} from '../../../mock-builders'; const SEARCH_RESULT_LIST_SELECTOR = '.str-chat__channel-search-result-list'; diff --git a/src/components/ChannelSearch/hooks/useChannelSearch.ts b/src/components/ChannelSearch/hooks/useChannelSearch.ts index c6014b940..608f9bec8 100644 --- a/src/components/ChannelSearch/hooks/useChannelSearch.ts +++ b/src/components/ChannelSearch/hooks/useChannelSearch.ts @@ -22,19 +22,23 @@ import type { SearchResultsController } from '../SearchResults'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export type ChannelSearchFunctionParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { setQuery: React.Dispatch>; - setResults: React.Dispatch[]>>; + setResults: React.Dispatch< + React.SetStateAction[]> + >; setSearching: React.Dispatch>; }; export type SearchController< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics -> = SearchInputController & SearchBarController & SearchResultsController; + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, +> = SearchInputController & + SearchBarController & + SearchResultsController; export type SearchQueryParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { channelFilters?: { filters?: ChannelFilters; @@ -51,7 +55,7 @@ export type SearchQueryParams< }; export type ChannelSearchParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** The type of channel to create on user result select, defaults to `messaging` */ channelType?: string; @@ -82,14 +86,14 @@ export type ChannelSearchParams< }; export type ChannelSearchControllerParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = ChannelSearchParams & { /** Set the array of channels displayed in the ChannelList */ setChannels?: React.Dispatch>>>; }; export const useChannelSearch = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ channelType = 'messaging', clearSearchOnClickOutside = true, @@ -103,11 +107,14 @@ export const useChannelSearch = < searchQueryParams, setChannels, }: ChannelSearchControllerParams): SearchController => { - const { client, setActiveChannel } = useChatContext('useChannelSearch'); + const { client, setActiveChannel } = + useChatContext('useChannelSearch'); const [inputIsFocused, setInputIsFocused] = useState(false); const [query, setQuery] = useState(''); - const [results, setResults] = useState>>([]); + const [results, setResults] = useState< + Array> + >([]); const [searching, setSearching] = useState(false); const searchQueryPromiseInProgress = useRef< @@ -156,7 +163,6 @@ export const useChannelSearch = < document.addEventListener('click', clickListener); return () => document.removeEventListener('click', clickListener); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [disabled, inputIsFocused, query, exitSearch, clearSearchOnClickOutside]); useEffect(() => { @@ -193,7 +199,9 @@ export const useChannelSearch = < setActiveChannel(result); selectedChannel = result; } else { - const newChannel = client.channel(channelType, { members: [client.userID, result.id] }); + const newChannel = client.channel(channelType, { + members: [client.userID, result.id], + }); await newChannel.watch(); setActiveChannel(newChannel); @@ -205,7 +213,14 @@ export const useChannelSearch = < } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [clearSearchOnClickOutside, client, exitSearch, onSelectResult, setActiveChannel, setChannels], + [ + clearSearchOnClickOutside, + client, + exitSearch, + onSelectResult, + setActiveChannel, + setChannels, + ], ); const getChannels = useCallback( @@ -213,7 +228,7 @@ export const useChannelSearch = < let results: ChannelOrUserResponse[] = []; try { const userQueryPromise = client.queryUsers( - // @ts-expect-error + // @ts-expect-error valid query { $or: [{ id: { $autocomplete: text } }, { name: { $autocomplete: text } }], ...searchQueryParams?.userFilters?.filters, @@ -228,7 +243,7 @@ export const useChannelSearch = < results = users.filter((u) => u.id !== client.user?.id); } else { const channelQueryPromise = client.queryChannels( - // @ts-expect-error + // @ts-expect-error valid query { name: { $autocomplete: text }, ...searchQueryParams?.channelFilters?.filters, @@ -263,10 +278,10 @@ export const useChannelSearch = < ); // eslint-disable-next-line react-hooks/exhaustive-deps - const scheduleGetChannels = useCallback(debounce(getChannels, searchDebounceIntervalMs), [ - getChannels, - searchDebounceIntervalMs, - ]); + const scheduleGetChannels = useCallback( + debounce(getChannels, searchDebounceIntervalMs), + [getChannels, searchDebounceIntervalMs], + ); const onSearch = useCallback( (event: React.ChangeEvent) => { diff --git a/src/components/ChannelSearch/icons.tsx b/src/components/ChannelSearch/icons.tsx index 20fab9a29..d841b79ef 100644 --- a/src/components/ChannelSearch/icons.tsx +++ b/src/components/ChannelSearch/icons.tsx @@ -36,7 +36,13 @@ export const ReturnIcon = () => ( ); export const XIcon = () => ( - + = Channel | UserResponse; export const isChannel = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( output: ChannelOrUserResponse, -): output is Channel => (output as Channel).cid != null; +): output is Channel => + (output as Channel).cid != null; diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index b908ee47b..f65b6a1ff 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -15,7 +15,7 @@ import type { DefaultStreamChatGenerics } from '../../types/types'; import type { MessageContextValue } from '../../context'; export type ChatProps< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { /** The StreamChat client object */ client: StreamChat; @@ -44,7 +44,7 @@ export type ChatProps< * as it provides the ChatContext. */ export const Chat = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( props: PropsWithChildren>, ) => { diff --git a/src/components/Chat/hooks/useChannelsQueryState.ts b/src/components/Chat/hooks/useChannelsQueryState.ts index 46329978c..0a7fc6fb7 100644 --- a/src/components/Chat/hooks/useChannelsQueryState.ts +++ b/src/components/Chat/hooks/useChannelsQueryState.ts @@ -16,7 +16,8 @@ export interface ChannelsQueryState { export const useChannelsQueryState = (): ChannelsQueryState => { const [error, setError] = useState | null>(null); - const [queryInProgress, setQueryInProgress] = useState('uninitialized'); + const [queryInProgress, setQueryInProgress] = + useState('uninitialized'); return { error, diff --git a/src/components/Chat/hooks/useChat.ts b/src/components/Chat/hooks/useChat.ts index 168c4c76e..d7fcedaea 100644 --- a/src/components/Chat/hooks/useChat.ts +++ b/src/components/Chat/hooks/useChat.ts @@ -8,12 +8,18 @@ import { SupportedTranslations, } from '../../../i18n'; -import type { AppSettingsAPIResponse, Channel, Event, Mute, StreamChat } from 'stream-chat'; +import type { + AppSettingsAPIResponse, + Channel, + Event, + Mute, + StreamChat, +} from 'stream-chat'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export type UseChatParams< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = { client: StreamChat; defaultLanguage?: SupportedTranslations; @@ -22,7 +28,7 @@ export type UseChatParams< }; export const useChat = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >({ client, defaultLanguage = 'en', @@ -45,7 +51,9 @@ export const useChat = < const closeMobileNav = () => setNavOpen(false); const openMobileNav = () => setTimeout(() => setNavOpen(true), 100); - const appSettings = useRef> | null>(null); + const appSettings = useRef> | null>( + null, + ); const getAppSettings = () => { if (appSettings.current) { @@ -91,7 +99,9 @@ export const useChat = < if (!userLanguage) { const browserLanguage = window.navigator.language.slice(0, 2); // just get language code, not country-specific version - userLanguage = isLanguageSupported(browserLanguage) ? browserLanguage : defaultLanguage; + userLanguage = isLanguageSupported(browserLanguage) + ? browserLanguage + : defaultLanguage; } const streami18n = i18nInstance || new Streami18n({ language: userLanguage }); diff --git a/src/components/Chat/hooks/useCreateChatClient.ts b/src/components/Chat/hooks/useCreateChatClient.ts index f98af175a..cefc95fe6 100644 --- a/src/components/Chat/hooks/useCreateChatClient.ts +++ b/src/components/Chat/hooks/useCreateChatClient.ts @@ -38,9 +38,11 @@ export const useCreateChatClient = (apiKey, undefined, cachedOptions); let didUserConnectInterrupt = false; - const connectionPromise = client.connectUser(cachedUserData, tokenOrProvider).then(() => { - if (!didUserConnectInterrupt) setChatClient(client); - }); + const connectionPromise = client + .connectUser(cachedUserData, tokenOrProvider) + .then(() => { + if (!didUserConnectInterrupt) setChatClient(client); + }); return () => { didUserConnectInterrupt = true; diff --git a/src/components/Chat/hooks/useCreateChatContext.ts b/src/components/Chat/hooks/useCreateChatContext.ts index d9cec2b0b..542362847 100644 --- a/src/components/Chat/hooks/useCreateChatContext.ts +++ b/src/components/Chat/hooks/useCreateChatContext.ts @@ -4,7 +4,7 @@ import type { ChatContextValue } from '../../../context/ChatContext'; import type { DefaultStreamChatGenerics } from '../../../types/types'; export const useCreateChatContext = < - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, >( value: ChatContextValue, ) => { diff --git a/src/components/ChatAutoComplete/ChatAutoComplete.tsx b/src/components/ChatAutoComplete/ChatAutoComplete.tsx index 7c5f2c512..eab63c1aa 100644 --- a/src/components/ChatAutoComplete/ChatAutoComplete.tsx +++ b/src/components/ChatAutoComplete/ChatAutoComplete.tsx @@ -10,30 +10,38 @@ import { useComponentContext } from '../../context/ComponentContext'; import type { CommandResponse, UserResponse } from 'stream-chat'; import type { TriggerSettings } from '../MessageInput/DefaultTriggerProvider'; -import type { CustomTrigger, DefaultStreamChatGenerics, UnknownType } from '../../types/types'; +import type { + CustomTrigger, + DefaultStreamChatGenerics, + UnknownType, +} from '../../types/types'; import { EmojiSearchIndex, EmojiSearchIndexResult } from '../MessageInput'; type ObjectUnion = T[keyof T]; export type SuggestionCommand< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = CommandResponse; export type SuggestionUser< - StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics + StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, > = UserResponse; -export type SuggestionEmoji = EmojiSearchIndexResult & T; +export type SuggestionEmoji = + EmojiSearchIndexResult & T; export type SuggestionItem< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - T extends UnknownType = UnknownType -> = SuggestionUser | SuggestionCommand | SuggestionEmoji; + T extends UnknownType = UnknownType, +> = + | SuggestionUser + | SuggestionCommand + | SuggestionEmoji; // FIXME: entity type is wrong, fix export type SuggestionItemProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - T extends UnknownType = UnknownType + T extends UnknownType = UnknownType, > = { className: string; component: React.ComponentType<{ @@ -59,41 +67,41 @@ export interface SuggestionHeaderProps { export type SuggestionListProps< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - V extends CustomTrigger = CustomTrigger -> = ObjectUnion< - { - [key in keyof TriggerSettings]: { - component: TriggerSettings[key]['component']; - currentTrigger: string; - dropdownScroll: (element: HTMLDivElement) => void; - getSelectedItem: - | ((item: Parameters[key]['output']>[0]) => void) - | null; - getTextToReplace: ( - item: Parameters[key]['output']>[0], - ) => { - caretPosition: 'start' | 'end' | 'next' | number; - text: string; - key?: string; - }; - Header: React.ComponentType; - onSelect: (newToken: { - caretPosition: 'start' | 'end' | 'next' | number; - text: string; - }) => void; - selectionEnd: number; - SuggestionItem: React.ComponentType; - values: Parameters< - Parameters[key]['dataProvider']>[2] - >[0]; - className?: string; - itemClassName?: string; - itemStyle?: React.CSSProperties; - style?: React.CSSProperties; - value?: string; + V extends CustomTrigger = CustomTrigger, +> = ObjectUnion<{ + [key in keyof TriggerSettings]: { + component: TriggerSettings[key]['component']; + currentTrigger: string; + dropdownScroll: (element: HTMLDivElement) => void; + getSelectedItem: + | (( + item: Parameters[key]['output']>[0], + ) => void) + | null; + getTextToReplace: ( + item: Parameters[key]['output']>[0], + ) => { + caretPosition: 'start' | 'end' | 'next' | number; + text: string; + key?: string; }; - } ->; + Header: React.ComponentType; + onSelect: (newToken: { + caretPosition: 'start' | 'end' | 'next' | number; + text: string; + }) => void; + selectionEnd: number; + SuggestionItem: React.ComponentType; + values: Parameters< + Parameters[key]['dataProvider']>[2] + >[0]; + className?: string; + itemClassName?: string; + itemStyle?: React.CSSProperties; + style?: React.CSSProperties; + value?: string; + }; +}>; export type ChatAutoCompleteProps = { /** Override the default disabled state of the underlying `textarea` component. */ @@ -120,7 +128,7 @@ export type ChatAutoCompleteProps = { const UnMemoizedChatAutoComplete = < StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, - V extends CustomTrigger = CustomTrigger + V extends CustomTrigger = CustomTrigger, >( props: ChatAutoCompleteProps, ) => { @@ -131,7 +139,12 @@ const UnMemoizedChatAutoComplete = < const { t } = useTranslationContext('ChatAutoComplete'); const messageInput = useMessageInputContext('ChatAutoComplete'); - const { cooldownRemaining, disabled, emojiSearchIndex, textareaRef: innerRef } = messageInput; + const { + cooldownRemaining, + disabled, + emojiSearchIndex, + textareaRef: innerRef, + } = messageInput; const placeholder = props.placeholder || t('Type your message'); diff --git a/src/components/ChatAutoComplete/__tests__/ChatAutocomplete.test.js b/src/components/ChatAutoComplete/__tests__/ChatAutocomplete.test.js index d5055a162..6d94894d3 100644 --- a/src/components/ChatAutoComplete/__tests__/ChatAutocomplete.test.js +++ b/src/components/ChatAutoComplete/__tests__/ChatAutocomplete.test.js @@ -52,7 +52,8 @@ const renderComponent = async ( messageInputContextOverrides = {}, activeChannel = channel, ) => { - const placeholderText = props.placeholder === null ? null : props.placeholder || 'placeholder'; + const placeholderText = + props.placeholder === null ? null : props.placeholder || 'placeholder'; const OverrideMessageInputContext = ({ children }) => { const currentContext = useMessageInputContext(); @@ -61,7 +62,9 @@ const renderComponent = async ( ...messageInputContextOverrides, }; return ( - {children} + + {children} + ); }; @@ -147,7 +150,10 @@ describe('ChatAutoComplete', () => { }); it('should give preference to cooldown value over the prop disabled', async () => { - await renderComponent({ disabled: false, placeholder: null }, { cooldownRemaining: 10 }); + await renderComponent( + { disabled: false, placeholder: null }, + { cooldownRemaining: 10 }, + ); expect(screen.queryByPlaceholderText('Placeholder')).not.toBeInTheDocument(); const textarea = screen.getByTestId('message-input'); expect(textarea).toBeDisabled(); @@ -236,7 +242,6 @@ describe('ChatAutoComplete', () => { typeText(userAutocompleteText); const userText = await queryAllByText(user.name); - // eslint-disable-next-line jest-dom/prefer-in-document expect(userText).toHaveLength(0); }); diff --git a/src/components/ChatView/ChatView.tsx b/src/components/ChatView/ChatView.tsx index e04b1195d..014731c87 100644 --- a/src/components/ChatView/ChatView.tsx +++ b/src/components/ChatView/ChatView.tsx @@ -23,9 +23,8 @@ const ChatViewContext = createContext({ }); export const ChatView = ({ children }: PropsWithChildren) => { - const [activeChatView, setActiveChatView] = useState( - 'channels', - ); + const [activeChatView, setActiveChatView] = + useState('channels'); const { theme } = useChatContext(); @@ -60,9 +59,8 @@ export const useThreadsViewContext = () => useContext(ThreadsViewContext); const ThreadsView = ({ children }: PropsWithChildren) => { const { activeChatView } = useContext(ChatViewContext); - const [activeThread, setActiveThread] = useState( - undefined, - ); + const [activeThread, setActiveThread] = + useState(undefined); const value = useMemo(() => ({ activeThread, setActiveThread }), [activeThread]); diff --git a/src/components/CommandItem/CommandItem.tsx b/src/components/CommandItem/CommandItem.tsx index 9b53eb7ef..08ad4b07b 100644 --- a/src/components/CommandItem/CommandItem.tsx +++ b/src/components/CommandItem/CommandItem.tsx @@ -25,4 +25,6 @@ const UnMemoizedCommandItem = (props: PropsWithChildren) => { ); }; -export const CommandItem = React.memo(UnMemoizedCommandItem) as typeof UnMemoizedCommandItem; +export const CommandItem = React.memo( + UnMemoizedCommandItem, +) as typeof UnMemoizedCommandItem; diff --git a/src/components/CommandItem/__tests__/CommandItem.test.js b/src/components/CommandItem/__tests__/CommandItem.test.js index 41f8195a2..df962b738 100644 --- a/src/components/CommandItem/__tests__/CommandItem.test.js +++ b/src/components/CommandItem/__tests__/CommandItem.test.js @@ -5,7 +5,7 @@ import '@testing-library/jest-dom'; import { CommandItem } from '../CommandItem'; -afterEach(cleanup); // eslint-disable-line +afterEach(cleanup); describe('commandItem', () => { it('should render component with empty entity', () => { diff --git a/src/components/DateSeparator/DateSeparator.tsx b/src/components/DateSeparator/DateSeparator.tsx index f4bbb3a50..daf75a6cb 100644 --- a/src/components/DateSeparator/DateSeparator.tsx +++ b/src/components/DateSeparator/DateSeparator.tsx @@ -56,4 +56,6 @@ const UnMemoizedDateSeparator = (props: DateSeparatorProps) => { /** * A simple date separator between messages. */ -export const DateSeparator = React.memo(UnMemoizedDateSeparator) as typeof UnMemoizedDateSeparator; +export const DateSeparator = React.memo( + UnMemoizedDateSeparator, +) as typeof UnMemoizedDateSeparator; diff --git a/src/components/DateSeparator/__tests__/DateSeparator.test.js b/src/components/DateSeparator/__tests__/DateSeparator.test.js index 22926d7af..e5669b48b 100644 --- a/src/components/DateSeparator/__tests__/DateSeparator.test.js +++ b/src/components/DateSeparator/__tests__/DateSeparator.test.js @@ -12,7 +12,7 @@ import { Streami18n } from '../../../i18n'; Dayjs.extend(calendar); -afterEach(cleanup); // eslint-disable-line +afterEach(cleanup); const DATE_SEPARATOR_TEST_ID = 'date-separator'; const dateMock = 'the date'; @@ -106,7 +106,8 @@ describe('DateSeparator', () => { chatProps: { i18nInstance: new Streami18n({ translationsForLanguage: { - 'timestamp/DateSeparator': '{{ timestamp | timestampFormatter(calendar: false) }}', + 'timestamp/DateSeparator': + '{{ timestamp | timestampFormatter(calendar: false) }}', }, }), }, @@ -144,7 +145,8 @@ describe('DateSeparator', () => { chatProps: { i18nInstance: new Streami18n({ translationsForLanguage: { - 'timestamp/DateSeparator': '{{ timestamp | timestampFormatter(calendar: false) }}', + 'timestamp/DateSeparator': + '{{ timestamp | timestampFormatter(calendar: false) }}', }, }), }, diff --git a/src/components/Dialog/DialogManager.ts b/src/components/Dialog/DialogManager.ts index caa14e7f3..5ec28036b 100644 --- a/src/components/Dialog/DialogManager.ts +++ b/src/components/Dialog/DialogManager.ts @@ -47,10 +47,13 @@ export class DialogManager { } get openDialogCount() { - return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => { - if (dialog.isOpen) return count + 1; - return count; - }, 0); + return Object.values(this.state.getLatestValue().dialogsById).reduce( + (count, dialog) => { + if (dialog.isOpen) return count + 1; + return count; + }, + 0, + ); } getOrCreate({ id }: GetOrCreateDialogParams) { @@ -120,7 +123,9 @@ export class DialogManager { } closeAll() { - Object.values(this.state.getLatestValue().dialogsById).forEach((dialog) => dialog.close()); + Object.values(this.state.getLatestValue().dialogsById).forEach((dialog) => + dialog.close(), + ); } toggle(params: GetOrCreateDialogParams, closeAll = false) { diff --git a/src/components/Dialog/DialogMenu.tsx b/src/components/Dialog/DialogMenu.tsx index 9fe04f998..33f7fcf09 100644 --- a/src/components/Dialog/DialogMenu.tsx +++ b/src/components/Dialog/DialogMenu.tsx @@ -3,7 +3,11 @@ import clsx from 'clsx'; export type DialogMenuButtonProps = ComponentProps<'button'>; -export const DialogMenuButton = ({ children, className, ...props }: DialogMenuButtonProps) => ( +export const DialogMenuButton = ({ + children, + className, + ...props +}: DialogMenuButtonProps) => (
      {children}
      diff --git a/src/components/Dialog/FormDialog.tsx b/src/components/Dialog/FormDialog.tsx index d9eacd9d3..9908b7301 100644 --- a/src/components/Dialog/FormDialog.tsx +++ b/src/components/Dialog/FormDialog.tsx @@ -36,7 +36,9 @@ type FormValue> = { }; export const FormDialog = < - F extends FormValue> = FormValue> + F extends FormValue> = FormValue< + Record + >, >({ className, close, @@ -55,7 +57,9 @@ export const FormDialog = < return acc as F; }); - const handleChange = useCallback>( + const handleChange = useCallback< + ChangeEventHandler + >( (event) => { const fieldId = event.target.id; const fieldConfig = fields[fieldId]; @@ -113,7 +117,9 @@ export const FormDialog = <
      {fieldConfig.label && (