Warning, file /plasma/plasma-workspace/libtaskmanager/autotests/common.h was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #pragma once
0008 
0009 #include <QDateTime>
0010 #include <QProcess>
0011 #include <QRasterWindow>
0012 #include <QSignalSpy>
0013 #include <QStandardPaths>
0014 #include <QTest>
0015 
0016 #include <KIconLoader>
0017 #include <KSycoca>
0018 #include <KWindowSystem>
0019 
0020 #include "abstractwindowtasksmodel.h"
0021 #include "samplewidgetwindow.h"
0022 
0023 namespace TestUtils
0024 {
0025 using namespace TaskManager;
0026 constexpr const char *dummyDesktopFileName = "org.kde.plasma.test.dummy.desktop";
0027 
0028 void cleanupTestCase()
0029 {
0030     QFile dummyFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications")
0031                     + QDir::separator() + QString::fromLatin1(dummyDesktopFileName));
0032     dummyFile.remove();
0033 }
0034 
0035 std::unique_ptr<QRasterWindow> createSingleWindow(const AbstractWindowTasksModel &model, const QString &title, QModelIndex &index)
0036 {
0037     auto window = std::make_unique<QRasterWindow>();
0038     window->setTitle(title);
0039     window->setBaseSize(QSize(320, 240));
0040 
0041     QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted);
0042     window->show();
0043     if (rowInsertedSpy.empty()) {
0044         Q_ASSERT(rowInsertedSpy.wait());
0045     }
0046 
0047     // Find the window index
0048     const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title);
0049     Q_ASSERT(results.size() == 1);
0050     index = results.at(0);
0051     Q_ASSERT(index.isValid());
0052     qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString();
0053 
0054     return window;
0055 }
0056 
0057 void testOpenCloseWindow(const AbstractWindowTasksModel &model)
0058 {
0059     auto findWindow = [&model](const QString &windowTitle) {
0060         for (int i = 0; i < model.rowCount(); ++i) {
0061             const QString title = model.index(i, 0).data(Qt::DisplayRole).toString();
0062             if (title == windowTitle) {
0063                 return true;
0064             }
0065         }
0066         return false;
0067     };
0068 
0069     // Create a window to test if the model can receive it
0070     QSignalSpy rowInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted);
0071 
0072     const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString());
0073     QModelIndex index;
0074     auto window = createSingleWindow(model, title, index);
0075 
0076     // A new window appears
0077     // Find the window in the model
0078     QVERIFY(findWindow(title));
0079 
0080     // Change the title of the window
0081     {
0082         QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged);
0083         const QString newTitle = title + QStringLiteral("__newtitle__");
0084         window->setTitle(newTitle);
0085         QVERIFY(dataChangedSpy.wait());
0086         QTRY_VERIFY(dataChangedSpy.constLast().at(2).value<QVector<int>>().contains(Qt::DisplayRole));
0087         // Make sure the title is updated
0088         QTRY_VERIFY(!findWindow(title));
0089         QTRY_VERIFY(findWindow(newTitle));
0090     }
0091 
0092     // Now close the window
0093     {
0094         int modelCount = model.rowCount();
0095         QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved);
0096         window->close();
0097         QVERIFY(rowsRemovedSpy.wait());
0098         QCOMPARE(modelCount - 1, model.rowCount());
0099     }
0100 }
0101 
0102 void testFullscreen(const AbstractWindowTasksModel &model)
0103 {
0104     const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString());
0105     QModelIndex index;
0106     auto window = createSingleWindow(model, title, index);
0107 
0108     QVERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool());
0109 
0110     QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged);
0111     window->showFullScreen();
0112     dataChangedSpy.wait();
0113 
0114     // There can be more than one dataChanged signal being emitted due to caching
0115     QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) {
0116         return list.at(2).value<QVector<int>>().contains(AbstractTasksModel::IsFullScreen);
0117     }));
0118     QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool());
0119     dataChangedSpy.clear();
0120     window->showNormal();
0121     QVERIFY(dataChangedSpy.wait());
0122     // There can be more than one dataChanged signal being emitted due to caching
0123     QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) {
0124         return list.at(2).value<QVector<int>>().contains(AbstractTasksModel::IsFullScreen);
0125     }));
0126     QTRY_VERIFY(!index.data(AbstractTasksModel::IsFullScreen).toBool());
0127 }
0128 
0129 void testGeometry(const AbstractWindowTasksModel &model)
0130 {
0131     const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString());
0132     QModelIndex index;
0133     auto window = createSingleWindow(model, title, index);
0134 
0135     const QSize oldSize = index.data(AbstractTasksModel::Geometry).toRect().size();
0136     QCoreApplication::processEvents();
0137     QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged);
0138     window->resize(QSize(240, 320));
0139     QVERIFY(dataChangedSpy.wait());
0140 
0141     // There can be more than one dataChanged signal being emitted due to caching
0142     // When using openbox the test is flaky
0143     QTRY_VERIFY(std::any_of(dataChangedSpy.cbegin(), dataChangedSpy.cend(), [](const QVariantList &list) {
0144         return list.at(2).value<QVector<int>>().contains(AbstractTasksModel::Geometry);
0145     }));
0146     QTRY_VERIFY(index.data(AbstractTasksModel::Geometry).toRect().size() != oldSize);
0147 }
0148 
0149 void testStackingOrder(const AbstractWindowTasksModel &model)
0150 {
0151     const QString title = QStringLiteral("__testwindow__%1").arg(QDateTime::currentDateTime().toString());
0152     QModelIndex index;
0153     auto firstWindow = createSingleWindow(model, title, index);
0154     QTRY_VERIFY(index.data(AbstractTasksModel::StackingOrder).toInt() >= 0);
0155     const int stackingOrder1 = index.data(AbstractTasksModel::StackingOrder).toInt();
0156 
0157     // Create another window to make stacking order change
0158     QModelIndex index2;
0159     auto secondWindow = createSingleWindow(model, QStringLiteral("second test window"), index2);
0160     QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() >= 0);
0161     const int stackingOrder2 = index2.data(AbstractTasksModel::StackingOrder).toInt();
0162     QVERIFY2(
0163         stackingOrder1 < stackingOrder2,
0164         QLatin1String("stackingOrder1: %1 stackingOrder2: %2").arg(QString::number(stackingOrder1), QString::number(stackingOrder2)).toLatin1().constData());
0165 
0166     firstWindow->close();
0167     QCoreApplication::processEvents();
0168     QTRY_VERIFY(index2.data(AbstractTasksModel::StackingOrder).toInt() < stackingOrder2);
0169 }
0170 
0171 void createDesktopFile(const char *fileName, const std::vector<std::string> &lines, QString &path)
0172 {
0173     path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QDir::separator() + QStringLiteral("applications") + QDir::separator()
0174         + QString::fromUtf8(fileName);
0175 
0176     QSignalSpy databaseChangedSpy(KSycoca::self(), &KSycoca::databaseChanged);
0177 
0178     QFile out(path);
0179     if (out.exists()) {
0180         qDebug() << "Removing the old desktop file in" << path;
0181         out.remove();
0182     }
0183 
0184     qDebug() << "Creating a desktop file in" << path;
0185     QVERIFY(out.open(QIODevice::WriteOnly));
0186     out.write("[Desktop Entry]\n");
0187     for (const std::string &l : lines) {
0188         out.write((l + "\n").c_str());
0189     }
0190     out.close();
0191 
0192     KSycoca::self()->ensureCacheValid();
0193     databaseChangedSpy.wait(2500);
0194 }
0195 
0196 void testModelDataFromDesktopFile(const AbstractWindowTasksModel &model)
0197 {
0198     // Case 1: A normal window
0199     std::vector<std::string> lines;
0200     lines.emplace_back("Name=DummyWindow");
0201     lines.emplace_back("GenericName=DummyGenericName");
0202     lines.emplace_back(std::string("Exec=") + TaskManagerTest::samplewidgetwindowExecutablePath);
0203     lines.emplace_back("Terminal=false");
0204     lines.emplace_back("Type=Application");
0205     lines.emplace_back(std::string("Icon=") + QFINDTESTDATA("data/windows/none.png").toStdString());
0206 
0207     // Test generic name, icon and launcher url
0208     QString desktopFilePath;
0209     TestUtils::createDesktopFile(dummyDesktopFileName, lines, desktopFilePath);
0210 
0211     QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted);
0212     QProcess sampleWindowProcess;
0213     sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath));
0214     sampleWindowProcess.setArguments(QStringList{
0215         QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())),
0216         QFINDTESTDATA("data/windows/samplewidgetwindow.png"),
0217     });
0218     sampleWindowProcess.start();
0219     rowsInsertedSpy.wait();
0220 
0221     // Find the window index
0222     auto findWindowIndex = [&model, &sampleWindowProcess](QModelIndex &index) {
0223         const auto results = model.match(model.index(0, 0), Qt::DisplayRole, sampleWindowProcess.arguments().at(0));
0224         QVERIFY(results.size() == 1);
0225         index = results.at(0);
0226         QVERIFY(index.isValid());
0227         qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString();
0228     };
0229 
0230     QModelIndex index;
0231     findWindowIndex(index);
0232 
0233     QCOMPARE(index.data(AbstractTasksModel::AppName).toString(), QStringLiteral("DummyWindow"));
0234     QCOMPARE(index.data(AbstractTasksModel::GenericName).toString(), QStringLiteral("DummyGenericName"));
0235     QCOMPARE(index.data(AbstractTasksModel::LauncherUrl).toUrl(), QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName))));
0236     QCOMPARE(index.data(AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(),
0237              QUrl(QStringLiteral("applications:%1").arg(QString::fromLatin1(dummyDesktopFileName))));
0238 
0239     // Test icon should use the icon from the desktop file (Not the png file filled with red color)
0240     const QIcon windowIcon = index.data(Qt::DecorationRole).value<QIcon>();
0241     QVERIFY(!windowIcon.isNull());
0242     QVERIFY(windowIcon.pixmap(KIconLoader::SizeLarge).toImage().pixelColor(KIconLoader::SizeLarge / 2, KIconLoader::SizeLarge / 2).red() < 200);
0243 
0244     // SingleMainWindow is not set, which implies it can launch a new instance.
0245     QVERIFY(index.data(AbstractTasksModel::CanLaunchNewInstance).toBool());
0246 
0247     QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved);
0248     sampleWindowProcess.terminate();
0249     QVERIFY(rowsRemovedSpy.wait());
0250 
0251     auto testCanLaunchNewInstance = [&](bool canLaunchNewInstance) {
0252         TestUtils::createDesktopFile(dummyDesktopFileName, lines, desktopFilePath);
0253         sampleWindowProcess.start();
0254         rowsInsertedSpy.wait();
0255 
0256         findWindowIndex(index);
0257         QCOMPARE(index.data(AbstractTasksModel::CanLaunchNewInstance).toBool(), canLaunchNewInstance);
0258 
0259         sampleWindowProcess.terminate();
0260         QVERIFY(rowsRemovedSpy.wait());
0261     };
0262 
0263     // Case 2: Set SingleMainWindow or X-GNOME-SingleWindow or both
0264     lines.emplace_back("SingleMainWindow=true");
0265     testCanLaunchNewInstance(false);
0266 
0267     lines.pop_back();
0268     lines.emplace_back("X-GNOME-SingleWindow=true");
0269     testCanLaunchNewInstance(false);
0270 
0271     lines.pop_back();
0272     lines.emplace_back("SingleMainWindow=false");
0273     lines.emplace_back("X-GNOME-SingleWindow=true");
0274     testCanLaunchNewInstance(false);
0275 
0276     lines.pop_back();
0277     lines.pop_back();
0278     lines.emplace_back("SingleMainWindow=true");
0279     lines.emplace_back("X-GNOME-SingleWindow=false");
0280     testCanLaunchNewInstance(false);
0281 
0282     lines.pop_back();
0283     lines.pop_back();
0284     lines.emplace_back("SingleMainWindow=false");
0285     lines.emplace_back("X-GNOME-SingleWindow=false");
0286     testCanLaunchNewInstance(true);
0287 }
0288 
0289 void testRequest(AbstractWindowTasksModel &model)
0290 {
0291     QSignalSpy rowsInsertedSpy(&model, &AbstractWindowTasksModel::rowsInserted);
0292 
0293     QProcess sampleWindowProcess;
0294     sampleWindowProcess.setProgram(QString::fromUtf8(TaskManagerTest::samplewidgetwindowExecutablePath));
0295     sampleWindowProcess.setArguments(QStringList{
0296         QStringLiteral("__testwindow__%1").arg(QString::number(QDateTime::currentDateTime().offsetFromUtc())),
0297     });
0298     sampleWindowProcess.start();
0299     QVERIFY(rowsInsertedSpy.wait());
0300 
0301     // Find the window index
0302     auto findWindowIndex = [&model](QModelIndex &index, const QString &title) {
0303         const auto results = model.match(model.index(0, 0), Qt::DisplayRole, title);
0304         qCritical() << "results for" << title << results << "total:" << model.rowCount();
0305         QVERIFY(results.size() == 1);
0306         index = results.at(0);
0307         QVERIFY(index.isValid());
0308         qDebug() << "Window title:" << index.data(Qt::DisplayRole).toString();
0309     };
0310 
0311     QModelIndex index;
0312     findWindowIndex(index, sampleWindowProcess.arguments().at(0));
0313     QVERIFY(index.isValid());
0314 
0315     QSignalSpy dataChangedSpy(&model, &AbstractWindowTasksModel::dataChanged);
0316 
0317     {
0318         qDebug("requestActivate");
0319         model.requestActivate(index);
0320         QVERIFY(dataChangedSpy.wait());
0321         QVERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool());
0322     }
0323 
0324     {
0325         qDebug("requestNewInstance");
0326         model.requestNewInstance(index);
0327         QVERIFY(rowsInsertedSpy.wait());
0328     }
0329 
0330     {
0331         qDebug("requestToggleMinimized");
0332         model.requestToggleMinimized(index);
0333         QVERIFY(dataChangedSpy.wait());
0334         QTRY_VERIFY(index.data(AbstractTasksModel::IsMinimized).toBool());
0335     }
0336 
0337     {
0338         qDebug("requestToggleMaximized");
0339         model.requestToggleMaximized(index);
0340         QVERIFY(dataChangedSpy.wait());
0341         QTRY_VERIFY(!index.data(AbstractTasksModel::IsMinimized).toBool());
0342         QTRY_VERIFY(index.data(AbstractTasksModel::IsMaximized).toBool());
0343     }
0344 
0345     {
0346         qDebug("requestToggleKeepAbove");
0347         model.requestToggleKeepAbove(index);
0348         QVERIFY(dataChangedSpy.wait());
0349         QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepAbove).toBool());
0350         QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepBelow).toBool());
0351     }
0352 
0353     {
0354         qDebug("requestToggleKeepBelow");
0355         model.requestToggleKeepBelow(index);
0356         QVERIFY(dataChangedSpy.wait());
0357         QTRY_VERIFY(!index.data(AbstractTasksModel::IsKeepAbove).toBool());
0358         QTRY_VERIFY(index.data(AbstractTasksModel::IsKeepBelow).toBool());
0359     }
0360 
0361     // {
0362     //     qDebug("requestToggleFullScreen is only available in Plasma 6");
0363     //     model.requestToggleFullScreen(index);
0364     //     QVERIFY(dataChangedSpy.wait());
0365     //     QTRY_VERIFY(index.data(AbstractTasksModel::IsFullScreen).toBool());
0366     // }
0367 
0368     if (KWindowSystem::isPlatformX11()) {
0369         model.requestToggleShaded(index);
0370         QVERIFY(dataChangedSpy.wait());
0371     }
0372 
0373     QSignalSpy rowsRemovedSpy(&model, &AbstractWindowTasksModel::rowsRemoved);
0374     {
0375         qDebug("requestClose");
0376         model.requestClose(index);
0377         QVERIFY(rowsRemovedSpy.wait());
0378     }
0379 
0380     {
0381         qDebug("Close the new instance");
0382         findWindowIndex(index, QStringLiteral("__test_window_no_title__"));
0383         model.requestClose(index);
0384         QVERIFY(rowsRemovedSpy.wait());
0385     }
0386 }
0387 }