Files
utun2/tools/chatgui/tests/test_group_edits.cpp
T

131 lines
8.0 KiB
C++

/* Настоящие MainWindow/uasync/SQLite: контекстное меню, composer и обновление существующей строки/цитаты. */
#include "../src/mainwindow.h"
#include "../src/messagelist.h"
#include "../src/messagedelegate.h"
#include "../src/chatview.h"
#include "../src/inputbar.h"
#include "../src/soundsettingspage.h"
#include "../db/db_manager.h"
#include "../transport/utun_node.h"
#include "../transport/gui_bridge.h"
#include <QtTest>
#include <QTemporaryDir>
#include <QFile>
#include <QTextEdit>
#include <QMenu>
#include <QSlider>
#include <QCheckBox>
#include <QScopeGuard>
#include <future>
#include <memory>
extern "C" {
#include "utun_instance.h"
#include "chat/chat_core.h"
#include "debug_config.h"
}
static int newMessages;
static int inCore(std::function<int(UTUN_INSTANCE*)> operation) {
auto* task = new std::packaged_task<int()>([operation] { return operation(gui_bridge_get_inst()); });
auto result = task->get_future();
gui_bridge_post_uasync_fn([](void* arg) {
std::unique_ptr<std::packaged_task<int()>> work(static_cast<std::packaged_task<int()>*>(arg)); (*work)();
}, task);
if (result.wait_for(std::chrono::seconds(5)) != std::future_status::ready) qFatal("Edit test: core task timed out");
return result.get();
}
class GroupEditTest : public QObject {
Q_OBJECT
private slots:
void editThroughMenuAndCore() {
debug_config_init(); gui_bridge_init(); QTemporaryDir directory; QVERIFY(directory.isValid());
directory.setAutoRemove(false);
auto diagnostics = qScopeGuard([&] { directory.setAutoRemove(!QTest::currentTestFailed()); });
const auto configPath = directory.filePath("node.cfg"); QFile config(configPath); QVERIFY(config.open(QIODevice::WriteOnly));
const QByteArray settings = "[global]\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_enabled=no\n[server:local]\naddr=127.0.0.1:0\ntype=private\n"
"[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\nlocal_invites_enabled=0\nlocal_broadcasts_enabled=0\n"
"[ntp]\nenabled=no\n";
QCOMPARE(config.write(settings), settings.size()); config.close(); DbManager db;
MainWindow window(nullptr, &db, configPath, directory.filePath("db"), directory.filePath("edit.log"), "warn", "chat_sync=debug");
window.show(); QFile log(directory.filePath("edit.log"));
const QByteArray startupLog = log.open(QIODevice::ReadOnly) ? log.readAll() : QByteArray("log unavailable");
QVERIFY2(window.utunNode()->isRunning(), qPrintable(window.utunNode()->startupError() + "\n" + startupLog));
QTRY_VERIFY_WITH_TIMEOUT(db.isOpen(), 3000);
QCOMPARE(inCore([](UTUN_INSTANCE* inst) { chat_core_create_channel_auto(inst, "Editable"); return 0; }), 0);
QTRY_COMPARE(db.getChannels().size(), 1); const auto channel = db.getChannels().first().channelId;
QByteArray id, replyId;
QCOMPARE(inCore([&](UTUN_INSTANCE* inst) {
chat_msg_submit req{}; req.inst = inst; strcpy(req.content_type, "text/plain");
snprintf(req.channel_id, sizeof(req.channel_id), "%s", qPrintable(channel));
req.data = (uint8_t*)"Original"; req.data_len = 8;
if (chat_core_submit_message(inst, &req)) return -1;
id = QByteArray((const char*)req.message_id, 32); memcpy(req.reply_to, req.message_id, 32);
req.data = (uint8_t*)"Reply"; req.data_len = 5;
const int rc = chat_core_submit_message(inst, &req); replyId = QByteArray((const char*)req.message_id, 32); return rc;
}), 0);
auto* history = window.findChild<MessageList*>(); QVERIFY(history); history->loadChannel(channel);
auto* view = history->findChild<ChatView*>(); auto* input = history->findChild<InputBar*>(); QVERIFY(view && input);
auto* editor = input->findChild<QTextEdit*>(); QVERIFY(editor);
QTRY_COMPARE(view->model()->rowCount(), 2);
auto indexFor = [&](const QByteArray& key) {
for (int row = 0; row < view->model()->rowCount(); row++) {
const auto index = view->model()->index(row, 0);
if (index.data(MsgMessageKeyRole).toString() == QString::fromLatin1(key.toHex())) return index;
}
return QModelIndex();
};
QCOMPARE(indexFor(id).data(MsgTextRole).toString(), QString("Original"));
editor->setPlainText("Unsent draft");
auto openEditor = [&] {
QCoreApplication::processEvents(); const auto original = indexFor(id); QVERIFY(original.isValid());
view->scrollTo(original);
const auto pos = view->visualRect(original).center();
QCOMPARE(view->indexAt(pos), original);
QTimer::singleShot(0, [] {
auto* menu = qobject_cast<QMenu*>(QApplication::activePopupWidget());
if (!menu) qFatal("Edit menu missing");
for (auto* action : menu->actions()) if (action->text() == "Edit message") { action->trigger(); menu->close(); return; }
qFatal("Edit action missing");
});
QMetaObject::invokeMethod(view, "customContextMenuRequested", Qt::DirectConnection, Q_ARG(QPoint, pos));
};
openEditor(); QCOMPARE(editor->toPlainText(), QString("Original"));
gui_bridge_set_msg_received_cb([](const char*, int, uint64_t, int) { ++newMessages; }); newMessages = 0;
editor->setPlainText(QString::fromUtf8("Изменено 👍")); QTest::keyClick(editor, Qt::Key_Return);
QTRY_COMPARE_WITH_TIMEOUT(indexFor(id).data(MsgVersionRole).toULongLong(), quint64(2), 3000);
QCOMPARE(view->model()->rowCount(), 2); QCOMPARE(indexFor(id).data(MsgTextRole).toString(), QString::fromUtf8("Изменено 👍"));
QVERIFY(indexFor(id).data(MsgTimeRole).toString().contains("edited "));
QCOMPARE(indexFor(replyId).data(MsgQuoteTextRole).toString(), QString::fromUtf8("Изменено 👍"));
QTRY_COMPARE(editor->toPlainText(), QString("Unsent draft")); QCOMPARE(newMessages, 0);
openEditor(); editor->setPlainText("Stale editor draft");
QCOMPARE(inCore([&](UTUN_INSTANCE* inst) {
return chat_core_edit_message(inst, qPrintable(channel), (const uint8_t*)id.constData(), 2, "Remote edit", 11);
}), 0);
QTRY_COMPARE(indexFor(id).data(MsgVersionRole).toULongLong(), quint64(3));
QCOMPARE(editor->toPlainText(), QString("Stale editor draft"));
QSignalSpy failures(input, &InputBar::attachmentRejected); QTest::keyClick(editor, Qt::Key_Return);
QTRY_COMPARE(failures.size(), 1); QCOMPARE(editor->toPlainText(), QString("Stale editor draft"));
QCOMPARE(indexFor(id).data(MsgTextRole).toString(), QString("Remote edit")); QCOMPARE(newMessages, 0);
QTest::keyClick(editor, Qt::Key_Escape); QCOMPARE(editor->toPlainText(), QString("Unsent draft"));
SoundSettingsPage sounds(&db); sounds.loadFromDb();
const auto checks = sounds.findChildren<QCheckBox*>(); const auto sliders = sounds.findChildren<QSlider*>();
QCOMPARE(checks.size(), 9); QCOMPARE(sliders.size(), 9);
for (int i = 0; i < 9; i++) { checks[i]->setChecked(i % 2 == 0); sliders[i]->setValue(20 + i); }
checks[0]->setChecked(false); sounds.applyAndSave();
QCOMPARE(inCore([](UTUN_INSTANCE*) { return 0; }), 0);
QCOMPARE(db.getUiStateInt("sound_enabled", -1), 0); QCOMPARE(db.getUiStateInt("sound_volume", -1), 20);
const char* events[] = {"msg_incoming", "msg_outgoing", "connect", "disconnect", "call_incoming", "call_connecting", "call_ringing", "radio_incoming"};
for (int i = 0; i < 8; i++) {
QCOMPARE(db.getUiStateInt(QString("sound_%1_on").arg(events[i]), -1), (i + 1) % 2 == 0 ? 1 : 0);
QCOMPARE(db.getUiStateInt(QString("sound_%1_vol").arg(events[i]), -1), 21 + i);
}
gui_bridge_set_msg_received_cb(nullptr);
}
};
QTEST_MAIN(GroupEditTest)
#include "test_group_edits.moc"