diff --git a/lib/Dashboard/TalkWidget.php b/lib/Dashboard/TalkWidget.php index 373457413b9..7987f54ae05 100644 --- a/lib/Dashboard/TalkWidget.php +++ b/lib/Dashboard/TalkWidget.php @@ -169,7 +169,7 @@ public function getItems(string $userId, ?string $since = null, int $limit = 7): } if (($room->isFederatedConversation() && $attendee->getLastMentionMessage()) - || (!$room->isFederatedConversation() && $attendee->getLastMentionMessage() > $attendee->getLastReadMessage())) { + || (!$room->isFederatedConversation() && $this->hasUnreadMention($attendee))) { return true; } @@ -226,7 +226,7 @@ public function getItemsV2(string $userId, ?string $since = null, int $limit = 7 } if (($room->isFederatedConversation() && $attendee->getLastMentionMessage()) - || (!$room->isFederatedConversation() && $attendee->getLastMentionMessage() > $attendee->getLastReadMessage())) { + || (!$room->isFederatedConversation() && $this->hasUnreadMention($attendee))) { // Really mentioned $mentions[] = $room; continue; @@ -290,7 +290,7 @@ protected function prepareRoom(Room $room, string $userId): WidgetItem { } } elseif ($room->getLastMessageId() && $room->getLastMessage() && !$room->isFederatedConversation()) { $comment = $room->getLastMessage(); - if ($attendee->getLastMentionMessage() > $attendee->getLastReadMessage()) { + if ($this->hasUnreadMention($attendee)) { // Last mention is unread, show that instead try { $comment = $this->chatManager->getComment($room, (string)$attendee->getLastMentionMessage()); @@ -334,6 +334,15 @@ protected function getSubtitleFromMessage(Message $message): string { return str_replace($placeholders, $replacements, $message->getMessage()); } + /** + * last_mention_message is 0 when there is no mention, while last_read_message + * can be negative ({@see ChatManager::UNREAD_FIRST_MESSAGE}, {@see ChatManager::UNREAD_MIGRATION}). + */ + protected function hasUnreadMention(Attendee $attendee): bool { + $lastMention = $attendee->getLastMentionMessage(); + return $lastMention > 0 && $lastMention > $attendee->getLastReadMessage(); + } + protected function sortRooms(Room $roomA, Room $roomB): int { if ($roomA->getCallFlag() !== $roomB->getCallFlag()) { return $roomA->getCallFlag() !== Participant::FLAG_DISCONNECTED ? -1 : 1; diff --git a/tests/php/Dashboard/TalkWidgetTest.php b/tests/php/Dashboard/TalkWidgetTest.php new file mode 100644 index 00000000000..4458ed03a4a --- /dev/null +++ b/tests/php/Dashboard/TalkWidgetTest.php @@ -0,0 +1,131 @@ +createMock(IL10N::class); + $l10n->method('t')->willReturnCallback(static fn (string $text): string => $text); + + $this->manager = $this->createMock(Manager::class); + $this->participantService = $this->createMock(ParticipantService::class); + $this->messageParser = $this->createMock(MessageParser::class); + $this->chatManager = $this->createMock(ChatManager::class); + + $this->widget = new TalkWidget( + $this->createMock(IUserSession::class), + $this->createMock(Config::class), + $this->createMock(IURLGenerator::class), + $l10n, + $this->manager, + $this->createMock(AvatarService::class), + $this->participantService, + $this->messageParser, + $this->chatManager, + $this->createMock(ProxyCacheMessageService::class), + $this->createMock(IEventDispatcher::class), + $this->createMock(ITimeFactory::class), + ); + } + + public static function dataHasUnreadMention(): array { + return [ + 'no mention, never read' => [ChatManager::UNREAD_FIRST_MESSAGE, 0, false], + 'no mention, migrated marker' => [ChatManager::UNREAD_MIGRATION, 0, false], + 'no mention, nothing read' => [0, 0, false], + 'no mention, read' => [10, 0, false], + 'mention, never read' => [ChatManager::UNREAD_FIRST_MESSAGE, 5, true], + 'mention after the read marker' => [4, 5, true], + 'mention at the read marker' => [5, 5, false], + 'mention before the read marker' => [6, 5, false], + ]; + } + + #[DataProvider('dataHasUnreadMention')] + public function testHasUnreadMention(int $lastRead, int $lastMention, bool $expected): void { + $attendee = new Attendee(); + $attendee->setLastReadMessage($lastRead); + $attendee->setLastMentionMessage($lastMention); + + $this->assertSame($expected, self::invokePrivate($this->widget, 'hasUnreadMention', [$attendee])); + } + + public function testGetItemsV2UnreadConversationWithoutMention(): void { + $comment = $this->createMock(IComment::class); + + $room = $this->createMock(Room::class); + $room->method('getObjectType')->willReturn(''); + $room->method('getLobbyState')->willReturn(Webinary::LOBBY_NONE); + $room->method('getCallFlag')->willReturn(Participant::FLAG_DISCONNECTED); + $room->method('isFederatedConversation')->willReturn(false); + $room->method('getType')->willReturn(Room::TYPE_CHANGELOG); + $room->method('getLastMessageId')->willReturn(110); + $room->method('getLastMessage')->willReturn($comment); + $room->method('getDisplayName')->willReturn('user'); + $room->method('getToken')->willReturn('token'); + + // Never read (-2) and never mentioned (0), as the changelog conversation + // of a user who did not open it yet + $attendee = new Attendee(); + $attendee->setActorType(Attendee::ACTOR_USERS); + $attendee->setActorId('user'); + $attendee->setLastReadMessage(ChatManager::UNREAD_FIRST_MESSAGE); + $attendee->setLastMentionMessage(0); + + $this->manager->method('getRoomsForUser')->willReturn([$room]); + $this->participantService->method('getParticipant') + ->willReturn(new Participant($room, $attendee, null)); + + $message = $this->createMock(Message::class); + $message->method('getVisibility')->willReturn(true); + $message->method('getMessageParameters')->willReturn([]); + $message->method('getMessage')->willReturn('Changelog'); + $message->method('getExpirationDateTime')->willReturn(null); + $this->messageParser->method('createMessage')->willReturn($message); + + // "0" is not a message: asking for it fails with an InvalidArgumentException + $this->chatManager->expects($this->never())->method('getComment'); + + $items = $this->widget->getItemsV2('user'); + + $this->assertCount(1, $items->getItems()); + $this->assertSame('No unread mentions', $items->getHalfEmptyContentMessage()); + } +}