/* SPDX-FileCopyrightText: 2023 Fushan Wen SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #pragma once #include #include #include #include #include #include #include #include #include #include #include "abstractwindowtasksmodel.h" #include "samplewidgetwindow.h" extern KSERVICE_EXPORT int ksycoca_ms_between_checks; namespace TestUtils { using namespace TaskManager; constexpr const char *dummyDesktopFileName = "org.kde.plasma.test.dummy.desktop"; void initTestCase() { ksycoca_ms_between_checks = 0; } void cleanupTestCase() { QFile dummyFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications") + QDir::separator() + QString::fromLatin1(dummyDesktopFileName)); dummyFile.remove(); QProcess killAllTestWindowProcess; killAllTestWindowProcess.setProgram(QStringLiteral("killall")); killAllTestWindowProcess.setArguments({QStringLiteral("-9"), QStringLiteral("samplewidgetwindow")}); killAllTestWindowProcess.start(); killAllTestWindowProcess.waitForFinished(); } std::unique_ptr createSingleWindow(const AbstractWindowTasksModel &model, const QString &title, QModelIndex &index) { auto window = std::make_unique(); window->setTitle(title); window->setBaseSize(QSize(320, 240)); QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); window->show(); if (rowInsertedSpy.empty()) { Q_ASSERT(rowInsertedSpy.wait()); } // Find the window index const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title); Q_ASSERT(results.size() == 1); index = results.at(0); Q_ASSERT(index.isValid()); qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString(); return window; } void testOpenCloseWindow(const AbstractWindowTasksModel &model) { auto findWindow = [&model](const QString &windowTitle) { for (int i = 0; i < model.rowCount(); ++i) { const QString title = model.index(i, 0).data(Qt::DisplayRole).toString(); if (title == windowTitle) { return true; } } return false; }; // Create a window to test if the model can receive it QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); QModelIndex index; auto window = createSingleWindow(model, title, index); // A new window appears // Find the window in the model QVERIFY(findWindow(title)); // Change the title of the window { QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); const QString newTitle = title + QStringLiteral("__newtitle__"); window->setTitle(newTitle); QVERIFY(dataChangedSpy.wait()); QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { return list.at(2).value>().contains(Qt::DisplayRole); })); // Make sure the title is updated QTRY_VERIFY(!findWindow(title)); QTRY_VERIFY(findWindow(newTitle)); } // Now close the window { int modelCount = model.rowCount(); QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); window->close(); QVERIFY(rowsRemovedSpy.wait()); QCOMPARE(modelCount - 1, model.rowCount()); } } void testFullscreen(const AbstractWindowTasksModel &model) { const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); QModelIndex index; auto window = createSingleWindow(model, title, index); QVERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); window->showFullScreen(); dataChangedSpy.wait(); // There can be more than one dataChanged signal being emitted due to caching QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { return list.at(2).value>().contains(AbstractTasksModel::IsFullScreen); })); QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool()); dataChangedSpy.clear(); window->showNormal(); QVERIFY(dataChangedSpy.wait()); // There can be more than one dataChanged signal being emitted due to caching QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { return list.at(2).value>().contains(AbstractTasksModel::IsFullScreen); })); QTRY_VERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); } void testGeometry(const AbstractWindowTasksModel &model) { const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); QModelIndex index; auto window = createSingleWindow(model, title, index); const QSize oldSize = index.data(AbstractTasksModel::Geometry).toRect().size(); QCoreApplication::processEvents(); QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); window->resize(QSize(240, 320)); QVERIFY(dataChangedSpy.wait()); // There can be more than one dataChanged signal being emitted due to caching // When using openbox the test is flaky QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) { return list.at(2).value>().contains(AbstractTasksModel::Geometry); })); QTRY_VERIFY(index.data(AbstractTasksModel::Geometry).toRect().size() != oldSize); } void testStackingOrder(const AbstractWindowTasksModel &model) { const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString()); QModelIndex index; auto firstWindow = createSingleWindow(model, title, index); QTRY_VERIFY(index.data(AbstractTasksModel::StackingOrder).toInt() >= 0); const int stackingOrder1 = index.data(AbstractTasksModel::StackingOrder).toInt(); // Create another window to make stacking order change QModelIndex index2; auto secondWindow = createSingleWindow(model, QStringLiteral("second test window"), index2); QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() >= 0); const int stackingOrder2 = index2.data(AbstractTasksModel::StackingOrder).toInt(); QVERIFY2( stackingOrder1 < stackingOrder2, QLatin1String("stackingOrder1: %1 stackingOrder2: %2").arg(QString::number(stackingOrder1), QString::number(stackingOrder2)).toLatin1().constData()); firstWindow->close(); QCoreApplication::processEvents(); QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() < stackingOrder2); } void createDesktopFile(const char *fileName, const std::vector &lines, QString &path) { path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications") + QDir::separator() + QString::fromUtf8(fileName); QFile out(path); if (out.exists()) { qDebug() << "Removing the old desktop file in" << path; out.remove(); } qDebug() << "Creating a desktop file in" << path; QVERIFY(out.open(QIODevice::WriteOnly)); out.write("[Desktop Entry]\n"); for (const std::string &l : lines) { out.write((l + "\n").c_str()); } out.close(); KSycoca::self()->ensureCacheValid(); } void testModelDataFromDesktopFile(const AbstractWindowTasksModel &model) { // Case 1: A normal window std::vector lines; lines.emplace_back("Name=DummyWindow"); lines.emplace_back("GenericName=DummyGenericName"); lines.emplace_back(std::string("Exec=") + TaskManagerTest::samplewidgetwindowExecutablePath); lines.emplace_back("Terminal=false"); lines.emplace_back("Type=Application"); lines.emplace_back(std::string("Icon=") + QFINDTESTDATA("data/windows/none.png").toStdString()); auto testCanLaunchNewInstance = [&](bool canLaunchNewInstance) { QString desktopFilePath; QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); TestUtils::createDesktopFile(dummyDesktopFileName, lines, desktopFilePath); QProcess sampleWindowProcess; sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath)); sampleWindowProcess.setArguments(QStringList{ QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())), QFINDTESTDATA("data/windows/samplewidgetwindow.png"), }); sampleWindowProcess.start(); rowsInsertedSpy.wait(); // find newly created window const auto results = model.match(model.index(0, 0), Qt::DisplayRole, sampleWindowProcess.arguments().at(0)); QVERIFY(results.size() == 1); QModelIndex index = results.at(0); QVERIFY(index.isValid()); QCOMPARE(index.data(AbstractTasksModel::AppName).toString(), QStringLiteral("DummyWindow")); QCOMPARE(index.data(AbstractTasksModel::GenericName).toString(), QStringLiteral("DummyGenericName")); QCOMPARE(index.data(AbstractTasksModel::LauncherUrl).toUrl(), QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName)))); QCOMPARE(index.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(), QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName)))); // Test icon should use the icon from the desktop file (Not the png file filled with red color) const QIcon windowIcon = index.data(Qt::DecorationRole).value(); QVERIFY(!windowIcon.isNull()); QVERIFY(windowIcon.pixmap(KIconLoader::SizeLarge).toImage().pixelColor(KIconLoader::SizeLarge / 2, KIconLoader::SizeLarge / 2).red() < 200); QCOMPARE(index.data(AbstractTasksModel::CanLaunchNewInstance).toBool(), canLaunchNewInstance); sampleWindowProcess.terminate(); QVERIFY(rowsRemovedSpy.wait()); sampleWindowProcess.waitForFinished(); }; // Case 1: Default case testCanLaunchNewInstance(true); // Case 2: Set SingleMainWindow or X-GNOME-SingleWindow or both lines.emplace_back("SingleMainWindow=true"); testCanLaunchNewInstance(false); lines.pop_back(); lines.emplace_back("X-GNOME-SingleWindow=true"); testCanLaunchNewInstance(false); lines.pop_back(); lines.emplace_back("SingleMainWindow=false"); lines.emplace_back("X-GNOME-SingleWindow=true"); testCanLaunchNewInstance(false); lines.pop_back(); lines.pop_back(); lines.emplace_back("SingleMainWindow=true"); lines.emplace_back("X-GNOME-SingleWindow=false"); testCanLaunchNewInstance(false); lines.pop_back(); lines.pop_back(); lines.emplace_back("SingleMainWindow=false"); lines.emplace_back("X-GNOME-SingleWindow=false"); testCanLaunchNewInstance(true); } void testRequest(AbstractWindowTasksModel &model) { constexpr int timeout = 30000; // openbox is slow to respond QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted); QProcess sampleWindowProcess; sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath)); sampleWindowProcess.setArguments(QStringList{ QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())), }); sampleWindowProcess.start(); QVERIFY(rowsInsertedSpy.wait(timeout)); // Find the window index auto findWindowIndex = [&model](QPersistentModelIndex &index, const QString &title) { const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title); qCritical() << "results for" << title << results << "total:" << model.rowCount(); QVERIFY(results.size() == 1); index = results.at(0); QVERIFY(index.isValid()); qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString(); }; QPersistentModelIndex index; findWindowIndex(index, sampleWindowProcess.arguments().at(0)); QVERIFY(index.isValid()); { qDebug("requestNewInstance"); model.requestNewInstance(index); QVERIFY(rowsInsertedSpy.wait(timeout)); } QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged); { qDebug("requestToggleMinimized"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleMinimized(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(index.data(AbstractTasksModel::IsMinimized).toBool()); } { qDebug("requestActivate"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestActivate(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); } { qDebug("requestToggleMaximized"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleMaximized(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); QTRY_VERIFY(index.data(AbstractTasksModel::IsMaximized).toBool()); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleMaximized(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool()); QTRY_VERIFY(!index.data(AbstractTasksModel::IsMaximized).toBool()); } { qDebug("requestToggleKeepAbove"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleKeepAbove(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepAbove).toBool()); QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepBelow).toBool()); } { qDebug("requestToggleKeepBelow"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleKeepBelow(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepAbove).toBool()); QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepBelow).toBool()); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleKeepBelow(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepAbove).toBool()); QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepBelow).toBool()); } { qDebug("requestToggleFullScreen"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleFullScreen(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool()); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleFullScreen(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool()); } if (KWindowSystem::isPlatformX11()) { qDebug("requestToggleShaded"); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleShaded(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(index.data(AbstractTasksModel::IsShaded).toBool()); QCoreApplication::processEvents(); dataChangedSpy.clear(); model.requestToggleShaded(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (dataChangedSpy.empty()) { QVERIFY(dataChangedSpy.wait(timeout)); } QTRY_VERIFY(!index.data(AbstractTasksModel::IsShaded).toBool()); } QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved); { qDebug("requestClose"); model.requestClose(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (rowsRemovedSpy.empty()) { QVERIFY(rowsRemovedSpy.wait(timeout)); } } { qDebug("Close the new instance"); findWindowIndex(index, QStringLiteral("__test_window_no_title__")); rowsRemovedSpy.clear(); model.requestClose(index); if (KWindowSystem::isPlatformX11()) { QX11Info::getTimestamp(); // roundtrip } if (rowsRemovedSpy.empty()) { QVERIFY(rowsRemovedSpy.wait(timeout)); } } } }