diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 8621baaade..6b938aaa4d 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -2206,6 +2206,9 @@ func processReceivedMsg(_ res: ChatEvent) async { m.decreaseGroupReportsCounter(item.deletedChatItem.chatInfo.id) } } + if let updatedChatInfo = items.last?.deletedChatItem.chatInfo { + m.updateChatInfo(updatedChatInfo) + } } case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member_): await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member_) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 093827ac59..8ce0c50849 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -2277,6 +2277,7 @@ struct ChatView: View { if deletedItem.isActiveReport { m.decreaseGroupReportsCounter(chat.chatInfo.id) } + m.updateChatInfo(itemDeletion.deletedChatItem.chatInfo) } } } @@ -2466,6 +2467,9 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id) } } + if let updatedChatInfo = deletedItems.last?.deletedChatItem.chatInfo { + ChatModel.shared.updateChatInfo(updatedChatInfo) + } } await onSuccess() } catch { @@ -2497,6 +2501,9 @@ func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _ ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id) } } + if let updatedChatInfo = deleted.last?.deletedChatItem.chatInfo { + ChatModel.shared.updateChatInfo(updatedChatInfo) + } } await onSuccess() } catch { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index c3b681dbc7..7cb2d9fe5e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -2533,6 +2533,11 @@ object ChatController { } } } + r.chatItemDeletions.lastOrNull()?.deletedChatItem?.chatInfo?.let { updatedChatInfo -> + withContext(Dispatchers.Main) { + chatModel.chatsContext.updateChatInfo(rhId, updatedChatInfo) + } + } } is CR.GroupChatItemsDeleted -> { groupChatItemsDeleted(rhId, r) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index c51b5ff622..2ca0dcc35d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -455,6 +455,7 @@ fun ChatView( if (deletedItem.isActiveReport) { chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id) } + chatModel.chatsContext.updateChatInfo(chatRh, deleted.deletedChatItem.chatInfo) } withContext(Dispatchers.Main) { if (toChatItem != null) { @@ -2672,6 +2673,9 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List + chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo) + } } withContext(Dispatchers.Main) { for (di in deleted) { @@ -2712,6 +2716,9 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List + chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo) + } } withContext(Dispatchers.Main) { for (di in deleted) { diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index 79714af0ec..20f9468cd6 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1032,8 +1032,9 @@ processChatCommand' vr = \case user <- getUserByGroupId db chatId gInfo <- getGroupInfo db vr user chatId pure (user, gInfo) + chatScopeInfo <- mapM (getChatScopeInfo vr user) scope (timedItems, gInfo') <- withFastStore $ \db -> do - (timedItems, gInfo') <- updateGroupChatItemsReadList db vr user gInfo scope itemIds + (timedItems, gInfo') <- updateGroupChatItemsReadList db vr user gInfo chatScopeInfo itemIds timedItems' <- liftIO $ setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime pure (timedItems', gInfo') forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt @@ -2239,7 +2240,7 @@ processChatCommand' vr = \case deleted = deleted1 <> deleted2 <> deleted3 <> deleted4 -- Read group info with updated membersRequireAttention gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId - let acis' = map (updateCIGroupInfo gInfo') acis + let acis' = map (updateACIGroupInfo gInfo') acis unless (null acis') $ toView $ CEvtNewChatItems user acis' unless (null errs) $ toView $ CEvtChatErrors errs when withMessages $ deleteMessages user gInfo' deleted @@ -2299,11 +2300,6 @@ processChatCommand' vr = \case -- instead we re-read it once after deleting all members before response. void $ deleteOrUpdateMemberRecordIO db user gInfo m pure m {memberStatus = GSMemRemoved} - updateCIGroupInfo :: GroupInfo -> AChatItem -> AChatItem - updateCIGroupInfo gInfo' = \case - AChatItem SCTGroup SMDSnd (GroupChat _gInfo chatScopeInfo) ci -> - AChatItem SCTGroup SMDSnd (GroupChat gInfo' chatScopeInfo) ci - aci -> aci deleteMessages user gInfo@GroupInfo {membership} ms | groupFeatureMemberAllowed SGFFullDelete membership gInfo = deleteGroupMembersCIs user gInfo ms membership | otherwise = markGroupMembersCIsDeleted user gInfo ms membership diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index d3a0e6e59f..bdc5e4b920 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -37,7 +37,7 @@ import Data.Foldable (foldr') import Data.Functor (($>)) import Data.Functor.Identity import Data.Int (Int64) -import Data.List (find, mapAccumL, partition) +import Data.List (find, foldl', mapAccumL, partition) import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as L import Data.Map.Strict (Map) @@ -459,7 +459,14 @@ deleteGroupCIs user gInfo chatScopeInfo items byGroupMember_ deletedTs = do deleteCIFiles user ciFilesInfo (errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items) unless (null errs) $ toView $ CEvtChatErrors errs - pure deletions + vr <- chatVersionRange + deletions' <- case chatScopeInfo of + Nothing -> pure deletions + Just scopeInfo@GCSIMemberSupport {groupMember_} -> do + let decStats = countDeletedUnreadItems groupMember_ deletions + gInfo' <- withFastStore' $ \db -> updateGroupScopeUnreadStats db vr user gInfo scopeInfo decStats + pure $ map (updateDeletionGroupInfo gInfo') deletions + pure deletions' where deleteItem :: DB.Connection -> CChatItem 'CTGroup -> IO ChatItemDeletion deleteItem db (CChatItem md ci) = do @@ -467,6 +474,32 @@ deleteGroupCIs user gInfo chatScopeInfo items byGroupMember_ deletedTs = do Just m -> Just <$> updateGroupChatItemModerated db user gInfo ci m deletedTs Nothing -> Nothing <$ deleteGroupChatItem db user gInfo ci pure $ groupDeletion md gInfo chatScopeInfo ci ci' + countDeletedUnreadItems :: Maybe GroupMember -> [ChatItemDeletion] -> (Int, Int, Int) + countDeletedUnreadItems scopeMember_ = foldl' countItem (0, 0, 0) + where + countItem :: (Int, Int, Int) -> ChatItemDeletion -> (Int, Int, Int) + countItem (!unread, !unanswered, !mentions) ChatItemDeletion {deletedChatItem} + | aChatItemIsRcvNew deletedChatItem = + let unread' = unread + 1 + unanswered' = case (scopeMember_, aChatItemRcvFromMember deletedChatItem) of + (Just scopeMember, Just rcvFromMember) + | groupMemberId' rcvFromMember == groupMemberId' scopeMember -> unanswered + 1 + _ -> unanswered + mentions' = if isACIUserMention deletedChatItem then mentions + 1 else mentions + in (unread', unanswered', mentions') + | otherwise = (unread, unanswered, mentions) + updateDeletionGroupInfo :: GroupInfo -> ChatItemDeletion -> ChatItemDeletion + updateDeletionGroupInfo gInfo' ChatItemDeletion {deletedChatItem, toChatItem} = + ChatItemDeletion + { deletedChatItem = updateACIGroupInfo gInfo' deletedChatItem, + toChatItem = updateACIGroupInfo gInfo' <$> toChatItem + } + +updateACIGroupInfo :: GroupInfo -> AChatItem -> AChatItem +updateACIGroupInfo gInfo' = \case + AChatItem SCTGroup dir (GroupChat _gInfo chatScopeInfo) ci -> + AChatItem SCTGroup dir (GroupChat gInfo' chatScopeInfo) ci + aci -> aci deleteGroupMemberCIs :: MsgDirectionI d => User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> CM () deleteGroupMemberCIs user gInfo member byGroupMember msgDir = do diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 9a8dc786cd..dfd37527d1 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -215,6 +215,9 @@ data CIMentionMember = CIMentionMember } deriving (Eq, Show) +isACIUserMention :: AChatItem -> Bool +isACIUserMention (AChatItem _ _ _ ci) = isUserMention ci + isUserMention :: ChatItem c d -> Bool isUserMention ChatItem {meta = CIMeta {userMention}} = userMention @@ -295,6 +298,16 @@ chatItemMember GroupInfo {membership} ChatItem {chatDir} = case chatDir of CIGroupSnd -> membership CIGroupRcv m -> m +chatItemRcvFromMember :: ChatItem c d -> Maybe GroupMember +chatItemRcvFromMember ChatItem {chatDir} = case chatDir of + CIGroupRcv m -> Just m + _ -> Nothing + +chatItemIsRcvNew :: ChatItem c d -> Bool +chatItemIsRcvNew ChatItem {meta = CIMeta {itemStatus}} = case itemStatus of + CISRcvNew -> True + _ -> False + ciReactionAllowed :: ChatItem c d -> Bool ciReactionAllowed ChatItem {meta = CIMeta {itemDeleted = Just _}} = False ciReactionAllowed ChatItem {content} = isJust $ ciMsgContent content @@ -395,6 +408,12 @@ aChatItemTs (AChatItem _ _ _ ci) = chatItemTs' ci aChatItemDir :: AChatItem -> MsgDirection aChatItemDir (AChatItem _ sMsgDir _ _) = toMsgDirection sMsgDir +aChatItemRcvFromMember :: AChatItem -> Maybe GroupMember +aChatItemRcvFromMember (AChatItem _ _ _ ci) = chatItemRcvFromMember ci + +aChatItemIsRcvNew :: AChatItem -> Bool +aChatItemIsRcvNew (AChatItem _ _ _ ci) = chatItemIsRcvNew ci + updateFileStatus :: forall c d. ChatItem c d -> CIFileStatus d -> ChatItem c d updateFileStatus ci@ChatItem {file} status = case file of Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}} @@ -966,7 +985,10 @@ ciStatusNew = case msgDirection @d of ciCreateStatus :: forall d. MsgDirectionI d => CIContent d -> CIStatus d ciCreateStatus content = case msgDirection @d of SMDSnd -> ciStatusNew - SMDRcv -> if ciRequiresAttention content then ciStatusNew else CISRcvRead + SMDRcv + | isCIReport content -> CISRcvRead + | ciRequiresAttention content -> ciStatusNew + | otherwise -> CISRcvRead membersGroupItemStatus :: [(GroupSndStatus, Int)] -> CIStatus 'MDSnd membersGroupItemStatus memStatusCounts diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index cc2f97ac44..cc6529831c 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -182,6 +182,9 @@ ciMsgContent = \case CIRcvMsgContent mc -> Just mc _ -> Nothing +isCIReport :: CIContent d -> Bool +isCIReport = maybe False isReport . ciMsgContent + data MsgDecryptError = MDERatchetHeader | MDETooManySkipped diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 4470641491..6c66ea0e64 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -1233,7 +1233,7 @@ updateGroupMemberAccepted db User {userId} m@GroupMember {groupMemberId} status pure m {memberStatus = status, memberRole = role, updatedAt = currentTs} deleteGroupMemberSupportChat :: DB.Connection -> User -> GroupInfo -> GroupMember -> IO (GroupInfo, GroupMember) -deleteGroupMemberSupportChat db user@User {userId} g@GroupInfo {groupId, membersRequireAttention} m@GroupMember {groupMemberId, supportChat} = do +deleteGroupMemberSupportChat db user g m@GroupMember {groupMemberId} = do let requiredAttention = gmRequiresAttention m currentTs <- getCurrentTime DB.execute diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index d6affc4f12..a9bd03ed0e 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -80,6 +80,7 @@ module Simplex.Chat.Store.Messages updateGroupChatItemsRead, getGroupUnreadTimedItems, updateGroupChatItemsReadList, + updateGroupScopeUnreadStats, setGroupChatItemsDeleteAt, updateLocalChatItemsRead, getChatRefViaItemId, @@ -2018,13 +2019,17 @@ getGroupUnreadTimedItems db User {userId} groupId = |] (userId, groupId, CISRcvNew) -updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScope -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo) -updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId, membership, membersRequireAttention} scope itemIds = do +updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo) +updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scopeInfo_ itemIds = do currentTs <- liftIO getCurrentTime -- Possible improvement is to differentiate retrieval queries for each scope, -- but we rely on UI to not pass item IDs from incorrect scope. readItemsData <- liftIO $ catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds - g' <- updateChatStats readItemsData + g' <- case scopeInfo_ of + Nothing -> pure g + Just scopeInfo@GCSIMemberSupport {groupMember_} -> do + let decStats = countReadItems groupMember_ readItemsData + liftIO $ updateGroupScopeUnreadStats db vr user g scopeInfo decStats pure (timedItems readItemsData, g') where getUpdateGroupItem :: UTCTime -> ChatItemId -> IO (Maybe (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)) @@ -2038,67 +2043,58 @@ updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId, memb RETURNING chat_item_id, timed_ttl, timed_delete_at, group_member_id, user_mention |] (CISRcvRead, currentTs, userId, groupId, CISRcvNew, itemId) - updateChatStats :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> ExceptT StoreError IO GroupInfo - updateChatStats readItemsData = case scope of - Nothing -> pure g - Just GCSMemberSupport {groupMemberId_} -> case groupMemberId_ of - Nothing -> do - membership' <- updateGMStats membership - pure g {membership = membership'} - Just groupMemberId -> do - member <- getGroupMemberById db vr user groupMemberId - member' <- updateGMStats member - let didRequire = gmRequiresAttention member - nowRequires = gmRequiresAttention member' - if (not nowRequires && didRequire) - then do - liftIO $ - DB.execute - db - [sql| - UPDATE groups - SET members_require_attention = members_require_attention - 1 - WHERE user_id = ? AND group_id = ? - |] - (userId, groupId) - pure g {membersRequireAttention = membersRequireAttention - 1} - else - pure g - where - updateGMStats GroupMember {groupMemberId} = do - let unread = length readItemsData - (unanswered, mentions) = decStats - liftIO $ - DB.execute - db - [sql| - UPDATE group_members - SET support_chat_items_unread = support_chat_items_unread - ?, - support_chat_items_member_attention = support_chat_items_member_attention - ?, - support_chat_items_mentions = support_chat_items_mentions - ? - WHERE group_member_id = ? - |] - (unread, unanswered, mentions, groupMemberId) - getGroupMemberById db vr user groupMemberId - where - decStats :: (Int, Int) - decStats = foldl' countItem (0, 0) readItemsData - where - countItem :: (Int, Int) -> (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt) -> (Int, Int) - countItem (!unanswered, !mentions) (_, _, _, itemGMId_, userMention_) = - let unanswered' = case (groupMemberId_, itemGMId_) of - (Just scopeGMId, Just itemGMId) | itemGMId == scopeGMId -> unanswered + 1 - _ -> unanswered - mentions' = case userMention_ of - Just (BI True) -> mentions + 1 - _ -> mentions - in (unanswered', mentions') + countReadItems :: Maybe GroupMember -> [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> (Int, Int, Int) + countReadItems scopeMember_ readItemsData = + let unread = length readItemsData + (unanswered, mentions) = foldl' countItem (0, 0) readItemsData + in (unread, unanswered, mentions) + where + countItem :: (Int, Int) -> (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt) -> (Int, Int) + countItem (!unanswered, !mentions) (_, _, _, itemGMId_, userMention_) = + let unanswered' = case (scopeMember_, itemGMId_) of + (Just scopeMember, Just itemGMId) | itemGMId == groupMemberId' scopeMember -> unanswered + 1 + _ -> unanswered + mentions' = case userMention_ of + Just (BI True) -> mentions + 1 + _ -> mentions + in (unanswered', mentions') timedItems :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> [(ChatItemId, Int)] timedItems = foldl' addTimedItem [] where addTimedItem acc (itemId, Just ttl, Nothing, _, _) = (itemId, ttl) : acc addTimedItem acc _ = acc +updateGroupScopeUnreadStats :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> (Int, Int, Int) -> IO GroupInfo +updateGroupScopeUnreadStats db vr user g@GroupInfo {membership} scopeInfo (unread, unanswered, mentions) = + case scopeInfo of + GCSIMemberSupport {groupMember_} -> case groupMember_ of + Nothing -> do + membership' <- updateGMStats membership + pure g {membership = membership'} + Just member -> do + member' <- updateGMStats member + let didRequire = gmRequiresAttention member + nowRequires = gmRequiresAttention member' + if (not nowRequires && didRequire) + then decreaseGroupMembersRequireAttention db user g + else pure g + where + updateGMStats m@GroupMember {groupMemberId} = do + currentTs <- getCurrentTime + DB.execute + db + [sql| + UPDATE group_members + SET support_chat_items_unread = support_chat_items_unread - ?, + support_chat_items_member_attention = support_chat_items_member_attention - ?, + support_chat_items_mentions = support_chat_items_mentions - ?, + updated_at = ? + WHERE group_member_id = ? + |] + (unread, unanswered, mentions, currentTs, groupMemberId) + m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId + pure $ either (const m) id m_ -- Left shouldn't happen, but types require it + deriving instance Show BoolInt setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)] diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index cf3c3fb206..b0e98856d9 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -1,21 +1,3 @@ -Query: - UPDATE groups - SET members_require_attention = members_require_attention - 1 - WHERE user_id = ? AND group_id = ? - -Plan: -SEARCH groups USING INTEGER PRIMARY KEY (rowid=?) - -Query: - UPDATE group_members - SET support_chat_items_unread = support_chat_items_unread - ?, - support_chat_items_member_attention = support_chat_items_member_attention - ?, - support_chat_items_mentions = support_chat_items_mentions - ? - WHERE group_member_id = ? - -Plan: -SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) - Query: UPDATE groups SET chat_ts = ?, @@ -1321,6 +1303,17 @@ Query: Plan: SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) +Query: + UPDATE group_members + SET support_chat_items_unread = support_chat_items_unread - ?, + support_chat_items_member_attention = support_chat_items_member_attention - ?, + support_chat_items_mentions = support_chat_items_mentions - ?, + updated_at = ? + WHERE group_member_id = ? + +Plan: +SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?) + Query: UPDATE group_profiles SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ? diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index d1588ce6fb..1cf6d0d0d6 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -196,7 +196,8 @@ chatGroupTests = do it "should send scoped messages to support (single moderator)" testScopedSupportSingleModerator it "should send scoped messages to support (many moderators)" testScopedSupportManyModerators it "should send messages to admins and members" testSupportCLISendCommand - it "should correctly maintain unread stats for support chats" testScopedSupportUnreadStats + it "should correctly maintain unread stats for support chats on reading chat items" testScopedSupportUnreadStatsOnRead + it "should correctly maintain unread stats for support chats on deleting chat items" testScopedSupportUnreadStatsOnDelete testGroupCheckMessages :: HasCallStack => TestParams -> IO () testGroupCheckMessages = @@ -7009,8 +7010,8 @@ testSupportCLISendCommand = bob ##> "#team (support 4" bob <## "bad chat command: Failed reading: empty" -testScopedSupportUnreadStats :: HasCallStack => TestParams -> IO () -testScopedSupportUnreadStats = +testScopedSupportUnreadStatsOnRead :: HasCallStack => TestParams -> IO () +testScopedSupportUnreadStatsOnRead = testChatOpts4 opts aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do createGroup4 "team" alice (bob, GRMember) (cath, GRMember) (dan, GRModerator) @@ -7149,3 +7150,33 @@ testScopedSupportUnreadStats = { markRead = False } +testScopedSupportUnreadStatsOnDelete :: HasCallStack => TestParams -> IO () +testScopedSupportUnreadStatsOnDelete = + testChatOpts2 opts aliceProfile bobProfile $ \alice bob -> do + createGroup2 "team" alice bob + + alice ##> "/set delete #team on" + alice <## "updated group preferences:" + alice <## "Full deletion: on" + bob <## "alice updated group #team:" + bob <## "updated group preferences:" + bob <## "Full deletion: on" + + bob #> "#team (support) 1" + alice <# "#team (support: bob) bob> 1" + + msgIdBob <- lastItemId bob + + alice ##> "/member support chats #team" + alice <## "bob (Bob) (id 2): unread: 1, require attention: 1, mentions: 0" + + bob #$> ("/_delete item #1(_support) " <> msgIdBob <> " broadcast", id, "message deleted") + alice <# "#team (support: bob) bob> [deleted] 1" + + alice ##> "/member support chats #team" + alice <## "bob (Bob) (id 2): unread: 0, require attention: 0, mentions: 0" + where + opts = + testOpts + { markRead = False + }