// SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include "../previewplacement.h" #include "../taskbargeometry.h" #include #include class PreviewTest : public QObject { Q_OBJECT private Q_SLOTS: void placementTracksButtonAndScreenEdges() { const QRect screen(0, 0, 1000, 800); const QSize size(300, 200); QCOMPARE(previewPosition(QRect(400, 760, 100, 40), screen, size, Plasma::Types::BottomEdge), QPoint(300, 560)); QCOMPARE(previewPosition(QRect(0, 760, 100, 40), screen, size, Plasma::Types::BottomEdge), QPoint(0, 560)); QCOMPARE(previewPosition(QRect(900, 760, 100, 40), screen, size, Plasma::Types::BottomEdge), QPoint(700, 560)); QCOMPARE(previewPosition(QRect(400, 0, 100, 40), screen, size, Plasma::Types::TopEdge), QPoint(300, 40)); QCOMPARE(previewPosition(QRect(0, 300, 40, 100), screen, size, Plasma::Types::LeftEdge), QPoint(40, 250)); QCOMPARE(previewPosition(QRect(960, 300, 40, 100), screen, size, Plasma::Types::RightEdge), QPoint(660, 250)); QCOMPARE(previewPosition(QRect(-1000, 760, 100, 40), QRect(-1000, 0, 1000, 800), size, Plasma::Types::BottomEdge), QPoint(-1000, 560)); QCOMPARE(previewPosition({}, screen, size, Plasma::Types::BottomEdge), QPoint(350, 600)); } void geometryBridgeOnWayland() { if (QGuiApplication::platformName() != QLatin1String("wayland")) { QSKIP("Requires KWin's Wayland compositor"); } auto bus = QDBusConnection::sessionBus(); const QString service = QStringLiteral("se.ajpanton.plasma-task-group-shortcuts"); QVERIFY(bus.registerService(service)); { TaskbarGeometry geometry; QSignalSpy results(&geometry, &TaskbarGeometry::received); QSignalSpy lookups(&geometry, &TaskbarGeometry::lookup); // The script must respond even when a window has disappeared. geometry.request(QStringLiteral("nonexistent-window")); QTRY_COMPARE(lookups.count(), 1); QTRY_COMPARE(results.count(), 1); QVERIFY(results.first().first().toRectF().isEmpty()); geometry.request(QStringLiteral("another-window")); geometry.cancel(); QTest::qWait(100); QCOMPARE(results.count(), 1); } QVERIFY(bus.unregisterService(service)); } void findsPublishedButtonGeometryOnWayland() { if (QGuiApplication::platformName() != QLatin1String("wayland")) { QSKIP("Requires KWin and the plasma-window-management protocol"); } TaskManager::TasksModel model; model.setGroupMode(TaskManager::TasksModel::GroupDisabled); QQuickWindow target; target.setTitle(QStringLiteral("Task group geometry test")); target.resize(400, 300); target.show(); QTRY_VERIFY(target.isExposed()); const auto matching = [&] { return model.match(model.index(0, 0), Qt::DisplayRole, target.title()); }; QTRY_VERIFY(!matching().isEmpty()); const QModelIndex index = matching().first(); const QString uuid = index.data(TaskManager::AbstractTasksModel::WinIdList).toList().first().toString(); QVERIFY(!uuid.isEmpty()); QQuickWindow panel; panel.setTitle(QStringLiteral("Task group geometry panel")); panel.resize(1000, 40); panel.show(); QTRY_VERIFY(panel.isExposed()); const auto panelTask = [&] { return model.match(model.index(0, 0), Qt::DisplayRole, panel.title()); }; QTRY_VERIFY(!panelTask().isEmpty()); QTRY_VERIFY(panelTask().first().data(TaskManager::AbstractTasksModel::Geometry).toRect().isValid()); const QPoint origin = panelTask().first().data(TaskManager::AbstractTasksModel::Geometry).toRect().topLeft(); const QRect button(origin + QPoint(120, 0), QSize(180, 40)); QQuickItem delegate(panel.contentItem()); delegate.setPosition(QPointF(120, 0)); delegate.setSize(QSizeF(180, 40)); model.requestPublishDelegateGeometry(index, button, &delegate); QTest::qWait(100); auto bus = QDBusConnection::sessionBus(); const QString service = QStringLiteral("se.ajpanton.plasma-task-group-shortcuts"); QVERIFY(bus.registerService(service)); { TaskbarGeometry geometry; QSignalSpy results(&geometry, &TaskbarGeometry::received); geometry.request(uuid); QTRY_COMPARE(results.count(), 1); QCOMPARE(results.first().first().toRectF(), QRectF(button)); } QVERIFY(bus.unregisterService(service)); } void loadsWithoutTakingFocus() { QTest::failOnWarning(QRegularExpression(QStringLiteral(".*Failed to create .*popup.*"))); QQmlEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/Preview.qml"))); std::unique_ptr object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); auto *window = qobject_cast(object.get()); QVERIFY(window); QVERIFY(!window->isVisible()); QCOMPARE(window->type(), Qt::Tool); QVERIFY(!window->transientParent()); QVERIFY(window->flags().testFlag(Qt::FramelessWindowHint)); QVERIFY(window->flags().testFlag(Qt::WindowDoesNotAcceptFocus)); QVERIFY(window->flags().testFlag(Qt::WindowTransparentForInput)); window->show(); QTRY_VERIFY(window->isExposed()); // Wayland can reject a window with an asynchronously delivered close. QTest::qWait(100); QVERIFY(window->isVisible()); QVERIFY(window->isExposed()); QTRY_VERIFY(window->width() > 0); QTRY_VERIFY(window->height() > 0); QVERIFY(window->flags().testFlag(Qt::WindowDoesNotAcceptFocus)); window->setProperty("windowTitle", QStringLiteral("Test window")); window->setProperty("position", 2); window->setProperty("count", 3); QCOMPARE(window->property("windowTitle").toString(), QStringLiteral("Test window")); window->hide(); } void preservesFocusOnWayland() { if (QGuiApplication::platformName() != QLatin1String("wayland")) { QSKIP("Requires a Wayland compositor"); } QQuickWindow original; original.resize(400, 300); original.show(); QTRY_VERIFY(original.isActive()); QQmlEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/Preview.qml"))); std::unique_ptr object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); auto *preview = qobject_cast(object.get()); QVERIFY(preview); preview->show(); QTRY_VERIFY(preview->isExposed()); QTest::qWait(100); QVERIFY(preview->isVisible()); QVERIFY(!preview->isActive()); QVERIFY(original.isActive()); preview->hide(); QTest::qWait(100); QVERIFY(original.isActive()); } }; QTEST_MAIN(PreviewTest) #include "test-preview.moc"