Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions lib/Dashboard/TalkWidget.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
Expand Down
131 changes: 131 additions & 0 deletions tests/php/Dashboard/TalkWidgetTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Talk\Tests\php\Dashboard;

use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
use OCA\Talk\Dashboard\TalkWidget;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Message;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ProxyCacheMessageService;
use OCA\Talk\Webinary;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

class TalkWidgetTest extends TestCase {
protected Manager&MockObject $manager;
protected ParticipantService&MockObject $participantService;
protected MessageParser&MockObject $messageParser;
protected ChatManager&MockObject $chatManager;
protected TalkWidget $widget;

public function setUp(): void {
parent::setUp();

$l10n = $this->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());
}
}
Loading