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 }