You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

167 lines
8.3 KiB

#include "../src/ptt_portal.h"
#include "../../../lib/debug_config.h"
#include <QtTest>
#include <QDBusAbstractAdaptor>
#include <QDBusContext>
#include <QDBusConnection>
#include <QDBusMetaType>
#include <QCoreApplication>
static const QString service = "org.freedesktop.portal.Desktop";
static const QString root = "/org/freedesktop/portal/desktop";
class FakeRequest : public QObject {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.portal.Request")
public:
using QObject::QObject;
public slots:
void Close() {}
signals:
void Response(uint code, const QVariantMap& results);
};
class FakeSession : public QObject {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.portal.Session")
public:
using QObject::QObject;
public slots:
void Close() { emit Closed({}); }
signals:
void Closed(const QVariantMap& details);
};
class FakePortal : public QObject, protected QDBusContext {
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.freedesktop.portal.GlobalShortcuts")
Q_PROPERTY(uint version READ version)
public:
uint version() const { return 2; }
uint bindResponse = 0;
int bindCount = 0, configureCount = 0;
QString session;
PttPortalShortcuts shortcuts;
FakeSession* sessionObject = nullptr;
QDBusObjectPath answer(const QVariantMap& options, uint code, const QVariantMap& results) {
QString sender = message().service().mid(1).replace('.', '_');
QString path = root + "/request/" + sender + "/" + options.value("handle_token").toString();
auto* request = new FakeRequest(this);
QDBusConnection::sessionBus().registerObject(path, request, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
QTimer::singleShot(0, request, [request, code, results]() { emit request->Response(code, results); });
return QDBusObjectPath(path);
}
public slots:
QDBusObjectPath CreateSession(const QVariantMap& options) {
QString sender = message().service().mid(1).replace('.', '_');
session = root + "/session/" + sender + "/" + options.value("session_handle_token").toString();
QDBusConnection::sessionBus().unregisterObject(session);
sessionObject = new FakeSession(this);
QDBusConnection::sessionBus().registerObject(session, sessionObject, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
return answer(options, 0, {{"session_handle", session}});
}
QDBusObjectPath ListShortcuts(const QDBusObjectPath&, const QVariantMap& options) {
return answer(options, 0, {{"shortcuts", QVariant::fromValue(shortcuts)}});
}
QDBusObjectPath BindShortcuts(const QDBusObjectPath&, const PttPortalShortcuts& requested,
const QString&, const QVariantMap& options) {
bindCount++;
if (bindResponse == 0) {
shortcuts = requested;
for (int i = 0; i < shortcuts.size(); i++) shortcuts[i].properties["trigger_description"] = QString("F%1").arg(8 + i);
}
return answer(options, bindResponse, {{"shortcuts", QVariant::fromValue(shortcuts)}});
}
void ConfigureShortcuts(const QDBusObjectPath&, const QString&, const QVariantMap&) { configureCount++; }
signals:
void Activated(const QDBusObjectPath& session, const QString& id, qulonglong timestamp, const QVariantMap& options);
void Deactivated(const QDBusObjectPath& session, const QString& id, qulonglong timestamp, const QVariantMap& options);
void ShortcutsChanged(const QDBusObjectPath& session, const PttPortalShortcuts& shortcuts);
};
class PortalTest : public QObject {
Q_OBJECT
private slots:
void lifecycle() {
qDBusRegisterMetaType<PttPortalShortcut>(); qDBusRegisterMetaType<PttPortalShortcuts>();
FakePortal fake;
auto bus = QDBusConnection::sessionBus();
QVERIFY(bus.registerService(service));
QVERIFY(bus.registerObject(root, &fake, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals | QDBusConnection::ExportAllProperties));
{
PttPortal portal(nullptr, "utun_ptt_test");
QSignalSpy talking(&portal, &PttPortal::talkingChanged);
portal.initialize();
QTRY_VERIFY(!portal.busy());
QVERIFY(!fake.session.isEmpty());
QCOMPARE(fake.bindCount, 0); // при первом запуске не открываем системный диалог
portal.configure("x11:123");
QTRY_COMPARE(fake.bindCount, 1);
QTRY_COMPARE(portal.descriptions().value(0), QString("F8"));
QTRY_VERIFY(!portal.busy());
QDBusObjectPath session(fake.session);
emit fake.Activated(QDBusObjectPath("/unrelated"), "ptt1", 0, {});
emit fake.Activated(session, "other_action", 0, {});
emit fake.Activated(session, "ptt1", 1, {});
QTRY_COMPARE(talking.size(), 1);
QCOMPARE(talking.last()[0].toBool(), true);
emit fake.Activated(session, "ptt1", 2, {}); // повторное событие не создаёт второй start
emit fake.Activated(session, "ptt2", 3, {});
emit fake.Deactivated(session, "ptt1", 4, {});
emit fake.Deactivated(session, "ptt2", 5, {});
QTRY_COMPARE(talking.size(), 2);
QCOMPARE(talking.last()[0].toBool(), false); // отпускание одного из двух хоткеев не останавливает второй
portal.setInhibited(true);
emit fake.Activated(session, "ptt1", 6, {});
emit fake.Deactivated(session, "ptt1", 7, {});
QTest::qWait(20);
QCOMPARE(talking.size(), 2);
portal.setInhibited(false);
portal.configure("x11:123");
QTRY_COMPARE(fake.configureCount, 1); // изменение через Configure, без повторного Bind
fake.shortcuts[0].properties["trigger_description"] = "Ctrl+F8";
emit fake.ShortcutsChanged(session, fake.shortcuts);
QTRY_COMPARE(portal.descriptions().value(0), QString("Ctrl+F8"));
emit fake.Activated(session, "ptt1", 8, {});
QTRY_COMPARE(talking.size(), 3);
emit fake.sessionObject->Closed({});
QTRY_COMPARE(talking.size(), 4);
QCOMPARE(talking.last()[0].toBool(), false);
QVERIFY(portal.descriptions()[0].isEmpty());
fake.bindResponse = 1;
portal.configure("");
QTRY_COMPARE(fake.bindCount, 2);
QTRY_VERIFY(!portal.busy());
QVERIFY(portal.descriptions()[0].isEmpty()); // отмена не оставляет активную сессию
fake.bindResponse = 0;
portal.configure("");
QTRY_COMPARE(fake.bindCount, 3);
QTRY_COMPARE(fake.configureCount, 2); // сохранённые сочетания восстанавливаются, затем открывается настройка
QTRY_COMPARE(portal.descriptions().value(0), QString("F8"));
emit fake.Activated(QDBusObjectPath(fake.session), "ptt3", 9, {});
QTRY_COMPARE(talking.size(), 5);
QVERIFY(bus.unregisterService(service));
QTRY_COMPARE(talking.size(), 6);
QCOMPARE(talking.last()[0].toBool(), false); // потеря сервиса не оставляет передачу включённой
}
bus.unregisterObject(root, QDBusConnection::UnregisterTree);
}
};
int main(int argc, char** argv) {
QCoreApplication app(argc, argv);
debug_set_level(DEBUG_LEVEL_INFO);
if (app.arguments().contains("--probe")) {
PttPortal portal(nullptr, "utun_vibechat_ptt_probe");
bool done = false;
QObject::connect(&portal, &PttPortal::statusChanged, &app, [&]() {
std::printf("portal: %s busy=%d\n", qPrintable(portal.status()), portal.busy());
if (!portal.busy()) { done = true; QTimer::singleShot(0, &app, &QCoreApplication::quit); }
});
QTimer::singleShot(10000, &app, &QCoreApplication::quit);
portal.initialize();
app.exec();
return done && !portal.status().contains("недоступны") ? 0 : 1;
}
PortalTest test;
return QTest::qExec(&test, argc, argv);
}
#include "test_ptt_portal.moc"