diff --git a/cmake/Testing.cmake b/cmake/Testing.cmake index f36e7b2ae..a1cc0976b 100644 --- a/cmake/Testing.cmake +++ b/cmake/Testing.cmake @@ -53,6 +53,7 @@ auto_test(persistence offlinemsgengine "") if(NOT "${SMILEYS}" STREQUAL "DISABLED") auto_test(persistence smileypack "${${PROJECT_NAME}_RESOURCES}") # needs emojione endif() +auto_test(model friendlistmanager "") auto_test(model friendmessagedispatcher "") auto_test(model groupmessagedispatcher "${MOCK_SOURCES}") auto_test(model messageprocessor "") diff --git a/src/model/friendlist/friendlistmanager.cpp b/src/model/friendlist/friendlistmanager.cpp index c89275ff7..0be8ba23d 100644 --- a/src/model/friendlist/friendlistmanager.cpp +++ b/src/model/friendlist/friendlistmanager.cpp @@ -42,7 +42,7 @@ bool FriendListManager::getPositionsChanged() const void FriendListManager::addFriendListItem(IFriendListItem *item) { - if (item->isGroup()) { + if (item->isGroup() && item->getWidget() != nullptr) { items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){ groupItem->getWidget()->deleteLater();})); } else { @@ -102,24 +102,24 @@ void FriendListManager::applyFilter() for (IFriendListItemPtr itemTmp : items) { if (searchString.isEmpty()) { - itemTmp->getWidget()->setVisible(true); + itemTmp->setWidgetVisible(true); } else { QString tmp_name = itemTmp->getNameItem(); - itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive)); + itemTmp->setWidgetVisible(tmp_name.contains(searchString, Qt::CaseInsensitive)); } if (filterParams.hideOnline && itemTmp->isOnline()) { if (itemTmp->isFriend()) { - itemTmp->getWidget()->setVisible(false); + itemTmp->setWidgetVisible(false); } } if (filterParams.hideOffline && !itemTmp->isOnline()) { - itemTmp->getWidget()->setVisible(false); + itemTmp->setWidgetVisible(false); } if (filterParams.hideGroups && itemTmp->isGroup()) { - itemTmp->getWidget()->setVisible(false); + itemTmp->setWidgetVisible(false); } } @@ -144,7 +144,7 @@ void FriendListManager::updatePositions() return; } } - std::sort(items.begin(), items.end(),sortName); + std::sort(items.begin(), items.end(), sortName); } else { auto sortActivity = [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) { diff --git a/src/model/friendlist/ifriendlistitem.h b/src/model/friendlist/ifriendlistitem.h index 6acff2781..b9477caf9 100644 --- a/src/model/friendlist/ifriendlistitem.h +++ b/src/model/friendlist/ifriendlistitem.h @@ -36,9 +36,11 @@ public: virtual bool isFriend() const = 0; virtual bool isGroup() const = 0; virtual bool isOnline() const = 0; + virtual bool widgetIsVisible() const = 0; virtual QString getNameItem() const = 0; virtual QDateTime getLastActivity() const = 0; virtual QWidget* getWidget() = 0; + virtual void setWidgetVisible(bool) = 0; virtual int getCircleId() const { diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 068d39a55..5e982d480 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -370,6 +370,11 @@ bool FriendWidget::isOnline() const return Status::isOnline(frnd->getStatus()); } +bool FriendWidget::widgetIsVisible() const +{ + return isVisible(); +} + QString FriendWidget::getNameItem() const { return nameLabel->fullText(); @@ -386,6 +391,11 @@ QWidget *FriendWidget::getWidget() return this; } +void FriendWidget::setWidgetVisible(bool visible) +{ + setVisible(visible); +} + int FriendWidget::getCircleId() const { return chatroom->getCircleId(); diff --git a/src/widget/friendwidget.h b/src/widget/friendwidget.h index e6b1f43b1..85b16541b 100644 --- a/src/widget/friendwidget.h +++ b/src/widget/friendwidget.h @@ -48,10 +48,12 @@ public: bool isFriend() const final; bool isGroup() const final; bool isOnline() const final; + bool widgetIsVisible() const final; QString getNameItem() const final; QDateTime getLastActivity() const final; int getCircleId() const final; QWidget* getWidget() final; + void setWidgetVisible(bool visible) final; signals: void friendWidgetClicked(FriendWidget* widget); diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index f0bb961aa..bdb44d89a 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -209,6 +209,11 @@ bool GroupWidget::isOnline() const return true; } +bool GroupWidget::widgetIsVisible() const +{ + return isVisible(); +} + QDateTime GroupWidget::getLastActivity() const { return QDateTime::currentDateTime(); @@ -219,6 +224,11 @@ QWidget *GroupWidget::getWidget() return this; } +void GroupWidget::setWidgetVisible(bool visible) +{ + setVisible(visible); +} + // TODO: Remove Group* GroupWidget::getGroup() const { diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index 2d9dfbdb9..8e80ebc3e 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -47,8 +47,10 @@ public: bool isGroup() const final; QString getNameItem() const final; bool isOnline() const final; + bool widgetIsVisible() const final; QDateTime getLastActivity() const final; QWidget* getWidget() final; + void setWidgetVisible(bool visible) final; signals: void groupWidgetClicked(GroupWidget* widget); diff --git a/test/model/friendlistmanager_test.cpp b/test/model/friendlistmanager_test.cpp new file mode 100644 index 000000000..409d1a266 --- /dev/null +++ b/test/model/friendlistmanager_test.cpp @@ -0,0 +1,540 @@ +/* + Copyright © 2022 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + qTox is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#include "src/model/friendlist/friendlistmanager.h" + +#include +#include + +class MockFriend : public IFriendListItem +{ +public: + MockFriend() + : name("No Name"), + lastActivity(QDateTime::currentDateTime()), + online(false) {} + + MockFriend(const QString& nameStr, bool onlineRes, const QDateTime& lastAct) + : name(nameStr), + lastActivity(lastAct), + online(onlineRes) {} + + ~MockFriend(); + + bool isFriend() const override { return true; } + bool isGroup() const override { return false; } + bool isOnline() const override { return online; } + bool widgetIsVisible() const override { return visible; } + + QString getNameItem() const override { return name; } + QDateTime getLastActivity() const override { return lastActivity; } + QWidget* getWidget() override { return nullptr; } + + void setWidgetVisible(bool v) override { visible = v; } + +private: + QString name; + QDateTime lastActivity; + bool online = false; + bool visible = true; + +}; + +MockFriend::~MockFriend() = default; + +class MockGroup : public IFriendListItem +{ +public: + MockGroup() + : name("group") {} + + MockGroup(const QString& nameStr) + :name(nameStr) {} + + ~MockGroup(); + + bool isFriend() const override { return false; } + bool isGroup() const override { return true; } + bool isOnline() const override { return true; } + bool widgetIsVisible() const override { return visible; } + + QString getNameItem() const override { return name; } + QDateTime getLastActivity() const override { return QDateTime::currentDateTime(); } + QWidget* getWidget() override { return nullptr; } + + void setWidgetVisible(bool v) override { visible = v; } + +private: + QString name; + bool visible = true; +}; + +MockGroup::~MockGroup() = default; + +class FriendItemsBuilder +{ +public: + FriendItemsBuilder* addOfflineFriends() + { + QStringList testNames {".test", "123", "A test user", "Aatest user", "atest", + "btest", "ctest", "Test user", "user with long nickname one", + "user with long nickname two"}; + + for (int i = 0; i < testNames.size(); ++i) { + int unsortedIndex = i % 2 ? i - 1 : testNames.size() - i - 1; // Mixes positions + int sortedByActivityIndex = testNames.size() - i - 1; + unsortedAllFriends.append(testNames[unsortedIndex]); + sortedByNameOfflineFriends.append(testNames[i]); + sortedByActivityFriends.append(testNames[sortedByActivityIndex]); + } + + return this; + } + + FriendItemsBuilder* addOnlineFriends() + { + QStringList testNames {".test online", "123 online", "A test user online", + "Aatest user online", "atest online", "btest online", "ctest online", + "Test user online", "user with long nickname one online", + "user with long nickname two online"}; + + for (int i = 0; i < testNames.size(); ++i) { + int unsortedIndex = i % 2 ? i - 1 : testNames.size() - i - 1; + int sortedByActivityIndex = testNames.size() - i - 1; + unsortedAllFriends.append(testNames[unsortedIndex]); + sortedByNameOnlineFriends.append(testNames[i]); + sortedByActivityFriends.append(testNames[sortedByActivityIndex]); + } + + return this; + } + + FriendItemsBuilder* addGroups() + { + unsortedGroups.append("Test Group"); + unsortedGroups.append("A Group"); + unsortedGroups.append("Test Group long name"); + unsortedGroups.append("Test Group long aname"); + unsortedGroups.append("123"); + + sortedByNameGroups.push_back("123"); + sortedByNameGroups.push_back("A Group"); + sortedByNameGroups.push_back("Test Group"); + sortedByNameGroups.push_back("Test Group long aname"); + sortedByNameGroups.push_back("Test Group long name"); + + return this; + } + + FriendItemsBuilder* setGroupsOnTop(bool val) + { + groupsOnTop = val; + return this; + } + + /** + * @brief buildUnsorted Creates items to init the FriendListManager. + * FriendListManager will own and manage these items + * @return Unsorted vector of items + */ + QVector buildUnsorted() + { + checkDifferentNames(); + + QVector vec; + for (auto name : unsortedAllFriends) { + vec.push_back(new MockFriend(name, isOnline(name), getDateTime(name))); + } + for (auto name : unsortedGroups) { + vec.push_back(new MockGroup(name)); + } + clear(); + return vec; + } + + /** + * @brief buildSortedByName Create items to compare with items + * from the FriendListManager. FriendItemsBuilder owns these items + * @return Sorted by name vector of items + */ + QVector> buildSortedByName() + { + QVector> vec; + if (!groupsOnTop) { + for (auto name : sortedByNameOnlineFriends) { + vec.push_back(std::shared_ptr(new MockFriend(name, true, QDateTime::currentDateTime()))); + } + + for (auto name : sortedByNameGroups) { + vec.push_back(std::shared_ptr(new MockGroup(name))); + } + } else { + for (auto name : sortedByNameGroups) { + vec.push_back(std::shared_ptr(new MockGroup(name))); + } + + for (auto name : sortedByNameOnlineFriends) { + vec.push_back(std::shared_ptr(new MockFriend(name, true, QDateTime::currentDateTime()))); + } + } + + for (auto name : sortedByNameOfflineFriends) { + vec.push_back(std::shared_ptr(new MockFriend(name, false, getDateTime(name)))); + } + clear(); + return vec; + } + + /** + * @brief buildSortedByActivity Creates items to compare with items + * from FriendListManager. FriendItemsBuilder owns these items + * @return Sorted by activity vector of items + */ + QVector> buildSortedByActivity() + { + QVector> vec; + + // Add groups on top + for (auto name : sortedByNameGroups) { + vec.push_back(std::shared_ptr(new MockGroup(name))); + } + + // Add friends and set the date of the last activity by index + QDateTime dateTime = QDateTime::currentDateTime(); + for (int i = 0; i < sortedByActivityFriends.size(); ++i) { + QString name = sortedByActivityFriends.at(i); + vec.push_back(std::shared_ptr(new MockFriend(name, isOnline(name), getDateTime(name)))); + } + clear(); + return vec; + } + +private: + void clear() + { + sortedByNameOfflineFriends.clear(); + sortedByNameOnlineFriends.clear(); + sortedByNameGroups.clear(); + sortedByActivityFriends.clear(); + sortedByActivityGroups.clear(); + unsortedAllFriends.clear(); + unsortedGroups.clear(); + groupsOnTop = true; + } + + bool isOnline(const QString& name) + { + return sortedByNameOnlineFriends.indexOf(name) != -1; + } + + /** + * @brief checkDifferentNames The check is necessary for + * the correct setting of the online status + */ + void checkDifferentNames() { + for (auto name : sortedByNameOnlineFriends) { + if (sortedByNameOfflineFriends.contains(name, Qt::CaseInsensitive)) { + QFAIL("Names in sortedByNameOnlineFriends and sortedByNameOfflineFriends " + "should be different"); + break; + } + } + } + + QDateTime getDateTime(const QString& name) + { + QDateTime dateTime = QDateTime::currentDateTime(); + int pos = sortedByActivityFriends.indexOf(name); + if (pos == -1) { + return dateTime; + } + const int dayRatio = -1; + return dateTime.addDays(dayRatio * pos * pos); + } + + QStringList sortedByNameOfflineFriends; + QStringList sortedByNameOnlineFriends; + QStringList sortedByNameGroups; + QStringList sortedByActivityFriends; + QStringList sortedByActivityGroups; + QStringList unsortedAllFriends; + QStringList unsortedGroups; + bool groupsOnTop = true; +}; + +class TestFriendListManager : public QObject +{ + Q_OBJECT +private slots: + void testAddFriendListItem(); + void testSortByName(); + void testSortByActivity(); + void testSetFilter(); + void testApplyFilterSearchString(); + void testApplyFilterByStatus(); + void testSetGroupsOnTop(); +private: + std::unique_ptr createManagerWithItems( + const QVector itemsVec); +}; + +void TestFriendListManager::testAddFriendListItem() +{ + auto manager = std::unique_ptr(new FriendListManager(0, this)); + QSignalSpy spy(manager.get(), &FriendListManager::itemsChanged); + FriendItemsBuilder listBuilder; + + auto checkFunc = [&](const QVector itemsVec) { + for (auto item : itemsVec) { + manager->addFriendListItem(item); + } + QCOMPARE(manager->getItems().size(), itemsVec.size()); + QCOMPARE(spy.count(), itemsVec.size()); + spy.clear(); + for (auto item : itemsVec) { + manager->removeFriendListItem(item); + } + QCOMPARE(manager->getItems().size(), 0); + QCOMPARE(spy.count(), itemsVec.size()); + spy.clear(); + }; + + // Only friends + checkFunc(listBuilder.addOfflineFriends()->buildUnsorted()); + checkFunc(listBuilder.addOfflineFriends()->addOnlineFriends()->buildUnsorted()); + // Friends and groups + checkFunc(listBuilder.addOfflineFriends()->addGroups()->buildUnsorted()); + checkFunc(listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups()->buildUnsorted()); + // Only groups + checkFunc(listBuilder.addGroups()->buildUnsorted()); +} + +void TestFriendListManager::testSortByName() +{ + FriendItemsBuilder listBuilder; + auto unsortedVec = listBuilder.addOfflineFriends() + ->addOnlineFriends()->addGroups()->buildUnsorted(); + auto sortedVec = listBuilder.addOfflineFriends() + ->addOnlineFriends()->addGroups()->buildSortedByName(); + auto manager = createManagerWithItems(unsortedVec); + + manager->sortByName(); + bool success = manager->getPositionsChanged(); + manager->sortByName(); + + QCOMPARE(success, true); + QCOMPARE(manager->getPositionsChanged(), false); + QCOMPARE(manager->getItems().size(), sortedVec.size()); + + for (int i = 0; i < sortedVec.size(); ++i) { + IFriendListItem* fromManager = manager->getItems().at(i).get(); + std::shared_ptr fromSortedVec = sortedVec.at(i); + QCOMPARE(fromManager->getNameItem(), fromSortedVec->getNameItem()); + } +} + +void TestFriendListManager::testSortByActivity() +{ + FriendItemsBuilder listBuilder; + auto unsortedVec = listBuilder.addOfflineFriends() + ->addOnlineFriends()->addGroups()->buildUnsorted(); + auto sortedVec = listBuilder.addOfflineFriends() + ->addOnlineFriends()->addGroups()->buildSortedByActivity(); + + std::unique_ptr manager = createManagerWithItems(unsortedVec); + manager->sortByActivity(); + bool success = manager->getPositionsChanged(); + manager->sortByActivity(); + + QCOMPARE(success, true); + QCOMPARE(manager->getPositionsChanged(), false); + QCOMPARE(manager->getItems().size(), sortedVec.size()); + for (int i = 0; i < sortedVec.size(); ++i) { + auto fromManager = manager->getItems().at(i).get(); + auto fromSortedVec = sortedVec.at(i); + QCOMPARE(fromManager->getNameItem(), fromSortedVec->getNameItem()); + } +} + +void TestFriendListManager::testSetFilter() +{ + FriendItemsBuilder listBuilder; + auto manager = createManagerWithItems( + listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups()->buildUnsorted()); + QSignalSpy spy(manager.get(), &FriendListManager::itemsChanged); + + manager->setFilter("", false, false, false); + + QCOMPARE(spy.count(), 0); + + manager->setFilter("Test", true, false, false); + manager->setFilter("Test", true, false, false); + + QCOMPARE(spy.count(), 1); +} + +void TestFriendListManager::testApplyFilterSearchString() +{ + FriendItemsBuilder listBuilder; + auto manager = createManagerWithItems( + listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups()->buildUnsorted()); + QVector> resultVec; + QString testNameA = "NOITEMSWITHTHISNAME"; + QString testNameB = "Test Name B"; + manager->sortByName(); + manager->setFilter(testNameA, false, false, false); + manager->applyFilter(); + + resultVec = manager->getItems(); + for (auto item : resultVec) { + QCOMPARE(item->widgetIsVisible(), false); + } + + manager->sortByActivity(); + manager->addFriendListItem(new MockFriend(testNameB, true, QDateTime::currentDateTime())); + manager->applyFilter(); + + resultVec = manager->getItems(); + for (auto item : resultVec) { + QCOMPARE(item->widgetIsVisible(), false); + } + + manager->addFriendListItem(new MockFriend(testNameA, true, QDateTime::currentDateTime())); + manager->applyFilter(); + + resultVec = manager->getItems(); + for (auto item : resultVec) { + if (item->getNameItem() == testNameA) { + QCOMPARE(item->widgetIsVisible(), true); + } else { + QCOMPARE(item->widgetIsVisible(), false); + } + } + + manager->setFilter("", false, false, false); + manager->applyFilter(); + + resultVec = manager->getItems(); + for (auto item : resultVec) { + QCOMPARE(item->widgetIsVisible(), true); + } +} + +void TestFriendListManager::testApplyFilterByStatus() +{ + FriendItemsBuilder listBuilder; + auto manager = createManagerWithItems( + listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups()->buildUnsorted()); + auto onlineItems = listBuilder.addOnlineFriends()->buildSortedByName(); + auto offlineItems = listBuilder.addOfflineFriends()->buildSortedByName(); + auto groupItems = listBuilder.addGroups()->buildSortedByName(); + manager->sortByName(); + + manager->setFilter("", true /*hideOnline*/, false /*hideOffline*/, false /*hideGroups*/); + manager->applyFilter(); + + for (auto item : manager->getItems()) { + if (item->isOnline() && item->isFriend()) { + QCOMPARE(item->widgetIsVisible(), false); + } else { + QCOMPARE(item->widgetIsVisible(), true); + } + } + + manager->setFilter("", false /*hideOnline*/, true /*hideOffline*/, false /*hideGroups*/); + manager->applyFilter(); + + for (auto item : manager->getItems()) { + if (item->isOnline()) { + QCOMPARE(item->widgetIsVisible(), true); + } else { + QCOMPARE(item->widgetIsVisible(), false); + } + } + + manager->setFilter("", false /*hideOnline*/, false /*hideOffline*/, true /*hideGroups*/); + manager->applyFilter(); + + for (auto item : manager->getItems()) { + if (item->isGroup()) { + QCOMPARE(item->widgetIsVisible(), false); + } else { + QCOMPARE(item->widgetIsVisible(), true); + } + } + + manager->setFilter("", true /*hideOnline*/, true /*hideOffline*/, true /*hideGroups*/); + manager->applyFilter(); + + for (auto item : manager->getItems()) { + QCOMPARE(item->widgetIsVisible(), false); + } + + manager->setFilter("", false /*hideOnline*/, false /*hideOffline*/, false /*hideGroups*/); + manager->applyFilter(); + + for (auto item : manager->getItems()) { + QCOMPARE(item->widgetIsVisible(), true); + } +} + +void TestFriendListManager::testSetGroupsOnTop() +{ + FriendItemsBuilder listBuilder; + auto manager = createManagerWithItems( + listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups()->buildUnsorted()); + auto sortedVecOnlineOnTop = listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups() + ->setGroupsOnTop(false)->buildSortedByName(); + auto sortedVecGroupsOnTop = listBuilder.addOfflineFriends()->addOnlineFriends()->addGroups() + ->setGroupsOnTop(true)->buildSortedByName(); + + manager->setGroupsOnTop(false); + manager->sortByName(); + + for (int i = 0; i < manager->getItems().size(); ++i) { + auto fromManager = manager->getItems().at(i); + auto fromSortedVec = sortedVecOnlineOnTop.at(i); + QCOMPARE(fromManager->getNameItem(), fromSortedVec->getNameItem()); + } + + manager->setGroupsOnTop(true); + manager->sortByName(); + + for (int i = 0; i < manager->getItems().size(); ++i) { + auto fromManager = manager->getItems().at(i); + auto fromSortedVec = sortedVecGroupsOnTop.at(i); + QCOMPARE(fromManager->getNameItem(), fromSortedVec->getNameItem()); + } +} + +std::unique_ptr TestFriendListManager::createManagerWithItems( + const QVector itemsVec) +{ + std::unique_ptr manager = + std::unique_ptr(new FriendListManager(0, this)); + + for (auto item : itemsVec) { + manager->addFriendListItem(item); + } + + return manager; +} + +QTEST_GUILESS_MAIN(TestFriendListManager); +#include "friendlistmanager_test.moc"