diff options
Diffstat (limited to 'tests/lib/Comments')
-rw-r--r-- | tests/lib/Comments/CommentTest.php | 223 | ||||
-rw-r--r-- | tests/lib/Comments/FakeFactory.php | 23 | ||||
-rw-r--r-- | tests/lib/Comments/FakeManager.php | 155 | ||||
-rw-r--r-- | tests/lib/Comments/ManagerTest.php | 2560 |
4 files changed, 2961 insertions, 0 deletions
diff --git a/tests/lib/Comments/CommentTest.php b/tests/lib/Comments/CommentTest.php new file mode 100644 index 00000000000..4a320666c83 --- /dev/null +++ b/tests/lib/Comments/CommentTest.php @@ -0,0 +1,223 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace Test\Comments; + +use OC\Comments\Comment; +use OCP\Comments\IComment; +use OCP\Comments\IllegalIDChangeException; +use OCP\Comments\MessageTooLongException; +use Test\TestCase; + +class CommentTest extends TestCase { + /** + * @throws IllegalIDChangeException + */ + public function testSettersValidInput(): void { + $comment = new Comment(); + + $id = 'comment23'; + $parentId = 'comment11.5'; + $topMostParentId = 'comment11.0'; + $childrenCount = 6; + $message = 'I like to comment comment'; + $verb = 'comment'; + $actor = ['type' => 'users', 'id' => 'alice']; + $creationDT = new \DateTime(); + $latestChildDT = new \DateTime('yesterday'); + $object = ['type' => 'files', 'id' => 'file64']; + $referenceId = sha1('referenceId'); + $metaData = ['last_edit_actor_id' => 'admin']; + + $comment + ->setId($id) + ->setParentId($parentId) + ->setTopmostParentId($topMostParentId) + ->setChildrenCount($childrenCount) + ->setMessage($message) + ->setVerb($verb) + ->setActor($actor['type'], $actor['id']) + ->setCreationDateTime($creationDT) + ->setLatestChildDateTime($latestChildDT) + ->setObject($object['type'], $object['id']) + ->setReferenceId($referenceId) + ->setMetaData($metaData); + + $this->assertSame($id, $comment->getId()); + $this->assertSame($parentId, $comment->getParentId()); + $this->assertSame($topMostParentId, $comment->getTopmostParentId()); + $this->assertSame($childrenCount, $comment->getChildrenCount()); + $this->assertSame($message, $comment->getMessage()); + $this->assertSame($verb, $comment->getVerb()); + $this->assertSame($actor['type'], $comment->getActorType()); + $this->assertSame($actor['id'], $comment->getActorId()); + $this->assertSame($creationDT, $comment->getCreationDateTime()); + $this->assertSame($latestChildDT, $comment->getLatestChildDateTime()); + $this->assertSame($object['type'], $comment->getObjectType()); + $this->assertSame($object['id'], $comment->getObjectId()); + $this->assertSame($referenceId, $comment->getReferenceId()); + $this->assertSame($metaData, $comment->getMetaData()); + } + + + public function testSetIdIllegalInput(): void { + $this->expectException(IllegalIDChangeException::class); + + $comment = new Comment(); + + $comment->setId('c23'); + $comment->setId('c17'); + } + + /** + * @throws IllegalIDChangeException + */ + public function testResetId(): void { + $comment = new Comment(); + $comment->setId('c23'); + $comment->setId(''); + + $this->assertSame('', $comment->getId()); + } + + public static function simpleSetterProvider(): array { + return [ + ['Id', true], + ['TopmostParentId', true], + ['ParentId', true], + ['Message', true], + ['Verb', true], + ['Verb', ''], + ['ChildrenCount', true], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('simpleSetterProvider')] + public function testSimpleSetterInvalidInput($field, $input): void { + $this->expectException(\InvalidArgumentException::class); + + $comment = new Comment(); + $setter = 'set' . $field; + + $comment->$setter($input); + } + + public static function roleSetterProvider(): array { + return [ + ['Actor', true, true], + ['Actor', 'users', true], + ['Actor', true, 'alice'], + ['Actor', ' ', ' '], + ['Object', true, true], + ['Object', 'files', true], + ['Object', true, 'file64'], + ['Object', ' ', ' '], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('roleSetterProvider')] + public function testSetRoleInvalidInput($role, $type, $id): void { + $this->expectException(\InvalidArgumentException::class); + + $comment = new Comment(); + $setter = 'set' . $role; + $comment->$setter($type, $id); + } + + + public function testSetUberlongMessage(): void { + $this->expectException(MessageTooLongException::class); + + $comment = new Comment(); + $msg = str_pad('', IComment::MAX_MESSAGE_LENGTH + 1, 'x'); + $comment->setMessage($msg); + } + + public static function mentionsProvider(): array { + return [ + [ + '@alice @bob look look, a cook!', + [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']], + ], + [ + 'no mentions in this message', + [] + ], + [ + '@alice @bob look look, a duplication @alice test @bob!', + [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']], + ], + [ + '@alice is the author, notify @bob, nevertheless mention her!', + [['type' => 'user', 'id' => 'alice'], ['type' => 'user', 'id' => 'bob']], + /* author: */ 'alice' + ], + [ + '@foobar and @barfoo you should know, @foo@bar.com is valid' + . ' and so is @bar@foo.org@foobar.io I hope that clarifies everything.' + . ' cc @23452-4333-54353-2342 @yolo!' + . ' however the most important thing to know is that www.croissant.com/@oil is not valid' + . ' and won\'t match anything at all', + [ + ['type' => 'user', 'id' => 'bar@foo.org@foobar.io'], + ['type' => 'user', 'id' => '23452-4333-54353-2342'], + ['type' => 'user', 'id' => 'foo@bar.com'], + ['type' => 'user', 'id' => 'foobar'], + ['type' => 'user', 'id' => 'barfoo'], + ['type' => 'user', 'id' => 'yolo'], + ], + ], + [ + '@@chef is also a valid mention, no matter how strange it looks', + [['type' => 'user', 'id' => '@chef']], + ], + [ + 'Also @"user with spaces" are now supported', + [['type' => 'user', 'id' => 'user with spaces']], + ], + [ + 'Also @"guest/0123456789abcdef" are now supported', + [['type' => 'guest', 'id' => 'guest/0123456789abcdef']], + ], + [ + 'Also @"group/My Group ID 321" are now supported', + [['type' => 'group', 'id' => 'My Group ID 321']], + ], + [ + 'Welcome federation @"federated_group/My Group ID 321" @"federated_team/Former Cirle" @"federated_user/cloudId@http://example.tld:8080/nextcloud"! Now freshly supported', + [ + ['type' => 'federated_user', 'id' => 'cloudId@http://example.tld:8080/nextcloud'], + ['type' => 'federated_group', 'id' => 'My Group ID 321'], + ['type' => 'federated_team', 'id' => 'Former Cirle'], + ], + ], + [ + 'Emails are supported since 30.0.2 right? @"email/aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886"', + [ + ['type' => 'email', 'id' => 'aa23d315de327cfc330f0401ea061005b2b0cdd45ec8346f12664dd1f34cb886'], + ], + ], + ]; + } + + /** + * + * @param string $message + * @param array $expectedMentions + * @param ?string $author + */ + #[\PHPUnit\Framework\Attributes\DataProvider('mentionsProvider')] + public function testMentions(string $message, array $expectedMentions, ?string $author = null): void { + $comment = new Comment(); + $comment->setMessage($message); + if (!is_null($author)) { + $comment->setActor('user', $author); + } + $mentions = $comment->getMentions(); + $this->assertSame($expectedMentions, $mentions); + } +} diff --git a/tests/lib/Comments/FakeFactory.php b/tests/lib/Comments/FakeFactory.php new file mode 100644 index 00000000000..0c3241078d7 --- /dev/null +++ b/tests/lib/Comments/FakeFactory.php @@ -0,0 +1,23 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace Test\Comments; + +use OCP\Comments\ICommentsManagerFactory; +use OCP\IServerContainer; + +/** + * Class FakeFactory + */ +class FakeFactory implements ICommentsManagerFactory { + public function __construct(IServerContainer $serverContainer) { + } + + public function getManager() { + return new FakeManager(); + } +} diff --git a/tests/lib/Comments/FakeManager.php b/tests/lib/Comments/FakeManager.php new file mode 100644 index 00000000000..15efed5e7dc --- /dev/null +++ b/tests/lib/Comments/FakeManager.php @@ -0,0 +1,155 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace Test\Comments; + +use OC\Comments\Comment; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\IUser; + +/** + * Class FakeManager + */ +class FakeManager implements ICommentsManager { + public function get($id) { + } + + public function getTree($id, $limit = 0, $offset = 0) { + } + + public function getForObject( + $objectType, + $objectId, + $limit = 0, + $offset = 0, + ?\DateTime $notOlderThan = null, + ) { + } + + public function getForObjectSince( + string $objectType, + string $objectId, + int $lastKnownCommentId, + string $sortDirection = 'asc', + int $limit = 30, + bool $includeLastKnown = false, + string $topmostParentId = '', + ): array { + return []; + } + + public function getCommentsWithVerbForObjectSinceComment( + string $objectType, + string $objectId, + array $verbs, + int $lastKnownCommentId, + string $sortDirection = 'asc', + int $limit = 30, + bool $includeLastKnown = false, + string $topmostParentId = '', + ): array { + return []; + } + + public function getNumberOfCommentsForObject($objectType, $objectId, ?\DateTime $notOlderThan = null, $verb = '') { + } + + public function search(string $search, string $objectType, string $objectId, string $verb, int $offset, int $limit = 50): array { + return []; + } + + public function create($actorType, $actorId, $objectType, $objectId) { + } + + public function delete($id) { + } + + public function getReactionComment(int $parentId, string $actorType, string $actorId, string $reaction): IComment { + return new Comment(); + } + + public function retrieveAllReactions(int $parentId): array { + return []; + } + + public function retrieveAllReactionsWithSpecificReaction(int $parentId, string $reaction): array { + return []; + } + + public function supportReactions(): bool { + return false; + } + + public function save(IComment $comment) { + } + + public function deleteReferencesOfActor($actorType, $actorId) { + } + + public function deleteCommentsAtObject($objectType, $objectId) { + } + + public function setReadMark($objectType, $objectId, \DateTime $dateTime, IUser $user) { + } + + public function getReadMark($objectType, $objectId, IUser $user) { + } + + public function deleteReadMarksFromUser(IUser $user) { + } + + public function deleteReadMarksOnObject($objectType, $objectId) { + } + + public function registerEventHandler(\Closure $closure) { + } + + public function registerDisplayNameResolver($type, \Closure $closure) { + } + + public function resolveDisplayName($type, $id) { + } + + public function getNumberOfUnreadCommentsForFolder($folderId, IUser $user) { + } + + public function getNumberOfUnreadCommentsForObjects(string $objectType, array $objectIds, IUser $user, $verb = ''): array { + return []; + } + + + public function getActorsInTree($id) { + } + + public function load(): void { + } + + public function searchForObjects(string $search, string $objectType, array $objectIds, string $verb, int $offset, int $limit = 50): array { + return []; + } + + public function getNumberOfCommentsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, string $verb = ''): int { + return 0; + } + + public function getNumberOfCommentsWithVerbsForObjectSinceComment(string $objectType, string $objectId, int $lastRead, array $verbs): int { + return 0; + } + + public function getLastCommentBeforeDate(string $objectType, string $objectId, \DateTime $beforeDate, string $verb = ''): int { + return 0; + } + + public function getLastCommentDateByActor(string $objectType, string $objectId, string $verb, string $actorType, array $actors): array { + return []; + } + + public function deleteCommentsExpiredAtObject(string $objectType, string $objectId = ''): bool { + return true; + } +} diff --git a/tests/lib/Comments/ManagerTest.php b/tests/lib/Comments/ManagerTest.php new file mode 100644 index 00000000000..bd991a5692a --- /dev/null +++ b/tests/lib/Comments/ManagerTest.php @@ -0,0 +1,2560 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace Test\Comments; + +use OC\Comments\Comment; +use OC\Comments\Manager; +use OC\EmojiHelper; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsEventHandler; +use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IInitialStateService; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Server; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +/** + * Class ManagerTest + * + * @group DB + */ +class ManagerTest extends TestCase { + /** @var IDBConnection */ + private $connection; + /** @var \PHPUnit\Framework\MockObject\MockObject|IRootFolder */ + private $rootFolder; + + protected function setUp(): void { + parent::setUp(); + + $this->connection = Server::get(IDBConnection::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + + $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*comments`'); + $this->connection->prepare($sql)->execute(); + $sql = $this->connection->getDatabasePlatform()->getTruncateTableSQL('`*PREFIX*reactions`'); + $this->connection->prepare($sql)->execute(); + } + + protected function addDatabaseEntry($parentId, $topmostParentId, $creationDT = null, $latestChildDT = null, $objectId = null, $expireDate = null) { + $creationDT ??= new \DateTime(); + $latestChildDT ??= new \DateTime('yesterday'); + $objectId ??= 'file64'; + + $qb = $this->connection->getQueryBuilder(); + $qb + ->insert('comments') + ->values([ + 'parent_id' => $qb->createNamedParameter($parentId), + 'topmost_parent_id' => $qb->createNamedParameter($topmostParentId), + 'children_count' => $qb->createNamedParameter(2), + 'actor_type' => $qb->createNamedParameter('users'), + 'actor_id' => $qb->createNamedParameter('alice'), + 'message' => $qb->createNamedParameter('nice one'), + 'verb' => $qb->createNamedParameter('comment'), + 'creation_timestamp' => $qb->createNamedParameter($creationDT, IQueryBuilder::PARAM_DATETIME_MUTABLE), + 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, IQueryBuilder::PARAM_DATETIME_MUTABLE), + 'object_type' => $qb->createNamedParameter('files'), + 'object_id' => $qb->createNamedParameter($objectId), + 'expire_date' => $qb->createNamedParameter($expireDate, IQueryBuilder::PARAM_DATETIME_MUTABLE), + 'reference_id' => $qb->createNamedParameter('referenceId'), + 'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])), + ]) + ->executeStatement(); + + return $qb->getLastInsertId(); + } + + protected function getManager() { + return new Manager( + $this->connection, + $this->createMock(LoggerInterface::class), + $this->createMock(IConfig::class), + $this->createMock(ITimeFactory::class), + new EmojiHelper($this->connection), + $this->createMock(IInitialStateService::class), + $this->rootFolder, + $this->createMock(IEventDispatcher::class), + ); + } + + + public function testGetCommentNotFound(): void { + $this->expectException(NotFoundException::class); + + $manager = $this->getManager(); + $manager->get('22'); + } + + + public function testGetCommentNotFoundInvalidInput(): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + $manager->get('unexisting22'); + } + + public function testGetComment(): void { + $manager = $this->getManager(); + + $creationDT = new \DateTime('yesterday'); + $latestChildDT = new \DateTime(); + + $qb = Server::get(IDBConnection::class)->getQueryBuilder(); + $qb + ->insert('comments') + ->values([ + 'parent_id' => $qb->createNamedParameter('2'), + 'topmost_parent_id' => $qb->createNamedParameter('1'), + 'children_count' => $qb->createNamedParameter(2), + 'actor_type' => $qb->createNamedParameter('users'), + 'actor_id' => $qb->createNamedParameter('alice'), + 'message' => $qb->createNamedParameter('nice one'), + 'verb' => $qb->createNamedParameter('comment'), + 'creation_timestamp' => $qb->createNamedParameter($creationDT, 'datetime'), + 'latest_child_timestamp' => $qb->createNamedParameter($latestChildDT, 'datetime'), + 'object_type' => $qb->createNamedParameter('files'), + 'object_id' => $qb->createNamedParameter('file64'), + 'reference_id' => $qb->createNamedParameter('referenceId'), + 'meta_data' => $qb->createNamedParameter(json_encode(['last_edit_actor_id' => 'admin'])), + ]) + ->executeStatement(); + + $id = (string)$qb->getLastInsertId(); + + $comment = $manager->get($id); + $this->assertInstanceOf(IComment::class, $comment); + $this->assertSame($id, $comment->getId()); + $this->assertSame('2', $comment->getParentId()); + $this->assertSame('1', $comment->getTopmostParentId()); + $this->assertSame(2, $comment->getChildrenCount()); + $this->assertSame('users', $comment->getActorType()); + $this->assertSame('alice', $comment->getActorId()); + $this->assertSame('nice one', $comment->getMessage()); + $this->assertSame('comment', $comment->getVerb()); + $this->assertSame('files', $comment->getObjectType()); + $this->assertSame('file64', $comment->getObjectId()); + $this->assertEquals($creationDT->getTimestamp(), $comment->getCreationDateTime()->getTimestamp()); + $this->assertEquals($latestChildDT->getTimestamp(), $comment->getLatestChildDateTime()->getTimestamp()); + $this->assertEquals('referenceId', $comment->getReferenceId()); + $this->assertEquals(['last_edit_actor_id' => 'admin'], $comment->getMetaData()); + } + + + public function testGetTreeNotFound(): void { + $this->expectException(NotFoundException::class); + + $manager = $this->getManager(); + $manager->getTree('22'); + } + + + public function testGetTreeNotFoundInvalidIpnut(): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + $manager->getTree('unexisting22'); + } + + public function testGetTree(): void { + $headId = $this->addDatabaseEntry(0, 0); + + $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours')); + $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours')); + $id = $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour')); + + $manager = $this->getManager(); + $tree = $manager->getTree($headId); + + // Verifying the root comment + $this->assertArrayHasKey('comment', $tree); + $this->assertInstanceOf(IComment::class, $tree['comment']); + $this->assertSame((string)$headId, $tree['comment']->getId()); + $this->assertArrayHasKey('replies', $tree); + $this->assertCount(3, $tree['replies']); + + // one level deep + foreach ($tree['replies'] as $reply) { + $this->assertInstanceOf(IComment::class, $reply['comment']); + $this->assertSame((string)$id, $reply['comment']->getId()); + $this->assertCount(0, $reply['replies']); + $id--; + } + } + + public function testGetTreeNoReplies(): void { + $id = $this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + $tree = $manager->getTree($id); + + // Verifying the root comment + $this->assertArrayHasKey('comment', $tree); + $this->assertInstanceOf(IComment::class, $tree['comment']); + $this->assertSame((string)$id, $tree['comment']->getId()); + $this->assertArrayHasKey('replies', $tree); + $this->assertCount(0, $tree['replies']); + } + + public function testGetTreeWithLimitAndOffset(): void { + $headId = $this->addDatabaseEntry(0, 0); + + $this->addDatabaseEntry($headId, $headId, new \DateTime('-3 hours')); + $this->addDatabaseEntry($headId, $headId, new \DateTime('-2 hours')); + $this->addDatabaseEntry($headId, $headId, new \DateTime('-1 hour')); + $idToVerify = $this->addDatabaseEntry($headId, $headId, new \DateTime()); + + $manager = $this->getManager(); + + for ($offset = 0; $offset < 3; $offset += 2) { + $tree = $manager->getTree((string)$headId, 2, $offset); + + // Verifying the root comment + $this->assertArrayHasKey('comment', $tree); + $this->assertInstanceOf(IComment::class, $tree['comment']); + $this->assertSame((string)$headId, $tree['comment']->getId()); + $this->assertArrayHasKey('replies', $tree); + $this->assertCount(2, $tree['replies']); + + // one level deep + foreach ($tree['replies'] as $reply) { + $this->assertInstanceOf(IComment::class, $reply['comment']); + $this->assertSame((string)$idToVerify, $reply['comment']->getId()); + $this->assertCount(0, $reply['replies']); + $idToVerify--; + } + } + } + + public function testGetForObject(): void { + $this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + $comments = $manager->getForObject('files', 'file64'); + + $this->assertIsArray($comments); + $this->assertCount(1, $comments); + $this->assertInstanceOf(IComment::class, $comments[0]); + $this->assertSame('nice one', $comments[0]->getMessage()); + } + + public function testGetForObjectWithLimitAndOffset(): void { + $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours')); + $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours')); + $this->addDatabaseEntry(1, 1, new \DateTime('-4 hours')); + $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours')); + $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours')); + $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours')); + $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime()); + + $manager = $this->getManager(); + $offset = 0; + do { + $comments = $manager->getForObject('files', 'file64', 3, $offset); + + $this->assertIsArray($comments); + foreach ($comments as $key => $comment) { + $this->assertInstanceOf(IComment::class, $comment); + $this->assertSame('nice one', $comment->getMessage()); + $this->assertSame((string)$idToVerify, $comment->getId(), 'ID wrong for comment ' . $key . ' on offset: ' . $offset); + $idToVerify--; + } + $offset += 3; + } while (count($comments) > 0); + } + + public function testGetForObjectWithDateTimeConstraint(): void { + $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours')); + $this->addDatabaseEntry(0, 0, new \DateTime('-5 hours')); + $id1 = $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours')); + $id2 = $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours')); + + $manager = $this->getManager(); + $comments = $manager->getForObject('files', 'file64', 0, 0, new \DateTime('-4 hours')); + + $this->assertCount(2, $comments); + $this->assertSame((string)$id2, $comments[0]->getId()); + $this->assertSame((string)$id1, $comments[1]->getId()); + } + + public function testGetForObjectWithLimitAndOffsetAndDateTimeConstraint(): void { + $this->addDatabaseEntry(0, 0, new \DateTime('-7 hours')); + $this->addDatabaseEntry(0, 0, new \DateTime('-6 hours')); + $this->addDatabaseEntry(1, 1, new \DateTime('-5 hours')); + $this->addDatabaseEntry(0, 0, new \DateTime('-3 hours')); + $this->addDatabaseEntry(2, 2, new \DateTime('-2 hours')); + $this->addDatabaseEntry(2, 2, new \DateTime('-1 hours')); + $idToVerify = $this->addDatabaseEntry(3, 1, new \DateTime()); + + $manager = $this->getManager(); + $offset = 0; + do { + $comments = $manager->getForObject('files', 'file64', 3, $offset, new \DateTime('-4 hours')); + + $this->assertIsArray($comments); + foreach ($comments as $comment) { + $this->assertInstanceOf(IComment::class, $comment); + $this->assertSame('nice one', $comment->getMessage()); + $this->assertSame((string)$idToVerify, $comment->getId()); + $this->assertGreaterThanOrEqual(4, $comment->getId()); + $idToVerify--; + } + $offset += 3; + } while (count($comments) > 0); + } + + public function testGetNumberOfCommentsForObject(): void { + for ($i = 1; $i < 5; $i++) { + $this->addDatabaseEntry(0, 0); + } + + $manager = $this->getManager(); + + $amount = $manager->getNumberOfCommentsForObject('untype', '00'); + $this->assertSame(0, $amount); + + $amount = $manager->getNumberOfCommentsForObject('files', 'file64'); + $this->assertSame(4, $amount); + } + + public function testGetNumberOfUnreadCommentsForFolder(): void { + $folder = $this->createMock(Folder::class); + $fileIds = range(1111, 1114); + $children = array_map(function (int $id) { + $file = $this->createMock(Folder::class); + $file->method('getId') + ->willReturn($id); + return $file; + }, $fileIds); + $folder->method('getId')->willReturn(1000); + $folder->method('getDirectoryListing')->willReturn($children); + $this->rootFolder->method('getFirstNodeById') + ->with($folder->getId()) + ->willReturn($folder); + + // 2 comment for 1111 with 1 before read marker + // 2 comments for 1112 with no read marker + // 1 comment for 1113 before read marker + // 1 comment for 1114 with no read marker + $this->addDatabaseEntry(0, 0, null, null, $fileIds[1]); + for ($i = 0; $i < 4; $i++) { + $this->addDatabaseEntry(0, 0, null, null, $fileIds[$i]); + } + $this->addDatabaseEntry(0, 0, (new \DateTime())->modify('-2 days'), null, $fileIds[0]); + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('comment_test'); + + $manager = $this->getManager(); + + $manager->setReadMark('files', (string)$fileIds[0], (new \DateTime())->modify('-1 days'), $user); + $manager->setReadMark('files', (string)$fileIds[2], (new \DateTime()), $user); + + $amount = $manager->getNumberOfUnreadCommentsForFolder($folder->getId(), $user); + $this->assertEquals([ + $fileIds[0] => 1, + $fileIds[1] => 2, + $fileIds[3] => 1, + ], $amount); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetForObjectSince')] + public function testGetForObjectSince(?int $lastKnown, string $order, int $limit, int $resultFrom, int $resultTo): void { + $ids = []; + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + $comments = $manager->getForObjectSince('files', 'file64', ($lastKnown === null ? 0 : $ids[$lastKnown]), $order, $limit); + + $expected = array_slice($ids, $resultFrom, $resultTo - $resultFrom + 1); + if ($order === 'desc') { + $expected = array_reverse($expected); + } + + $this->assertSame($expected, array_map(static fn (IComment $c): int => (int)$c->getId(), $comments)); + } + + public static function dataGetForObjectSince(): array { + return [ + [null, 'asc', 20, 0, 4], + [null, 'asc', 2, 0, 1], + [null, 'desc', 20, 0, 4], + [null, 'desc', 2, 3, 4], + [1, 'asc', 20, 2, 4], + [1, 'asc', 2, 2, 3], + [3, 'desc', 20, 0, 2], + [3, 'desc', 2, 1, 2], + ]; + } + + public static function invalidCreateArgsProvider(): array { + return [ + ['', 'aId-1', 'oType-1', 'oId-1'], + ['aType-1', '', 'oType-1', 'oId-1'], + ['aType-1', 'aId-1', '', 'oId-1'], + ['aType-1', 'aId-1', 'oType-1', ''], + [1, 'aId-1', 'oType-1', 'oId-1'], + ['aType-1', 1, 'oType-1', 'oId-1'], + ['aType-1', 'aId-1', 1, 'oId-1'], + ['aType-1', 'aId-1', 'oType-1', 1], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('invalidCreateArgsProvider')] + public function testCreateCommentInvalidArguments(string|int $aType, string|int $aId, string|int $oType, string|int $oId): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + $manager->create($aType, $aId, $oType, $oId); + } + + public function testCreateComment(): void { + $actorType = 'bot'; + $actorId = 'bob'; + $objectType = 'weather'; + $objectId = 'bielefeld'; + + $comment = $this->getManager()->create($actorType, $actorId, $objectType, $objectId); + $this->assertInstanceOf(IComment::class, $comment); + $this->assertSame($actorType, $comment->getActorType()); + $this->assertSame($actorId, $comment->getActorId()); + $this->assertSame($objectType, $comment->getObjectType()); + $this->assertSame($objectId, $comment->getObjectId()); + } + + + public function testDelete(): void { + $this->expectException(NotFoundException::class); + + $manager = $this->getManager(); + + $done = $manager->delete('404'); + $this->assertFalse($done); + + $done = $manager->delete('%'); + $this->assertFalse($done); + + $done = $manager->delete(''); + $this->assertFalse($done); + + $id = (string)$this->addDatabaseEntry(0, 0); + $comment = $manager->get($id); + $this->assertInstanceOf(IComment::class, $comment); + $done = $manager->delete($id); + $this->assertTrue($done); + $manager->get($id); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestSave')] + public function testSave(string $message, string $actorId, string $verb, ?string $parentId, ?string $id = ''): IComment { + $manager = $this->getManager(); + $comment = new Comment(); + $comment + ->setId($id) + ->setActor('users', $actorId) + ->setObject('files', 'file64') + ->setMessage($message) + ->setVerb($verb); + if ($parentId) { + $comment->setParentId($parentId); + } + + $saveSuccessful = $manager->save($comment); + $this->assertTrue($saveSuccessful, 'Comment saving was not successful'); + $this->assertNotEquals('', $comment->getId(), 'Comment ID should not be empty'); + $this->assertNotEquals('0', $comment->getId(), 'Comment ID should not be string \'0\''); + $this->assertNotNull($comment->getCreationDateTime(), 'Comment creation date should not be null'); + + $loadedComment = $manager->get($comment->getId()); + $this->assertSame($comment->getMessage(), $loadedComment->getMessage(), 'Comment message should match'); + $this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $loadedComment->getCreationDateTime()->getTimestamp(), 'Comment creation date should match'); + return $comment; + } + + public static function providerTestSave(): array { + return [ + ['very beautiful, I am impressed!', 'alice', 'comment', null], + ]; + } + + public function testSaveUpdate(): void { + $manager = $this->getManager(); + $comment = new Comment(); + $comment + ->setActor('users', 'alice') + ->setObject('files', 'file64') + ->setMessage('very beautiful, I am impressed!') + ->setVerb('comment') + ->setExpireDate(new \DateTime('+2 hours')); + + $manager->save($comment); + + $loadedComment = $manager->get($comment->getId()); + // Compare current object with database values + $this->assertSame($comment->getMessage(), $loadedComment->getMessage()); + $this->assertSame( + $comment->getExpireDate()->format('Y-m-d H:i:s'), + $loadedComment->getExpireDate()->format('Y-m-d H:i:s') + ); + + // Preserve the original comment to compare after update + $original = clone $comment; + + // Update values + $comment->setMessage('very beautiful, I am really so much impressed!') + ->setExpireDate(new \DateTime('+1 hours')); + $manager->save($comment); + + $loadedComment = $manager->get($comment->getId()); + // Compare current object with database values + $this->assertSame($comment->getMessage(), $loadedComment->getMessage()); + $this->assertSame( + $comment->getExpireDate()->format('Y-m-d H:i:s'), + $loadedComment->getExpireDate()->format('Y-m-d H:i:s') + ); + + // Compare original object with database values + $this->assertNotSame($original->getMessage(), $loadedComment->getMessage()); + $this->assertNotSame( + $original->getExpireDate()->format('Y-m-d H:i:s'), + $loadedComment->getExpireDate()->format('Y-m-d H:i:s') + ); + } + + + public function testSaveUpdateException(): void { + $manager = $this->getManager(); + $comment = new Comment(); + $comment + ->setActor('users', 'alice') + ->setObject('files', 'file64') + ->setMessage('very beautiful, I am impressed!') + ->setVerb('comment'); + + $manager->save($comment); + + $manager->delete($comment->getId()); + + $comment->setMessage('very beautiful, I am really so much impressed!'); + $this->expectException(NotFoundException::class); + $manager->save($comment); + } + + + public function testSaveIncomplete(): void { + + $manager = $this->getManager(); + $comment = new Comment(); + $comment->setMessage('from no one to nothing'); + + $this->expectException(\UnexpectedValueException::class); + $manager->save($comment); + } + + public function testSaveAsChild(): void { + $id = (string)$this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + + for ($i = 0; $i < 3; $i++) { + $comment = new Comment(); + $comment + ->setActor('users', 'alice') + ->setObject('files', 'file64') + ->setParentId($id) + ->setMessage('full ack') + ->setVerb('comment') + // setting the creation time avoids using sleep() while making sure to test with different timestamps + ->setCreationDateTime(new \DateTime('+' . $i . ' minutes')); + + $manager->save($comment); + + $this->assertSame($id, $comment->getTopmostParentId()); + $parentComment = $manager->get($id); + $this->assertSame($i + 1, $parentComment->getChildrenCount()); + $this->assertEquals($comment->getCreationDateTime()->getTimestamp(), $parentComment->getLatestChildDateTime()->getTimestamp()); + } + } + + public static function invalidActorArgsProvider(): array { + return + [ + ['', ''], + [1, 'alice'], + ['users', 1], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('invalidActorArgsProvider')] + public function testDeleteReferencesOfActorInvalidInput(string|int $type, string|int $id): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + $manager->deleteReferencesOfActor($type, $id); + } + + public function testDeleteReferencesOfActor(): void { + $ids = []; + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + + // just to make sure they are really set, with correct actor data + $comment = $manager->get((string)$ids[1]); + $this->assertSame('users', $comment->getActorType()); + $this->assertSame('alice', $comment->getActorId()); + + $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice'); + $this->assertTrue($wasSuccessful); + + foreach ($ids as $id) { + $comment = $manager->get((string)$id); + $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType()); + $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId()); + } + + // actor info is gone from DB, but when database interaction is alright, + // we still expect to get true back + $wasSuccessful = $manager->deleteReferencesOfActor('users', 'alice'); + $this->assertTrue($wasSuccessful); + } + + public function testDeleteReferencesOfActorWithUserManagement(): void { + $user = Server::get(IUserManager::class)->createUser('xenia', 'NotAnEasyPassword123456+'); + $this->assertInstanceOf(IUser::class, $user); + + $manager = Server::get(ICommentsManager::class); + $comment = $manager->create('users', $user->getUID(), 'files', 'file64'); + $comment + ->setMessage('Most important comment I ever left on the Internet.') + ->setVerb('comment'); + $status = $manager->save($comment); + $this->assertTrue($status); + + $commentID = $comment->getId(); + $user->delete(); + + $comment = $manager->get($commentID); + $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorType()); + $this->assertSame(ICommentsManager::DELETED_USER, $comment->getActorId()); + } + + public static function invalidObjectArgsProvider(): array { + return + [ + ['', ''], + [1, 'file64'], + ['files', 1], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('invalidObjectArgsProvider')] + public function testDeleteCommentsAtObjectInvalidInput(string|int $type, string|int $id): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + $manager->deleteCommentsAtObject($type, $id); + } + + public function testDeleteCommentsAtObject(): void { + $ids = []; + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + $ids[] = $this->addDatabaseEntry(0, 0); + + $manager = $this->getManager(); + + // just to make sure they are really set, with correct actor data + $comment = $manager->get((string)$ids[1]); + $this->assertSame('files', $comment->getObjectType()); + $this->assertSame('file64', $comment->getObjectId()); + + $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64'); + $this->assertTrue($wasSuccessful); + + $verified = 0; + foreach ($ids as $id) { + try { + $manager->get((string)$id); + } catch (NotFoundException) { + $verified++; + } + } + $this->assertSame(3, $verified); + + // actor info is gone from DB, but when database interaction is alright, + // we still expect to get true back + $wasSuccessful = $manager->deleteCommentsAtObject('files', 'file64'); + $this->assertTrue($wasSuccessful); + } + + public function testDeleteCommentsExpiredAtObjectTypeAndId(): void { + $ids = []; + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('+2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('+2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('+2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('-2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('-2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, null, new \DateTime('-2 hours')); + + $manager = new Manager( + $this->connection, + $this->createMock(LoggerInterface::class), + $this->createMock(IConfig::class), + Server::get(ITimeFactory::class), + new EmojiHelper($this->connection), + $this->createMock(IInitialStateService::class), + $this->rootFolder, + $this->createMock(IEventDispatcher::class) + ); + + // just to make sure they are really set, with correct actor data + $comment = $manager->get((string)$ids[1]); + $this->assertSame('files', $comment->getObjectType()); + $this->assertSame('file64', $comment->getObjectId()); + + $deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64'); + $this->assertTrue($deleted); + + $deleted = 0; + $exists = 0; + foreach ($ids as $id) { + try { + $manager->get((string)$id); + $exists++; + } catch (NotFoundException) { + $deleted++; + } + } + $this->assertSame(3, $exists); + $this->assertSame(3, $deleted); + + // actor info is gone from DB, but when database interaction is alright, + // we still expect to get true back + $deleted = $manager->deleteCommentsExpiredAtObject('files', 'file64'); + $this->assertFalse($deleted); + } + + public function testDeleteCommentsExpiredAtObjectType(): void { + $ids = []; + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file1', new \DateTime('-2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file2', new \DateTime('-2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file3', new \DateTime('-2 hours')); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file3', new \DateTime()); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file3', new \DateTime()); + $ids[] = $this->addDatabaseEntry(0, 0, null, null, 'file3', new \DateTime()); + + $manager = new Manager( + $this->connection, + $this->createMock(LoggerInterface::class), + $this->createMock(IConfig::class), + Server::get(ITimeFactory::class), + new EmojiHelper($this->connection), + $this->createMock(IInitialStateService::class), + $this->rootFolder, + $this->createMock(IEventDispatcher::class) + ); + + $deleted = $manager->deleteCommentsExpiredAtObject('files'); + $this->assertTrue($deleted); + + $deleted = 0; + $exists = 0; + foreach ($ids as $id) { + try { + $manager->get((string)$id); + $exists++; + } catch (NotFoundException) { + $deleted++; + } + } + $this->assertSame(0, $exists); + $this->assertSame(6, $deleted); + + // actor info is gone from DB, but when database interaction is alright, + // we still expect to get true back + $deleted = $manager->deleteCommentsExpiredAtObject('files'); + $this->assertFalse($deleted); + } + + public function testSetMarkRead(): void { + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('alice'); + + $dateTimeSet = new \DateTime(); + + $manager = $this->getManager(); + $manager->setReadMark('robot', '36', $dateTimeSet, $user); + + $dateTimeGet = $manager->getReadMark('robot', '36', $user); + + $this->assertEquals($dateTimeSet->getTimestamp(), $dateTimeGet->getTimestamp()); + } + + public function testSetMarkReadUpdate(): void { + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('alice'); + + $dateTimeSet = new \DateTime('yesterday'); + + $manager = $this->getManager(); + $manager->setReadMark('robot', '36', $dateTimeSet, $user); + + $dateTimeSet = new \DateTime('today'); + $manager->setReadMark('robot', '36', $dateTimeSet, $user); + + $dateTimeGet = $manager->getReadMark('robot', '36', $user); + + $this->assertEquals($dateTimeSet, $dateTimeGet); + } + + public function testReadMarkDeleteUser(): void { + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('alice'); + + $dateTimeSet = new \DateTime(); + + $manager = $this->getManager(); + $manager->setReadMark('robot', '36', $dateTimeSet, $user); + + $manager->deleteReadMarksFromUser($user); + $dateTimeGet = $manager->getReadMark('robot', '36', $user); + + $this->assertNull($dateTimeGet); + } + + public function testReadMarkDeleteObject(): void { + /** @var IUser|\PHPUnit\Framework\MockObject\MockObject $user */ + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('alice'); + + $dateTimeSet = new \DateTime(); + + $manager = $this->getManager(); + $manager->setReadMark('robot', '36', $dateTimeSet, $user); + + $manager->deleteReadMarksOnObject('robot', '36'); + $dateTimeGet = $manager->getReadMark('robot', '36', $user); + + $this->assertNull($dateTimeGet); + } + + public function testSendEvent(): void { + $handler1 = $this->createMock(ICommentsEventHandler::class); + $handler1->expects($this->exactly(4)) + ->method('handle'); + + $handler2 = $this->createMock(ICommentsEventHandler::class); + $handler1->expects($this->exactly(4)) + ->method('handle'); + + $manager = $this->getManager(); + $manager->registerEventHandler(function () use ($handler1) { + return $handler1; + }); + $manager->registerEventHandler(function () use ($handler2) { + return $handler2; + }); + + $comment = new Comment(); + $comment + ->setActor('users', 'alice') + ->setObject('files', 'file64') + ->setMessage('very beautiful, I am impressed!') + ->setVerb('comment'); + + // Add event + $manager->save($comment); + + // Update event + $comment->setMessage('Different topic'); + $manager->save($comment); + + // Delete event + $manager->delete($comment->getId()); + } + + public function testResolveDisplayName(): void { + $manager = $this->getManager(); + + $planetClosure = function ($name) { + return ucfirst($name); + }; + + $galaxyClosure = function ($name) { + return strtoupper($name); + }; + + $manager->registerDisplayNameResolver('planet', $planetClosure); + $manager->registerDisplayNameResolver('galaxy', $galaxyClosure); + + $this->assertSame('Neptune', $manager->resolveDisplayName('planet', 'neptune')); + $this->assertSame('SOMBRERO', $manager->resolveDisplayName('galaxy', 'sombrero')); + } + + + public function testRegisterResolverDuplicate(): void { + $this->expectException(\OutOfBoundsException::class); + + $manager = $this->getManager(); + + $planetClosure = function ($name) { + return ucfirst($name); + }; + $manager->registerDisplayNameResolver('planet', $planetClosure); + $manager->registerDisplayNameResolver('planet', $planetClosure); + } + + + public function testRegisterResolverInvalidType(): void { + $this->expectException(\InvalidArgumentException::class); + + $manager = $this->getManager(); + + $planetClosure = function ($name) { + return ucfirst($name); + }; + $manager->registerDisplayNameResolver(1337, $planetClosure); + } + + + public function testResolveDisplayNameUnregisteredType(): void { + $this->expectException(\OutOfBoundsException::class); + + $manager = $this->getManager(); + + $planetClosure = function ($name) { + return ucfirst($name); + }; + + $manager->registerDisplayNameResolver('planet', $planetClosure); + $manager->resolveDisplayName('galaxy', 'sombrero'); + } + + public function testResolveDisplayNameDirtyResolver(): void { + $manager = $this->getManager(); + + $planetClosure = function () { + return null; + }; + + $manager->registerDisplayNameResolver('planet', $planetClosure); + $this->assertIsString($manager->resolveDisplayName('planet', 'neptune')); + } + + public function testResolveDisplayNameInvalidType(): void { + + $manager = $this->getManager(); + + $planetClosure = function () { + return null; + }; + + $manager->registerDisplayNameResolver('planet', $planetClosure); + $this->expectException(\InvalidArgumentException::class); + $this->assertIsString($manager->resolveDisplayName(1337, 'neptune')); + } + + private function skipIfNotSupport4ByteUTF(): void { + if (!$this->getManager()->supportReactions()) { + $this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8'); + } + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestReactionAddAndDelete')] + public function testReactionAddAndDelete(array $comments, array $reactionsExpected): void { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $processedComments = $this->proccessComments($comments); + $comment = end($processedComments); + if ($comment->getParentId()) { + $parent = $manager->get($comment->getParentId()); + $this->assertEqualsCanonicalizing($reactionsExpected, $parent->getReactions()); + } + } + + public static function providerTestReactionAddAndDelete(): array { + return[ + [ + [ + ['message', 'alice', 'comment', null], + ], [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ], ['๐' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ], ['๐' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ], ['๐' => 2], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction_deleted', 'message#alice'], + ], ['๐' => 1], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction_deleted', 'message#alice'], + ['๐', 'frank', 'reaction_deleted', 'message#alice'], + ], [], + ], + ]; + } + + /** + * @param array $data + * @return array<string, IComment> + */ + private function proccessComments(array $data): array { + $this->connection->beginTransaction(); + /** @var array<string, IComment> $comments */ + $comments = []; + foreach ($data as $comment) { + [$message, $actorId, $verb, $parentText] = $comment; + $parentId = null; + if ($parentText) { + $parentId = (string)$comments[$parentText]->getId(); + } + $id = ''; + if ($verb === 'reaction_deleted') { + $id = $comments[$message . '#' . $actorId]->getId(); + } + $comment = $this->testSave($message, $actorId, $verb, $parentId, $id); + $comments[$comment->getMessage() . '#' . $comment->getActorId()] = $comment; + } + $this->connection->commit(); + return $comments; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestRetrieveAllReactions')] + public function testRetrieveAllReactions(array $comments, array $expected): void { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $processedComments = $this->proccessComments($comments); + $comment = reset($processedComments); + $all = $manager->retrieveAllReactions((int)$comment->getId()); + $actual = array_map(static function (IComment $row): array { + return [ + $row->getActorId(), + $row->getMessage(), + ]; + }, $all); + + usort($actual, static fn (array $a, array $b): int => $a[1] <=> $b[1]); + usort($expected, static fn (array $a, array $b): int => $a[1] <=> $b[1]); + + $this->assertEqualsCanonicalizing($expected, $actual); + } + + public static function providerTestRetrieveAllReactions(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ], + [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ], + [ + ['๐', 'alice'], + ['๐', 'frank'], + ], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ], + [ + ['๐', 'alice'], + ['๐', 'frank'], + ], + ], + [# 600 reactions to cover chunk size when retrieve comments of reactions. + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐
', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐คฃ', 'alice', 'reaction', 'message#alice'], + ['๐ฅฒ', 'alice', 'reaction', 'message#alice'], + ['๐ฅน', 'alice', 'reaction', 'message#alice'], + ['โบ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฅฐ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐คช', 'alice', 'reaction', 'message#alice'], + ['๐คจ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฅธ', 'alice', 'reaction', 'message#alice'], + ['๐คฉ', 'alice', 'reaction', 'message#alice'], + ['๐ฅณ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['โน๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฃ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ', 'alice', 'reaction', 'message#alice'], + ['๐ฅบ', 'alice', 'reaction', 'message#alice'], + ['๐ข', 'alice', 'reaction', 'message#alice'], + ['๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐ฎโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ ', 'alice', 'reaction', 'message#alice'], + ['๐ก', 'alice', 'reaction', 'message#alice'], + ['๐คฌ', 'alice', 'reaction', 'message#alice'], + ['๐คฏ', 'alice', 'reaction', 'message#alice'], + ['๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐ฅต', 'alice', 'reaction', 'message#alice'], + ['๐ฅถ', 'alice', 'reaction', 'message#alice'], + ['๐ฑ', 'alice', 'reaction', 'message#alice'], + ['๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฐ', 'alice', 'reaction', 'message#alice'], + ['๐ฅ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ซฃ', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ซก', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ซข', 'alice', 'reaction', 'message#alice'], + ['๐คญ', 'alice', 'reaction', 'message#alice'], + ['๐คซ', 'alice', 'reaction', 'message#alice'], + ['๐คฅ', 'alice', 'reaction', 'message#alice'], + ['๐ถ', 'alice', 'reaction', 'message#alice'], + ['๐ถโ๐ซ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐ซ ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฏ', 'alice', 'reaction', 'message#alice'], + ['๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฎ', 'alice', 'reaction', 'message#alice'], + ['๐ฒ', 'alice', 'reaction', 'message#alice'], + ['๐ฅฑ', 'alice', 'reaction', 'message#alice'], + ['๐ด', 'alice', 'reaction', 'message#alice'], + ['๐คค', 'alice', 'reaction', 'message#alice'], + ['๐ช', 'alice', 'reaction', 'message#alice'], + ['๐ต', 'alice', 'reaction', 'message#alice'], + ['๐ตโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ซฅ', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ฅด', 'alice', 'reaction', 'message#alice'], + ['๐คข', 'alice', 'reaction', 'message#alice'], + ['๐คฎ', 'alice', 'reaction', 'message#alice'], + ['๐คง', 'alice', 'reaction', 'message#alice'], + ['๐ท', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ค ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฟ', 'alice', 'reaction', 'message#alice'], + ['๐น', 'alice', 'reaction', 'message#alice'], + ['๐บ', 'alice', 'reaction', 'message#alice'], + ['๐คก', 'alice', 'reaction', 'message#alice'], + ['๐ฉ', 'alice', 'reaction', 'message#alice'], + ['๐ป', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['โ ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐พ', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐บ', 'alice', 'reaction', 'message#alice'], + ['๐ธ', 'alice', 'reaction', 'message#alice'], + ['๐น', 'alice', 'reaction', 'message#alice'], + ['๐ป', 'alice', 'reaction', 'message#alice'], + ['๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐ฟ', 'alice', 'reaction', 'message#alice'], + ['๐พ', 'alice', 'reaction', 'message#alice'], + ['๐ถ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐ฑโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฑ', 'alice', 'reaction', 'message#alice'], + ['๐ฑโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ด', 'alice', 'reaction', 'message#alice'], + ['๐ฒ', 'alice', 'reaction', 'message#alice'], + ['๐ณโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐ณโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฎโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฎ', 'alice', 'reaction', 'message#alice'], + ['๐ฎโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ทโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ท', 'alice', 'reaction', 'message#alice'], + ['๐ทโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๏ธโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๏ธโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฐโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฐ', 'alice', 'reaction', 'message#alice'], + ['๐ฐโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คตโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คต', 'alice', 'reaction', 'message#alice'], + ['๐คตโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ธ', 'alice', 'reaction', 'message#alice'], + ['๐ซ
', 'alice', 'reaction', 'message#alice'], + ['๐คด', 'alice', 'reaction', 'message#alice'], + ['๐ฅท', 'alice', 'reaction', 'message#alice'], + ['๐ฆธโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆธโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆนโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆน', 'alice', 'reaction', 'message#alice'], + ['๐ฆนโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คถ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐', 'alice', 'reaction', 'message#alice'], + ['๐
', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐คฐ', 'alice', 'reaction', 'message#alice'], + ['๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐คฑ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐
โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐
', 'alice', 'reaction', 'message#alice'], + ['๐
โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คฆโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คฆ', 'alice', 'reaction', 'message#alice'], + ['๐คฆโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คทโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คท', 'alice', 'reaction', 'message#alice'], + ['๐คทโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐
', 'alice', 'reaction', 'message#alice'], + ['๐คณ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐บ', 'alice', 'reaction', 'message#alice'], + ['๐ฏโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฏ', 'alice', 'reaction', 'message#alice'], + ['๐ฏโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ด', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆฝ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆฝ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆผ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆผ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆผ', 'alice', 'reaction', 'message#alice'], + ['๐ถโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ถ', 'alice', 'reaction', 'message#alice'], + ['๐ถโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆฏ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐ฆฏ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆฏ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐โโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง', 'alice', 'reaction', 'message#alice'], + ['๐งโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐งโ๐คโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโค๏ธโ๐ฉ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐จโโค๏ธโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโค๏ธโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโค๏ธโ๐โ๐ฉ', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐จโโค๏ธโ๐โ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโโค๏ธโ๐โ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ช', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฉโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฉโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฉโ๐งโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฉโ๐ฆโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฉโ๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จโ๐งโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จโ๐ฆโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐จโ๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฉโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฉโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฉโ๐งโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฉโ๐ฆโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฉโ๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ฆโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐งโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐จโ๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ฆโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐งโ๐ฆ', 'alice', 'reaction', 'message#alice'], + ['๐ฉโ๐งโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฃ', 'alice', 'reaction', 'message#alice'], + ['๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ฅ', 'alice', 'reaction', 'message#alice'], + ['๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['โ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['โ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซฐ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซต๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซฑ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซฒ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซณ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซด๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['โ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['โ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ค๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซถ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คฒ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['โ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐
๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คณ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ช๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆต๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆถ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆป๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ถ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ฆฑ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ฆฐ', 'alice', 'reaction', 'message#alice'], + ['๐ฑ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฑ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฑ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ฆณ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ฆฒ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ด๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฒ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ณ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ณ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ณ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฎ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฎ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฎ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ท๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ท๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ท๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ต๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ต๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐พ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ณ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ค', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ซ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ญ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ป', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ง', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ฌ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐จ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฐ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฐ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฐ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คต๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คต๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คต๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ธ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซ
๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คด๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฅท๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆธ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆธ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆธ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆน๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ฆน๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฆน๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คถ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐', 'alice', 'reaction', 'message#alice'], + ['๐
๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ผ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คฐ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ซ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คฑ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐จ๐ฝโ๐ผ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐
๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐
๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐
๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คฆ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คฆ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คฆ๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คท๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐คท๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐คท๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโโ๏ธ', 'alice', 'reaction', 'message#alice'], + ['๐๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐บ๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ด๐ฝ', 'alice', 'reaction', 'message#alice'], + ['๐ฉ๐ฝโ๐ฆฝ', 'alice', 'reaction', 'message#alice'], + ['๐ง๐ฝโ๐ฆฝ', 'alice', 'reaction', 'message#alice'], + ], + [ + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐
', 'alice'], + ['๐', 'alice'], + ['๐คฃ', 'alice'], + ['๐ฅฒ', 'alice'], + ['๐ฅน', 'alice'], + ['โบ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐ฅฐ', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐คช', 'alice'], + ['๐คจ', 'alice'], + ['๐ง', 'alice'], + ['๐ค', 'alice'], + ['๐', 'alice'], + ['๐ฅธ', 'alice'], + ['๐คฉ', 'alice'], + ['๐ฅณ', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['โน๏ธ', 'alice'], + ['๐ฃ', 'alice'], + ['๐', 'alice'], + ['๐ซ', 'alice'], + ['๐ฉ', 'alice'], + ['๐ฅบ', 'alice'], + ['๐ข', 'alice'], + ['๐ญ', 'alice'], + ['๐ฎโ๐จ', 'alice'], + ['๐ค', 'alice'], + ['๐ ', 'alice'], + ['๐ก', 'alice'], + ['๐คฌ', 'alice'], + ['๐คฏ', 'alice'], + ['๐ณ', 'alice'], + ['๐ฅต', 'alice'], + ['๐ฅถ', 'alice'], + ['๐ฑ', 'alice'], + ['๐จ', 'alice'], + ['๐ฐ', 'alice'], + ['๐ฅ', 'alice'], + ['๐', 'alice'], + ['๐ซฃ', 'alice'], + ['๐ค', 'alice'], + ['๐ซก', 'alice'], + ['๐ค', 'alice'], + ['๐ซข', 'alice'], + ['๐คญ', 'alice'], + ['๐คซ', 'alice'], + ['๐คฅ', 'alice'], + ['๐ถ', 'alice'], + ['๐ถโ๐ซ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐', 'alice'], + ['๐ฌ', 'alice'], + ['๐ซ ', 'alice'], + ['๐', 'alice'], + ['๐ฏ', 'alice'], + ['๐ฆ', 'alice'], + ['๐ง', 'alice'], + ['๐ฎ', 'alice'], + ['๐ฒ', 'alice'], + ['๐ฅฑ', 'alice'], + ['๐ด', 'alice'], + ['๐คค', 'alice'], + ['๐ช', 'alice'], + ['๐ต', 'alice'], + ['๐ตโ๐ซ', 'alice'], + ['๐ซฅ', 'alice'], + ['๐ค', 'alice'], + ['๐ฅด', 'alice'], + ['๐คข', 'alice'], + ['๐คฎ', 'alice'], + ['๐คง', 'alice'], + ['๐ท', 'alice'], + ['๐ค', 'alice'], + ['๐ค', 'alice'], + ['๐ค', 'alice'], + ['๐ค ', 'alice'], + ['๐', 'alice'], + ['๐ฟ', 'alice'], + ['๐น', 'alice'], + ['๐บ', 'alice'], + ['๐คก', 'alice'], + ['๐ฉ', 'alice'], + ['๐ป', 'alice'], + ['๐', 'alice'], + ['โ ๏ธ', 'alice'], + ['๐ฝ', 'alice'], + ['๐พ', 'alice'], + ['๐ค', 'alice'], + ['๐', 'alice'], + ['๐บ', 'alice'], + ['๐ธ', 'alice'], + ['๐น', 'alice'], + ['๐ป', 'alice'], + ['๐ผ', 'alice'], + ['๐ฝ', 'alice'], + ['๐', 'alice'], + ['๐ฟ', 'alice'], + ['๐พ', 'alice'], + ['๐ถ', 'alice'], + ['๐ง', 'alice'], + ['๐ง', 'alice'], + ['๐ฆ', 'alice'], + ['๐ฉ', 'alice'], + ['๐ง', 'alice'], + ['๐จ', 'alice'], + ['๐ฉโ๐ฆฑ', 'alice'], + ['๐งโ๐ฆฑ', 'alice'], + ['๐จโ๐ฆฑ', 'alice'], + ['๐ฉโ๐ฆฐ', 'alice'], + ['๐งโ๐ฆฐ', 'alice'], + ['๐จโ๐ฆฐ', 'alice'], + ['๐ฑโโ๏ธ', 'alice'], + ['๐ฑ', 'alice'], + ['๐ฑโโ๏ธ', 'alice'], + ['๐ฉโ๐ฆณ', 'alice'], + ['๐งโ๐ฆณ', 'alice'], + ['๐จโ๐ฆณ', 'alice'], + ['๐ฉโ๐ฆฒ', 'alice'], + ['๐งโ๐ฆฒ', 'alice'], + ['๐จโ๐ฆฒ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ต', 'alice'], + ['๐ง', 'alice'], + ['๐ด', 'alice'], + ['๐ฒ', 'alice'], + ['๐ณโโ๏ธ', 'alice'], + ['๐ณ', 'alice'], + ['๐ณโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐ฎโโ๏ธ', 'alice'], + ['๐ฎ', 'alice'], + ['๐ฎโโ๏ธ', 'alice'], + ['๐ทโโ๏ธ', 'alice'], + ['๐ท', 'alice'], + ['๐ทโโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐ต๏ธโโ๏ธ', 'alice'], + ['๐ต๏ธ', 'alice'], + ['๐ต๏ธโโ๏ธ', 'alice'], + ['๐ฉโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐จโโ๏ธ', 'alice'], + ['๐ฉโ๐พ', 'alice'], + ['๐งโ๐พ', 'alice'], + ['๐จโ๐พ', 'alice'], + ['๐ฉโ๐ณ', 'alice'], + ['๐งโ๐ณ', 'alice'], + ['๐จโ๐ณ', 'alice'], + ['๐ฉโ๐', 'alice'], + ['๐งโ๐', 'alice'], + ['๐จโ๐', 'alice'], + ['๐ฉโ๐ค', 'alice'], + ['๐งโ๐ค', 'alice'], + ['๐จโ๐ค', 'alice'], + ['๐ฉโ๐ซ', 'alice'], + ['๐งโ๐ซ', 'alice'], + ['๐จโ๐ซ', 'alice'], + ['๐ฉโ๐ญ', 'alice'], + ['๐งโ๐ญ', 'alice'], + ['๐จโ๐ญ', 'alice'], + ['๐ฉโ๐ป', 'alice'], + ['๐งโ๐ป', 'alice'], + ['๐จโ๐ป', 'alice'], + ['๐ฉโ๐ผ', 'alice'], + ['๐งโ๐ผ', 'alice'], + ['๐จโ๐ผ', 'alice'], + ['๐ฉโ๐ง', 'alice'], + ['๐งโ๐ง', 'alice'], + ['๐จโ๐ง', 'alice'], + ['๐ฉโ๐ฌ', 'alice'], + ['๐งโ๐ฌ', 'alice'], + ['๐จโ๐ฌ', 'alice'], + ['๐ฉโ๐จ', 'alice'], + ['๐งโ๐จ', 'alice'], + ['๐จโ๐จ', 'alice'], + ['๐ฉโ๐', 'alice'], + ['๐งโ๐', 'alice'], + ['๐จโ๐', 'alice'], + ['๐ฉโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐จโโ๏ธ', 'alice'], + ['๐ฉโ๐', 'alice'], + ['๐งโ๐', 'alice'], + ['๐จโ๐', 'alice'], + ['๐ฉโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐จโโ๏ธ', 'alice'], + ['๐ฐโโ๏ธ', 'alice'], + ['๐ฐ', 'alice'], + ['๐ฐโโ๏ธ', 'alice'], + ['๐คตโโ๏ธ', 'alice'], + ['๐คต', 'alice'], + ['๐คตโโ๏ธ', 'alice'], + ['๐ธ', 'alice'], + ['๐ซ
', 'alice'], + ['๐คด', 'alice'], + ['๐ฅท', 'alice'], + ['๐ฆธโโ๏ธ', 'alice'], + ['๐ฆธ', 'alice'], + ['๐ฆธโโ๏ธ', 'alice'], + ['๐ฆนโโ๏ธ', 'alice'], + ['๐ฆน', 'alice'], + ['๐ฆนโโ๏ธ', 'alice'], + ['๐คถ', 'alice'], + ['๐งโ๐', 'alice'], + ['๐
', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐ผ', 'alice'], + ['๐คฐ', 'alice'], + ['๐ซ', 'alice'], + ['๐ซ', 'alice'], + ['๐คฑ', 'alice'], + ['๐ฉโ๐ผ', 'alice'], + ['๐งโ๐ผ', 'alice'], + ['๐จโ๐ผ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐
โโ๏ธ', 'alice'], + ['๐
', 'alice'], + ['๐
โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐คฆโโ๏ธ', 'alice'], + ['๐คฆ', 'alice'], + ['๐คฆโโ๏ธ', 'alice'], + ['๐คทโโ๏ธ', 'alice'], + ['๐คท', 'alice'], + ['๐คทโโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐
', 'alice'], + ['๐คณ', 'alice'], + ['๐', 'alice'], + ['๐บ', 'alice'], + ['๐ฏโโ๏ธ', 'alice'], + ['๐ฏ', 'alice'], + ['๐ฏโโ๏ธ', 'alice'], + ['๐ด', 'alice'], + ['๐ฉโ๐ฆฝ', 'alice'], + ['๐งโ๐ฆฝ', 'alice'], + ['๐จโ๐ฆฝ', 'alice'], + ['๐ฉโ๐ฆผ', 'alice'], + ['๐งโ๐ฆผ', 'alice'], + ['๐จโ๐ฆผ', 'alice'], + ['๐ถโโ๏ธ', 'alice'], + ['๐ถ', 'alice'], + ['๐ถโโ๏ธ', 'alice'], + ['๐ฉโ๐ฆฏ', 'alice'], + ['๐งโ๐ฆฏ', 'alice'], + ['๐จโ๐ฆฏ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐', 'alice'], + ['๐โโ๏ธ', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ง', 'alice'], + ['๐งโโ๏ธ', 'alice'], + ['๐ญ', 'alice'], + ['๐งโ๐คโ๐ง', 'alice'], + ['๐ฌ', 'alice'], + ['๐ซ', 'alice'], + ['๐ฉโโค๏ธโ๐ฉ', 'alice'], + ['๐', 'alice'], + ['๐จโโค๏ธโ๐จ', 'alice'], + ['๐ฉโโค๏ธโ๐จ', 'alice'], + ['๐ฉโโค๏ธโ๐โ๐ฉ', 'alice'], + ['๐', 'alice'], + ['๐จโโค๏ธโ๐โ๐จ', 'alice'], + ['๐ฉโโค๏ธโ๐โ๐จ', 'alice'], + ['๐ช', 'alice'], + ['๐จโ๐ฉโ๐ฆ', 'alice'], + ['๐จโ๐ฉโ๐ง', 'alice'], + ['๐จโ๐ฉโ๐งโ๐ฆ', 'alice'], + ['๐จโ๐ฉโ๐ฆโ๐ฆ', 'alice'], + ['๐จโ๐ฉโ๐งโ๐ง', 'alice'], + ['๐จโ๐จโ๐ฆ', 'alice'], + ['๐จโ๐จโ๐ง', 'alice'], + ['๐จโ๐จโ๐งโ๐ฆ', 'alice'], + ['๐จโ๐จโ๐ฆโ๐ฆ', 'alice'], + ['๐จโ๐จโ๐งโ๐ง', 'alice'], + ['๐ฉโ๐ฉโ๐ฆ', 'alice'], + ['๐ฉโ๐ฉโ๐ง', 'alice'], + ['๐ฉโ๐ฉโ๐งโ๐ฆ', 'alice'], + ['๐ฉโ๐ฉโ๐ฆโ๐ฆ', 'alice'], + ['๐ฉโ๐ฉโ๐งโ๐ง', 'alice'], + ['๐จโ๐ฆ', 'alice'], + ['๐จโ๐ฆโ๐ฆ', 'alice'], + ['๐จโ๐ง', 'alice'], + ['๐จโ๐งโ๐ฆ', 'alice'], + ['๐จโ๐งโ๐ง', 'alice'], + ['๐ฉโ๐ฆ', 'alice'], + ['๐ฉโ๐ฆโ๐ฆ', 'alice'], + ['๐ฉโ๐ง', 'alice'], + ['๐ฉโ๐งโ๐ฆ', 'alice'], + ['๐ฉโ๐งโ๐ง', 'alice'], + ['๐ฃ', 'alice'], + ['๐ค', 'alice'], + ['๐ฅ', 'alice'], + ['๐ซ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['โ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['โ๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ซฐ๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ซต๐ฝ', 'alice'], + ['๐ซฑ๐ฝ', 'alice'], + ['๐ซฒ๐ฝ', 'alice'], + ['๐ซณ๐ฝ', 'alice'], + ['๐ซด๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['โ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['โ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐ค๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ซถ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐คฒ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['โ๐ฝ', 'alice'], + ['๐
๐ฝ', 'alice'], + ['๐คณ๐ฝ', 'alice'], + ['๐ช๐ฝ', 'alice'], + ['๐ฆต๐ฝ', 'alice'], + ['๐ฆถ๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ฆป๐ฝ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐ถ๐ฝ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ฆ๐ฝ', 'alice'], + ['๐ฉ๐ฝ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐จ๐ฝ', 'alice'], + ['๐ฉ๐ฝโ๐ฆฑ', 'alice'], + ['๐ง๐ฝโ๐ฆฑ', 'alice'], + ['๐จ๐ฝโ๐ฆฑ', 'alice'], + ['๐ฉ๐ฝโ๐ฆฐ', 'alice'], + ['๐ง๐ฝโ๐ฆฐ', 'alice'], + ['๐จ๐ฝโ๐ฆฐ', 'alice'], + ['๐ฑ๐ฝโโ๏ธ', 'alice'], + ['๐ฑ๐ฝ', 'alice'], + ['๐ฑ๐ฝโโ๏ธ', 'alice'], + ['๐ฉ๐ฝโ๐ฆณ', 'alice'], + ['๐ง๐ฝโ๐ฆณ', 'alice'], + ['๐จ๐ฝโ๐ฆณ', 'alice'], + ['๐ฉ๐ฝโ๐ฆฒ', 'alice'], + ['๐ง๐ฝโ๐ฆฒ', 'alice'], + ['๐จ๐ฝโ๐ฆฒ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ต๐ฝ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ด๐ฝ', 'alice'], + ['๐ฒ๐ฝ', 'alice'], + ['๐ณ๐ฝโโ๏ธ', 'alice'], + ['๐ณ๐ฝ', 'alice'], + ['๐ณ๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ฎ๐ฝโโ๏ธ', 'alice'], + ['๐ฎ๐ฝ', 'alice'], + ['๐ฎ๐ฝโโ๏ธ', 'alice'], + ['๐ท๐ฝโโ๏ธ', 'alice'], + ['๐ท๐ฝ', 'alice'], + ['๐ท๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐ต๐ฝโโ๏ธ', 'alice'], + ['๐ต๐ฝ', 'alice'], + ['๐ต๐ฝโโ๏ธ', 'alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐จ๐ฝโโ๏ธ', 'alice'], + ['๐ฉ๐ฝโ๐พ', 'alice'], + ['๐ง๐ฝโ๐พ', 'alice'], + ['๐จ๐ฝโ๐พ', 'alice'], + ['๐ฉ๐ฝโ๐ณ', 'alice'], + ['๐ง๐ฝโ๐ณ', 'alice'], + ['๐จ๐ฝโ๐ณ', 'alice'], + ['๐ฉ๐ฝโ๐', 'alice'], + ['๐ง๐ฝโ๐', 'alice'], + ['๐จ๐ฝโ๐', 'alice'], + ['๐ฉ๐ฝโ๐ค', 'alice'], + ['๐ง๐ฝโ๐ค', 'alice'], + ['๐จ๐ฝโ๐ค', 'alice'], + ['๐ฉ๐ฝโ๐ซ', 'alice'], + ['๐ง๐ฝโ๐ซ', 'alice'], + ['๐จ๐ฝโ๐ซ', 'alice'], + ['๐ฉ๐ฝโ๐ญ', 'alice'], + ['๐ง๐ฝโ๐ญ', 'alice'], + ['๐จ๐ฝโ๐ญ', 'alice'], + ['๐ฉ๐ฝโ๐ป', 'alice'], + ['๐ง๐ฝโ๐ป', 'alice'], + ['๐จ๐ฝโ๐ป', 'alice'], + ['๐ฉ๐ฝโ๐ผ', 'alice'], + ['๐ง๐ฝโ๐ผ', 'alice'], + ['๐จ๐ฝโ๐ผ', 'alice'], + ['๐ฉ๐ฝโ๐ง', 'alice'], + ['๐ง๐ฝโ๐ง', 'alice'], + ['๐จ๐ฝโ๐ง', 'alice'], + ['๐ฉ๐ฝโ๐ฌ', 'alice'], + ['๐ง๐ฝโ๐ฌ', 'alice'], + ['๐จ๐ฝโ๐ฌ', 'alice'], + ['๐ฉ๐ฝโ๐จ', 'alice'], + ['๐ง๐ฝโ๐จ', 'alice'], + ['๐จ๐ฝโ๐จ', 'alice'], + ['๐ฉ๐ฝโ๐', 'alice'], + ['๐ง๐ฝโ๐', 'alice'], + ['๐จ๐ฝโ๐', 'alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐จ๐ฝโโ๏ธ', 'alice'], + ['๐ฉ๐ฝโ๐', 'alice'], + ['๐ง๐ฝโ๐', 'alice'], + ['๐จ๐ฝโ๐', 'alice'], + ['๐ฉ๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐จ๐ฝโโ๏ธ', 'alice'], + ['๐ฐ๐ฝโโ๏ธ', 'alice'], + ['๐ฐ๐ฝ', 'alice'], + ['๐ฐ๐ฝโโ๏ธ', 'alice'], + ['๐คต๐ฝโโ๏ธ', 'alice'], + ['๐คต๐ฝ', 'alice'], + ['๐คต๐ฝโโ๏ธ', 'alice'], + ['๐ธ๐ฝ', 'alice'], + ['๐ซ
๐ฝ', 'alice'], + ['๐คด๐ฝ', 'alice'], + ['๐ฅท๐ฝ', 'alice'], + ['๐ฆธ๐ฝโโ๏ธ', 'alice'], + ['๐ฆธ๐ฝ', 'alice'], + ['๐ฆธ๐ฝโโ๏ธ', 'alice'], + ['๐ฆน๐ฝโโ๏ธ', 'alice'], + ['๐ฆน๐ฝ', 'alice'], + ['๐ฆน๐ฝโโ๏ธ', 'alice'], + ['๐คถ๐ฝ', 'alice'], + ['๐ง๐ฝโ๐', 'alice'], + ['๐
๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ผ๐ฝ', 'alice'], + ['๐คฐ๐ฝ', 'alice'], + ['๐ซ๐ฝ', 'alice'], + ['๐ซ๐ฝ', 'alice'], + ['๐คฑ๐ฝ', 'alice'], + ['๐ฉ๐ฝโ๐ผ', 'alice'], + ['๐ง๐ฝโ๐ผ', 'alice'], + ['๐จ๐ฝโ๐ผ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐
๐ฝโโ๏ธ', 'alice'], + ['๐
๐ฝ', 'alice'], + ['๐
๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐คฆ๐ฝโโ๏ธ', 'alice'], + ['๐คฆ๐ฝ', 'alice'], + ['๐คฆ๐ฝโโ๏ธ', 'alice'], + ['๐คท๐ฝโโ๏ธ', 'alice'], + ['๐คท๐ฝ', 'alice'], + ['๐คท๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐ง๐ฝ', 'alice'], + ['๐ง๐ฝโโ๏ธ', 'alice'], + ['๐๐ฝ', 'alice'], + ['๐บ๐ฝ', 'alice'], + ['๐ด๐ฝ', 'alice'], + ['๐ฉ๐ฝโ๐ฆฝ', 'alice'], + ['๐ง๐ฝโ๐ฆฝ', 'alice'], + ], + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestRetrieveAllReactionsWithSpecificReaction')] + public function testRetrieveAllReactionsWithSpecificReaction(array $comments, string $reaction, array $expected): void { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $processedComments = $this->proccessComments($comments); + $comment = reset($processedComments); + $all = $manager->retrieveAllReactionsWithSpecificReaction((int)$comment->getId(), $reaction); + $actual = array_map(static function (IComment $row): array { + return [ + $row->getActorId(), + $row->getMessage(), + ]; + }, $all); + $this->assertEqualsCanonicalizing($expected, $actual); + } + + public static function providerTestRetrieveAllReactionsWithSpecificReaction(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ], + '๐', + [], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ], + '๐', + [ + ['๐', 'alice'], + ['๐', 'frank'], + ], + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'alice', 'reaction', 'message#alice'], + ['๐', 'frank', 'reaction', 'message#alice'], + ], + '๐', + [ + ['๐', 'alice'], + ], + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestGetReactionComment')] + public function testGetReactionComment(array $comments, array $expected, bool $notFound): void { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + $processedComments = $this->proccessComments($comments); + + $keys = ['message', 'actorId', 'verb', 'parent']; + $expected = array_combine($keys, $expected); + + if ($notFound) { + $this->expectException(NotFoundException::class); + } + $comment = $processedComments[$expected['message'] . '#' . $expected['actorId']]; + $actual = $manager->getReactionComment((int)$comment->getParentId(), $comment->getActorType(), $comment->getActorId(), $comment->getMessage()); + if (!$notFound) { + $this->assertEquals($expected['message'], $actual->getMessage()); + $this->assertEquals($expected['actorId'], $actual->getActorId()); + $this->assertEquals($expected['verb'], $actual->getVerb()); + $this->assertEquals($processedComments[$expected['parent']]->getId(), $actual->getParentId()); + } + } + + public static function providerTestGetReactionComment(): array { + return [ + [ + [ + ['message', 'Matthew', 'comment', null], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + ['๐', 'Mark', 'reaction', 'message#Matthew'], + ['๐', 'Luke', 'reaction', 'message#Matthew'], + ['๐', 'John', 'reaction', 'message#Matthew'], + ], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + false, + ], + [ + [ + ['message', 'Matthew', 'comment', null], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + ['๐', 'Mark', 'reaction', 'message#Matthew'], + ['๐', 'Luke', 'reaction', 'message#Matthew'], + ['๐', 'John', 'reaction', 'message#Matthew'], + ], + ['๐', 'Mark', 'reaction', 'message#Matthew'], + false, + ], + [ + [ + ['message', 'Matthew', 'comment', null], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + ], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + false, + ], + [ + [ + ['message', 'Matthew', 'comment', null], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + ['๐', 'Matthew', 'reaction_deleted', 'message#Matthew'], + ], + ['๐', 'Matthew', 'reaction', 'message#Matthew'], + true, + ], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestReactionMessageSize')] + public function testReactionMessageSize(string $reactionString, bool $valid): void { + $this->skipIfNotSupport4ByteUTF(); + if (!$valid) { + $this->expectException(\UnexpectedValueException::class); + } + + $manager = $this->getManager(); + $comment = new Comment(); + $comment->setMessage($reactionString) + ->setVerb('reaction') + ->setActor('users', 'alice') + ->setObject('files', 'file64'); + $status = $manager->save($comment); + $this->assertTrue($status); + } + + public static function providerTestReactionMessageSize(): array { + return [ + ['a', false], + ['1', false], + ['๐', true], + ['๐๐', false], + ['๐๐ฝ', true], + ['๐จ๐ฝโ๐ป', true], + ['๐จ๐ฝโ๐ป๐', false], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('providerTestReactionsSummarizeOrdered')] + public function testReactionsSummarizeOrdered(array $comments, array $expected, bool $isFullMatch): void { + $this->skipIfNotSupport4ByteUTF(); + $manager = $this->getManager(); + + + $processedComments = $this->proccessComments($comments); + $comment = end($processedComments); + $actual = $manager->get($comment->getParentId()); + + if ($isFullMatch) { + $this->assertSame($expected, $actual->getReactions()); + } else { + $subResult = array_slice($actual->getReactions(), 0, count($expected)); + $this->assertSame($expected, $subResult); + } + } + + public static function providerTestReactionsSummarizeOrdered(): array { + return [ + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'alice', 'reaction', 'message#alice'], + ], + ['๐' => 1], + true, + ], + [ + [ + ['message', 'alice', 'comment', null], + ['๐', 'John', 'reaction', 'message#alice'], + ['๐ผ', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐ค', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐ฅ', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['โ', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ['๐ธ', 'Luke', 'reaction', 'message#alice'], + ['๐ฐ', 'Luke', 'reaction', 'message#alice'], + ['โ๏ธ', 'Luke', 'reaction', 'message#alice'], + ['๐จ', 'Luke', 'reaction', 'message#alice'], + ['๐ฅ', 'Luke', 'reaction', 'message#alice'], + ['๐', 'Paul', 'reaction', 'message#alice'], + ['๐', 'Peter', 'reaction', 'message#alice'], + ['๐', 'Matthew', 'reaction', 'message#alice'], + ['๐', 'Mark', 'reaction', 'message#alice'], + ['๐', 'Luke', 'reaction', 'message#alice'], + ], + [ + '๐' => 3, + '๐' => 2, + ], + false, + ], + ]; + } +} |