/* Настоящие Qt-меню, БД ядра и bridge: обновления списка и отмена его команд. */ #include "../src/channellist.h" #include "../src/channeldelegate.h" #include "../db/db_manager.h" #include "../transport/gui_bridge.h" #include #include #include #include extern "C" { #include "utun_instance.h" #include "chat/chat_core.h" #include "dm/dm_core.h" #include "mem.h" #include "debug_config.h" } struct ListCore { UASYNC* ua = uasync_create(); UTUN_INSTANCE* inst = nullptr; DbManager db; QString channel; ~ListCore() { gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr); if (ua) uasync_mark_stopped(ua); db.setDb(nullptr); if (inst) utun_instance_destroy(inst); if (ua) uasync_destroy(ua, 0); } bool start() { if (!ua) return false; const char* config = "[global]\n" "my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n" "my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n" "[chatserver]\ngroup_autoconnect=0\nmember_files_enabled=0\nstorage_autoload=0\n"; inst = utun_instance_create_from_str(ua, config); if (!inst || utun_core_start(inst) || chat_service_start(inst)) return false; chat_core_create_channel_auto(inst, "Group"); db.setDb(inst->topo_sqlite_db); db.setMyNodeId(inst->node_id); if (db.getChannels().size() != 1) return false; channel = db.getChannels()[0].channelId; const QByteArray peer = QByteArray::fromHex("ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a"); return dm_start(inst, 7, (const uint8_t*)peer.constData(), (const uint8_t*)peer.constData(), "PM", nullptr) == 0; } }; class ChannelListTest : public QObject { Q_OBJECT private slots: void initTestCase() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); } void reloadKeepsConnectionState() { ListCore core; QVERIFY(core.start()); ChannelList list(&core.db); list.loadChannels(); auto* view = list.findChild(); QVERIFY(view); list.setChannelPeersOnline(core.channel, 3); list.setChannelPeersOnline(core.channel, -1); list.setAutoConnectActive(true); const auto dm = core.db.getDmConversations()[0]; list.selectChannel("dm:" + dm.convId); QVERIFY(!view->currentIndex().data(ChannelAutoConnectActiveRole).toBool()); list.selectChannel(core.channel); list.loadChannels(); QCOMPARE(view->currentIndex().data(ChannelPeersOnlineRole).toInt(), 3); QVERIFY(view->currentIndex().data(ChannelAutoConnectActiveRole).toBool()); QSignalSpy selected(&list, &ChannelList::channelSelected); list.selectChannel(QString()); QCOMPARE(view->currentIndex().data(ChannelChannelIdRole).toString(), core.channel); QVERIFY(selected.isEmpty()); } void unreadNotificationIsUnique() { ListCore core; QVERIFY(core.start()); ChannelList list(&core.db); list.loadChannels(); auto* view = list.findChild(); QVERIFY(view); QSignalSpy changes(view->model(), &QAbstractItemModel::dataChanged); list.setChannelUnreadCount(core.channel, 7); QCOMPARE(list.getChannelUnreadCount(core.channel), 7); QCOMPARE(changes.count(), 1); list.setChannelUnreadCount(core.channel, 7); QCOMPARE(changes.count(), 1); list.setChannelUnreadCount(core.channel, -1); QCOMPARE(list.getChannelUnreadCount(core.channel), 7); QCOMPARE(changes.count(), 1); } void presenceRequestsAreCancelledWithoutLeaks() { ListCore core; QVERIFY(core.start()); ChannelList list(&core.db); UTUN_INSTANCE instance{}; instance.ua = uasync_create(); QVERIFY(instance.ua); auto release = qScopeGuard([&] { gui_bridge_set_inst(nullptr); gui_bridge_set_uasync(nullptr); uasync_destroy(instance.ua, 1); }); uasync_mark_running(instance.ua); gui_bridge_set_uasync(instance.ua); gui_bridge_set_inst(&instance); const auto allocations = u_get_allocated_count(); list.refreshDmPresence(); list.refreshDmPresence(); QTest::qWait(100); QVERIFY(instance.ua->posted_tasks_head != nullptr); gui_bridge_set_inst(nullptr); QVERIFY(instance.ua->posted_tasks_head == nullptr); QCOMPARE(u_get_allocated_count(), allocations); } void menuAction_data() { QTest::addColumn("dm"); QTest::addColumn("change"); QTest::newRow("group-current") << false << 0; QTest::newRow("group-removed") << false << 1; QTest::newRow("pm-current") << true << 0; QTest::newRow("pm-removed") << true << 1; QTest::newRow("pm-generation-changed") << true << 2; QTest::newRow("pm-deletion-started") << true << 3; } void menuAction() { QFETCH(bool, dm); QFETCH(int, change); ListCore core; QVERIFY(core.start()); ChannelList list(&core.db); list.loadChannels(); list.resize(300, 400); list.show(); const auto conversation = core.db.getDmConversations()[0]; const QString target = dm ? "dm:" + conversation.convId : core.channel; list.selectChannel(target); auto* view = list.findChild(); QVERIFY(view); QSignalSpy removed(&list, &ChannelList::dmDeleteRequested); QSignalSpy files(&list, &ChannelList::filesRequested); QTimer::singleShot(0, &list, [&] { auto* menu = qobject_cast(QApplication::activePopupWidget()); QVERIFY(menu); QCOMPARE(menu->actions().size(), 1); QVERIFY(menu->actions()[0]->isEnabled()); if (change == 1) view->model()->removeRow(view->currentIndex().row()); if (change == 2) view->model()->setData(view->currentIndex(), QVariant::fromValue(conversation.generation + 1), ChannelDmGenerationRole); if (change == 3) list.setDmDeleting(conversation.convId, conversation.generation, true); QTest::keyClick(menu, Qt::Key_Down); QTest::keyClick(menu, Qt::Key_Return); }); view->customContextMenuRequested(view->visualRect(view->currentIndex()).center()); QCOMPARE(removed.count(), int(dm && !change)); QCOMPARE(files.count(), int(!dm && !change)); if (dm && !change) { QCOMPARE(removed[0][0].toString(), conversation.convId); QCOMPARE(removed[0][1].toULongLong(), conversation.generation); } else if (!dm && !change) QCOMPARE(files[0][0].toString(), target); } void menuSurvivesOwnerDeletion() { ListCore core; QVERIFY(core.start()); QPointer list = new ChannelList(&core.db); list->loadChannels(); list->resize(300, 400); list->show(); auto* view = list->findChild(); QVERIFY(view); const QPoint point = view->visualRect(view->currentIndex()).center(); QTimer::singleShot(0, [&] { delete list.data(); }); view->customContextMenuRequested(point); QVERIFY(!list); } void borrowedDbCanDisappear() { QPointer db = new DbManager; ChannelList list(db); delete db.data(); list.loadChannels(); list.refreshDmPresence(); QTest::qWait(100); QCOMPARE(list.findChild()->model()->rowCount(), 0); } }; QTEST_MAIN(ChannelListTest) #include "test_channel_list.moc"