You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libcutefish/bluetooth/tests/tst_bluetoothqml.cpp

241 lines
9.3 KiB
C++

#include "fakebluez.h"
#include <QGuiApplication>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickImageProvider>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSignalSpy>
#include <QTest>
class TestHardware : public QObject
{
Q_OBJECT
Q_PROPERTY(bool available READ available CONSTANT)
Q_PROPERTY(bool wirelessHwEnabled READ available CONSTANT)
Q_PROPERTY(bool ready READ available NOTIFY readyChanged)
Q_PROPERTY(bool hotspotCapable READ available NOTIFY hotspotSupportedChanged)
public:
explicit TestHardware(QObject *parent = nullptr)
: QObject(parent)
{
}
bool available() const { return true; }
Q_SIGNALS:
void readyChanged();
void hotspotSupportedChanged();
};
// Stands in for the network models the sidebar watches for a wired device.
class TestNetworkModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int type MEMBER m_type)
Q_PROPERTY(QObject *sourceModel MEMBER m_sourceModel)
public:
enum Type {
UnknownType,
WiredType,
WirelessType
};
Q_ENUM(Type)
explicit TestNetworkModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
{
}
int rowCount(const QModelIndex & = {}) const override { return 0; }
QVariant data(const QModelIndex &, int) const override { return {}; }
private:
int m_type = UnknownType;
QObject *m_sourceModel = nullptr;
};
class TestIconProvider : public QQuickImageProvider
{
public:
TestIconProvider()
: QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage requestImage(const QString &, QSize *size, const QSize &) override
{
QImage image(1, 1, QImage::Format_ARGB32);
image.fill(Qt::transparent);
if (size)
*size = image.size();
return image;
}
};
class BluetoothQmlTest : public QObject
{
Q_OBJECT
private slots:
void initTestCase()
{
qmlRegisterType<TestHardware>("Cutefish.Settings", 1, 0, "Battery");
qmlRegisterType<TestHardware>("Cutefish.Settings", 1, 0, "Touchpad");
qmlRegisterType<TestHardware>("Cutefish.Settings", 1, 0, "Touchscreen");
qmlRegisterType<TestHardware>("Cutefish.Network", 1, 0, "EnabledConnections");
qmlRegisterType<TestHardware>("Cutefish.Network", 1, 0, "Handler");
qmlRegisterType<TestNetworkModel>("Cutefish.Network", 1, 0, "NetworkModel");
qmlRegisterType<TestNetworkModel>("Cutefish.Network", 1, 0, "AppletProxyModel");
qmlRegisterType<TestNetworkModel>("Cutefish.Settings", 1, 0, "NetworkMockModel");
// Registered from C++ by the settings application too.
qmlRegisterSingletonType(QUrl::fromLocalFile(QStringLiteral(SETTINGS_BLUETOOTH_PAGE))
.resolved(QUrl(QStringLiteral("../Metrics.qml"))),
"Cutefish.Settings", 1, 0, "Metrics");
qmlProtectModule("Cutefish.Settings", 1);
qmlProtectModule("Cutefish.Network", 1);
}
void sidebarAvailability()
{
auto bus = QDBusConnection::sessionBus();
FakeBluez fake(bus);
QVERIFY(bus.registerVirtualObject(QStringLiteral("/"), &fake, QDBusConnection::SubPath));
QQmlEngine engine;
engine.addImportPath(QStringLiteral(BLUETOOTH_IMPORT_PATH));
engine.addImportPath(QStringLiteral(STUB_IMPORT_PATH));
QQmlComponent host(&engine);
host.setData(R"(
import QtQuick
import QtQuick.Controls
ApplicationWindow {
title: "Settings"
property int headerInset: 40
header: Item { height: 40 }
}
)",
QUrl());
std::unique_ptr<QObject> window(host.create());
QVERIFY2(window, qPrintable(host.errorString()));
engine.rootContext()->setContextProperty(QStringLiteral("rootWindow"), window.get());
const auto sidebarUrl = QUrl::fromLocalFile(QStringLiteral(SETTINGS_BLUETOOTH_PAGE))
.resolved(QUrl(QStringLiteral("../SideBar.qml")));
QQmlComponent component(&engine, sidebarUrl);
std::unique_ptr<QObject> sidebar(component.create());
QVERIFY2(sidebar, qPrintable(component.errorString()));
auto *model = qvariant_cast<QObject *>(sidebar->property("model"));
QVERIFY(model);
auto *list = qobject_cast<QAbstractItemModel *>(model);
QVERIFY(list);
const int nameRole = list->roleNames().key("name");
auto indexOf = [list, nameRole](const QString &name) {
for (int row = 0; row < list->rowCount(); ++row) {
if (list->data(list->index(row, 0), nameRole).toString() == name)
return row;
}
return -1;
};
auto *manager =
engine.singletonInstance<BluetoothManager *>("Cutefish.Bluetooth", "Manager");
QVERIFY(manager);
QTest::qWait(30);
QVERIFY(!manager->available());
QCOMPARE(indexOf(QStringLiteral("bluetooth")), -1);
sidebar->setProperty("currentIndex", indexOf(QStringLiteral("sound")));
QVERIFY(bus.registerService(QStringLiteral("org.bluez")));
QTRY_VERIFY(manager->available());
QTRY_VERIFY(indexOf(QStringLiteral("bluetooth")) >= 0);
QCOMPARE(sidebar->property("currentIndex").toInt(), indexOf(QStringLiteral("sound")));
manager->setEnabled(false);
QTRY_VERIFY(!manager->enabled());
QVERIFY(indexOf(QStringLiteral("bluetooth")) >= 0);
sidebar->setProperty("currentIndex", indexOf(QStringLiteral("bluetooth")));
QSignalSpy removed(sidebar.get(), SIGNAL(currentPageRemoved()));
const auto adapterProperties = fake.objects.value(QDBusObjectPath(adapter));
fake.remove(adapter, {adapterInterface});
QTRY_COMPARE(indexOf(QStringLiteral("bluetooth")), -1);
QCOMPARE(sidebar->property("currentIndex").toInt(), 0);
QCOMPARE(removed.size(), 1);
fake.add(adapter, adapterProperties);
QTRY_VERIFY(indexOf(QStringLiteral("bluetooth")) >= 0);
QVERIFY(bus.unregisterService(QStringLiteral("org.bluez")));
QTRY_COMPARE(indexOf(QStringLiteral("bluetooth")), -1);
bus.unregisterObject(QStringLiteral("/"), QDBusConnection::UnregisterTree);
}
void settingsPage()
{
auto bus = QDBusConnection::sessionBus();
FakeBluez fake(bus);
QVERIFY(bus.registerVirtualObject(QStringLiteral("/"), &fake, QDBusConnection::SubPath));
QVERIFY(bus.registerService(QStringLiteral("org.bluez")));
QQmlEngine engine;
engine.addImportPath(QStringLiteral(BLUETOOTH_IMPORT_PATH));
engine.addImportPath(QStringLiteral(STUB_IMPORT_PATH));
QSignalSpy warnings(&engine, &QQmlEngine::warnings);
QQmlComponent host(&engine);
host.setData(R"(
import QtQuick
import QtQuick.Controls
import FishUI
ApplicationWindow {
width: 900
height: 650
visible: true
property int headerInset: 40
header: Item { height: 40 }
background: Rectangle { color: "white" }
}
)",
QUrl());
std::unique_ptr<QObject> window(host.create());
QVERIFY2(window, qPrintable(host.errorString()));
// After FishUI's import, which installs the real provider in its place.
engine.addImageProvider(QStringLiteral("icontheme"), new TestIconProvider);
engine.rootContext()->setContextProperty(QStringLiteral("rootWindow"), window.get());
QQmlComponent component(&engine,
QUrl::fromLocalFile(QStringLiteral(SETTINGS_BLUETOOTH_PAGE)));
std::unique_ptr<QObject> page(component.create());
QVERIFY2(page, qPrintable(component.errorString()));
auto *item = qobject_cast<QQuickItem *>(page.get());
QVERIFY(item);
item->setParentItem(qobject_cast<QQuickWindow *>(window.get())->contentItem());
item->setWidth(900);
item->setHeight(600);
auto *manager =
engine.singletonInstance<BluetoothManager *>("Cutefish.Bluetooth", "Manager");
QVERIFY(manager);
QTRY_VERIFY(manager->operational());
QTRY_COMPARE(manager->devices().size(), 1);
QTRY_VERIFY(manager->scanning());
// Pairing prompts belong to the shell's agent, not to the page.
QVERIFY(!manager->agentEnabled());
item->setVisible(false);
QTRY_VERIFY(!manager->scanning());
item->setVisible(true);
QTRY_VERIFY(manager->scanning());
QTest::qWait(50);
for (const auto &arguments : warnings) {
const auto errors = qvariant_cast<QList<QQmlError>>(arguments.first());
for (const auto &error : errors)
qWarning().noquote() << error.toString();
}
QCOMPARE(warnings.size(), 0);
page.reset();
bus.unregisterService(QStringLiteral("org.bluez"));
bus.unregisterObject(QStringLiteral("/"), QDBusConnection::UnregisterTree);
}
};
int main(int argc, char **argv)
{
qputenv("DBUS_SYSTEM_BUS_ADDRESS", qgetenv("DBUS_SESSION_BUS_ADDRESS"));
QGuiApplication app(argc, argv);
BluetoothQmlTest test;
return QTest::qExec(&test, argc, argv);
}
#include "tst_bluetoothqml.moc"