File indexing completed on 2023-09-24 08:11:58
0001 /* 0002 SPDX-FileCopyrightText: 2016 David Rosca <nowrep@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "iconitemtest.h" 0008 0009 #include <QIcon> 0010 #include <QQmlComponent> 0011 #include <QQmlContext> 0012 #include <QQmlEngine> 0013 #include <QQuickItemGrabResult> 0014 #include <QSignalSpy> 0015 0016 #include <KConfigGroup> 0017 #include <KIconEngine> 0018 #include <KIconLoader> 0019 #include <KIconTheme> 0020 0021 #include "plasma/svg.h" 0022 #include "plasma/theme.h" 0023 0024 #include "utils.h" 0025 0026 static bool imageIsEmpty(const QImage &img) 0027 { 0028 for (int i = 0; i < img.width(); ++i) { 0029 for (int j = 0; j < img.height(); ++j) { 0030 if (img.pixel(i, j) != 0) { 0031 return false; 0032 } 0033 } 0034 } 0035 return true; 0036 } 0037 0038 void IconItemTest::initTestCase() 0039 { 0040 Plasma::TestUtils::installPlasmaTheme("breeze"); 0041 Plasma::TestUtils::installPlasmaTheme("breeze-light"); 0042 Plasma::TestUtils::installPlasmaTheme("breeze-dark"); 0043 0044 qputenv("XDG_DATA_DIRS", QByteArray(qgetenv("XDG_DATA_DIRS") + ":" + QFINDTESTDATA("data").toLocal8Bit())); 0045 0046 QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); 0047 0048 if (!QDir(configPath).mkpath(QStringLiteral("."))) { 0049 qFatal("Failed to create test configuration directory."); 0050 } 0051 0052 QFile::remove(configPath); 0053 0054 QIcon::setThemeSearchPaths({QFINDTESTDATA("data/icons")}); 0055 QIcon::setThemeName("test-theme"); 0056 0057 KIconTheme::forceThemeForTests("test-theme"); 0058 KIconTheme::reconfigure(); 0059 KIconLoader::global()->reconfigure(QString()); 0060 0061 m_view = new QQuickView(); 0062 m_view->setSource(QUrl::fromLocalFile(QFINDTESTDATA("data/view.qml"))); 0063 m_view->show(); 0064 QVERIFY(QTest::qWaitForWindowExposed(m_view)); 0065 0066 if (!m_view->rootObject() || !m_view->rootObject()->grabToImage()) { 0067 QSKIP("Cannot grab item to image."); 0068 } 0069 } 0070 0071 void IconItemTest::cleanupTestCase() 0072 { 0073 delete m_view; 0074 } 0075 0076 void IconItemTest::init() 0077 { 0078 Plasma::Theme().setThemeName(QStringLiteral("default")); 0079 } 0080 0081 void IconItemTest::cleanup() 0082 { 0083 qDeleteAll(m_view->rootObject()->childItems()); 0084 } 0085 0086 QQuickItem *IconItemTest::createIconItem() 0087 { 0088 QByteArray iconQml = 0089 "import QtQuick 2.0;" 0090 "import org.kde.plasma.core 2.0 as PlasmaCore;" 0091 "PlasmaCore.IconItem {" 0092 " id: root;" 0093 "}"; 0094 0095 QQmlComponent component(m_view->engine()); 0096 0097 QSignalSpy spy(&component, SIGNAL(statusChanged(QQmlComponent::Status))); 0098 component.setData(iconQml, QUrl("test://iconTest")); 0099 if (component.status() != QQmlComponent::Ready) { 0100 spy.wait(); 0101 } 0102 0103 QQuickItem *item = qobject_cast<QQuickItem *>(component.create(m_view->engine()->rootContext())); 0104 Q_ASSERT(item && qstrcmp(item->metaObject()->className(), "IconItem") == 0); 0105 item->setParentItem(m_view->rootObject()); 0106 return item; 0107 } 0108 0109 QImage IconItemTest::grabImage(QQuickItem *item) 0110 { 0111 QSharedPointer<QQuickItemGrabResult> grab = item->grabToImage(); 0112 QSignalSpy spy(grab.data(), SIGNAL(ready())); 0113 spy.wait(); 0114 return grab->image(); 0115 } 0116 0117 QImage IconItemTest::waitAndGrabImage(QQuickItem *item, int delay) 0118 { 0119 auto window = item->window(); 0120 0121 // Ensure the window is exposed, as otherwise rendering does not happen. 0122 Q_ASSERT(QTest::qWaitForWindowExposed(window)); 0123 0124 // Wait for the provided time. This ensures we can delay so animations have 0125 // time to run. 0126 QTest::qWait(delay); 0127 0128 // Ensure the item is rendered at least once before continuing. Otherwise 0129 // we run the risk of nothing having changed when calling this in quick 0130 // succession. 0131 QSignalSpy frameSwappedSpy(window, &QQuickWindow::frameSwapped); 0132 frameSwappedSpy.wait(100); 0133 0134 auto result = grabImage(item); 0135 0136 // In rare cases, we can trigger a use-after free if we don't explicitly 0137 // disconnect from QQuickWindow::frameSwapped. 0138 frameSwappedSpy.disconnect(window); 0139 0140 return result; 0141 } 0142 0143 Plasma::Svg *IconItemTest::findPlasmaSvg(QQuickItem *item) 0144 { 0145 return item->findChild<Plasma::Svg *>(); 0146 } 0147 0148 void IconItemTest::changeTheme(Plasma::Theme *theme, const QString &themeName) 0149 { 0150 if (theme->themeName() != themeName) { 0151 QSignalSpy spy(theme, SIGNAL(themeChanged())); 0152 theme->setThemeName(themeName); 0153 spy.wait(); 0154 } 0155 } 0156 0157 // ------ Tests 0158 0159 void IconItemTest::loadPixmap() 0160 { 0161 std::unique_ptr<QQuickItem> item(createIconItem()); 0162 QPixmap sourcePixmap(QFINDTESTDATA("data/test_image.png")); 0163 0164 item->setSize(sourcePixmap.size()); 0165 item->setProperty("source", sourcePixmap); 0166 QVERIFY(item->property("valid").toBool()); 0167 0168 QImage capture = grabImage(item.get()); 0169 QCOMPARE(capture, sourcePixmap.toImage().convertToFormat(capture.format())); 0170 QCOMPARE(sourcePixmap, item->property("source").value<QPixmap>()); 0171 } 0172 0173 // tests setting icon from a QImage 0174 void IconItemTest::loadImage() 0175 { 0176 std::unique_ptr<QQuickItem> item(createIconItem()); 0177 QImage sourceImage(QFINDTESTDATA("data/test_image.png")); 0178 0179 item->setSize(sourceImage.size()); 0180 item->setProperty("source", sourceImage); 0181 QVERIFY(item->property("valid").toBool()); 0182 0183 QImage capture = grabImage(item.get()); 0184 QCOMPARE(capture, sourceImage.convertToFormat(capture.format())); 0185 QCOMPARE(sourceImage, item->property("source").value<QImage>()); 0186 } 0187 0188 void IconItemTest::invalidIcon() 0189 { 0190 QString name("tst-plasma-framework-invalid-icon-name"); 0191 KIconLoader iconLoader("tst_plasma-framework"); 0192 if (iconLoader.hasIcon(name)) { 0193 QSKIP("Current icon theme has 'tst-plasma-framework-invalid-icon-name' icon."); 0194 } 0195 0196 QQuickItem *item = createIconItem(); 0197 item->setProperty("source", name); 0198 QVERIFY(!item->property("valid").toBool()); 0199 QVERIFY(imageIsEmpty(grabImage(item))); 0200 } 0201 0202 void IconItemTest::usesPlasmaTheme() 0203 { 0204 // usesPlasmaTheme = true (default) 0205 QQuickItem *item1 = createIconItem(); 0206 item1->setProperty("source", "konversation"); 0207 QVERIFY(item1->property("valid").toBool()); 0208 QCOMPARE(QStringLiteral("konversation"), item1->property("source").toString()); 0209 0210 Plasma::Svg svg; 0211 svg.setContainsMultipleImages(true); 0212 svg.setImagePath("icons/konversation"); 0213 0214 QImage img1 = grabImage(item1); 0215 QImage img2 = svg.image(QSize(item1->width(), item1->height()), "konversation").convertToFormat(img1.format()); 0216 QVERIFY(!imageIsEmpty(img1)); 0217 QVERIFY(!imageIsEmpty(img2)); 0218 QCOMPARE(img1, img2); 0219 0220 // usesPlasmaTheme = false 0221 QQuickItem *item2 = createIconItem(); 0222 item2->setProperty("usesPlasmaTheme", false); 0223 item2->setProperty("source", "konversation"); 0224 0225 img1 = grabImage(item2); 0226 // This depends on konversation icon being different in Plasma Breeze theme 0227 // and our test icon theme 0228 QVERIFY(img1 != img2); 0229 } 0230 0231 void IconItemTest::animation() 0232 { 0233 // animated = true (default) 0234 QQuickItem *item1 = createIconItem(); 0235 item1->setProperty("source", "user-away"); 0236 // first icon is not animated 0237 QImage userAwayImg = grabImage(item1); 0238 0239 item1->setProperty("source", "user-busy"); 0240 grabImage(item1); 0241 item1->setProperty("source", "user-away"); 0242 // animation from user-busy -> user-away 0243 QVERIFY(userAwayImg != waitAndGrabImage(item1)); 0244 0245 // animated = false 0246 QQuickItem *item2 = createIconItem(); 0247 item2->setProperty("animated", false); 0248 item2->setProperty("source", "user-busy"); 0249 QImage userBusyImg = grabImage(item2); 0250 0251 item2->setProperty("source", "user-away"); 0252 QCOMPARE(userAwayImg, grabImage(item2)); 0253 0254 item2->setProperty("source", "user-busy"); 0255 QCOMPARE(userBusyImg, grabImage(item2)); 0256 } 0257 0258 void IconItemTest::animationAfterHide() 0259 { 0260 QQuickItem *item1 = createIconItem(); 0261 QQuickItem *item2 = createIconItem(); 0262 item1->setProperty("source", "user-away"); 0263 item2->setProperty("source", "user-busy"); 0264 // first icon is not animated 0265 QImage userAwayImg = grabImage(item1); 0266 QImage userBusyImg = grabImage(item2); 0267 0268 item1->setProperty("source", "user-busy"); 0269 grabImage(item1); 0270 item1->setProperty("visible", "false"); 0271 item1->setProperty("visible", "true"); 0272 item1->setProperty("source", "user-away"); 0273 // icon was hidden, no animation 0274 QCOMPARE(userAwayImg, grabImage(item1)); 0275 0276 item1->setProperty("source", "user-busy"); 0277 QVERIFY(userBusyImg != waitAndGrabImage(item1)); 0278 } 0279 0280 void IconItemTest::bug_359388() 0281 { 0282 if (!KIconTheme::list().contains("hicolor")) { 0283 // This test depends on hicolor icon theme to resolve the icon. 0284 QSKIP("hicolor icon theme not available"); 0285 } 0286 0287 QString name("bug359388"); 0288 KIconLoader iconLoader("tst_plasma-framework"); 0289 QIcon customThemeIcon(new KIconEngine(name, &iconLoader)); 0290 if (iconLoader.hasIcon(name)) { 0291 QSKIP("Current icon theme has 'bug359388' icon."); 0292 } 0293 0294 iconLoader.addAppDir("tst_plasma-framework", QFINDTESTDATA("data/bug359388")); 0295 0296 QQuickItem *item1 = createIconItem(); 0297 item1->setProperty("source", customThemeIcon); 0298 QVERIFY(item1->property("valid").toBool()); 0299 QCOMPARE(customThemeIcon, item1->property("source").value<QIcon>()); 0300 0301 QQuickItem *item2 = createIconItem(); 0302 item2->setProperty("source", QIcon(QFINDTESTDATA("data/bug359388/hicolor/22x22/apps/" + name + ".svg"))); 0303 QVERIFY(item2->property("valid").toBool()); 0304 0305 QCOMPARE(grabImage(item1), grabImage(item2)); 0306 } 0307 0308 void IconItemTest::loadSvg() 0309 { 0310 QString name("tst-plasma-framework-test-icon"); 0311 0312 QQuickItem *item = createIconItem(); 0313 item->setProperty("animated", false); 0314 item->setSize(QSize(22, 22)); 0315 item->setProperty("source", name); 0316 QVERIFY(item->property("valid").toBool()); 0317 0318 Plasma::Svg *svg; 0319 svg = findPlasmaSvg(item); 0320 Q_ASSERT(svg); 0321 QCOMPARE(svg->imagePath(), QFINDTESTDATA("data/icons/test-theme/apps/22/" + name + ".svg")); 0322 0323 // we only have 32x32 and 22x22 version in the theme, thus 32x32 is a better match. 0324 item->setSize(QSize(64, 64)); 0325 // just to update the icon 0326 grabImage(item); 0327 svg = findPlasmaSvg(item); 0328 Q_ASSERT(svg); 0329 QCOMPARE(svg->imagePath(), QFINDTESTDATA("data/icons/test-theme/apps/32/" + name + ".svg")); 0330 } 0331 0332 void IconItemTest::themeChange() 0333 { 0334 // Icon from Plasma theme 0335 QQuickItem *item1 = createIconItem(); 0336 item1->setProperty("animated", false); 0337 item1->setProperty("source", "zoom-fit-height"); 0338 Plasma::Svg *svg1 = item1->findChild<Plasma::Svg *>(); 0339 changeTheme(svg1->theme(), "breeze-light"); 0340 QImage img1 = grabImage(item1); 0341 changeTheme(svg1->theme(), "breeze-dark"); 0342 QImage img2 = grabImage(item1); 0343 QVERIFY(img1 != img2); 0344 0345 // Icon from icon theme 0346 QQuickItem *item2 = createIconItem(); 0347 item2->setProperty("animated", false); 0348 item2->setProperty("width", 22); 0349 item2->setProperty("height", 22); 0350 item2->setProperty("source", "tst-plasma-framework-test-icon"); 0351 Plasma::Svg *svg2 = item2->findChild<Plasma::Svg *>(); 0352 changeTheme(svg2->theme(), "breeze-light"); 0353 img1 = grabImage(item2); 0354 changeTheme(svg2->theme(), "breeze-dark"); 0355 img2 = grabImage(item2); 0356 QVERIFY(img1 != img2); 0357 } 0358 0359 void IconItemTest::qiconFromTheme() 0360 { 0361 // Icon from Plasma theme 0362 QQuickItem *item1 = createIconItem(); 0363 QIcon icon1 = QIcon::fromTheme("konversation"); 0364 item1->setProperty("source", icon1); 0365 QVERIFY(item1->findChild<Plasma::Svg *>()); 0366 QVERIFY(!imageIsEmpty(grabImage(item1))); 0367 QCOMPARE(icon1, item1->property("source").value<QIcon>()); 0368 0369 // Icon from icon theme 0370 QQuickItem *item2 = createIconItem(); 0371 QIcon icon2 = QIcon::fromTheme("tst-plasma-framework-test-icon"); 0372 item2->setProperty("source", icon2); 0373 QVERIFY(item2->findChild<Plasma::Svg *>()); 0374 QVERIFY(!imageIsEmpty(grabImage(item2))); 0375 QCOMPARE(icon2, item2->property("source").value<QIcon>()); 0376 } 0377 0378 void IconItemTest::changeColorGroup() 0379 { 0380 // Icon from Plasma theme 0381 QQuickItem *item = createIconItem(); 0382 item->setProperty("animated", false); 0383 item->setProperty("source", "zoom-fit-height"); 0384 Plasma::Svg *svg = item->findChild<Plasma::Svg *>(); 0385 // not using "breeze" theme as that one follows system color scheme 0386 // and that one might not have a complementary group or a broken one 0387 changeTheme(svg->theme(), "breeze-light"); 0388 QSignalSpy spy(svg, SIGNAL(repaintNeeded())); 0389 QVERIFY(spy.isValid()); 0390 QImage img1 = grabImage(item); 0391 item->setProperty("colorGroup", Plasma::Theme::ComplementaryColorGroup); 0392 QTRY_VERIFY(spy.count() == 1); 0393 QImage img2 = grabImage(item); 0394 QVERIFY(img1 != img2); 0395 } 0396 0397 void IconItemTest::animatingActiveChange() 0398 { 0399 QQuickItem *item1 = createIconItem(); 0400 item1->setProperty("animated", false); 0401 item1->setProperty("source", "tst-plasma-framework-test-icon"); 0402 QImage img1 = grabImage(item1); 0403 0404 QQuickItem *item2 = createIconItem(); 0405 item2->setProperty("animated", false); 0406 item2->setProperty("active", true); 0407 item2->setProperty("source", "tst-plasma-framework-test-icon"); 0408 QImage img2 = grabImage(item2); 0409 QVERIFY(img1 != img2); 0410 0411 item1->setProperty("active", true); 0412 img1 = grabImage(item1); 0413 QVERIFY(img1 != img2); // animation is running 0414 } 0415 0416 void IconItemTest::animatingEnabledChange() 0417 { 0418 QQuickItem *item1 = createIconItem(); 0419 item1->setProperty("animated", false); 0420 item1->setProperty("source", "tst-plasma-framework-test-icon"); 0421 QImage img1 = grabImage(item1); 0422 0423 QQuickItem *item2 = createIconItem(); 0424 item2->setProperty("animated", false); 0425 item2->setProperty("enabled", false); 0426 item2->setProperty("source", "tst-plasma-framework-test-icon"); 0427 QImage img2 = grabImage(item2); 0428 QVERIFY(img1 != img2); 0429 0430 item1->setProperty("enabled", false); 0431 img1 = grabImage(item1); 0432 QVERIFY(img1 != img2); // animation is running 0433 } 0434 0435 void IconItemTest::windowChanged() 0436 { 0437 QQuickItem *item = createIconItem(); 0438 item->setProperty("animated", false); 0439 item->setProperty("source", "tst-plasma-framework-test-icon"); 0440 QImage img = grabImage(item); 0441 0442 QQuickView newView; 0443 newView.setSource(QUrl::fromLocalFile(QFINDTESTDATA("data/view.qml"))); 0444 newView.show(); 0445 QVERIFY(QTest::qWaitForWindowExposed(&newView)); 0446 0447 item->setProperty("visible", false); 0448 item->setParentItem(newView.rootObject()); 0449 item->setProperty("visible", true); 0450 QCOMPARE(grabImage(item), img); 0451 } 0452 0453 void IconItemTest::paintedSize() 0454 { 0455 QQuickItem *item = createIconItem(); 0456 0457 QCOMPARE(item->property("paintedWidth").toInt(), item->property("implicitWidth").toInt()); 0458 QCOMPARE(item->property("paintedHeight").toInt(), item->property("implicitHeight").toInt()); 0459 0460 item->setWidth(40); 0461 item->setHeight(40); 0462 0463 QCOMPARE(item->property("paintedWidth").toInt(), 32); 0464 QCOMPARE(item->property("paintedHeight").toInt(), 32); 0465 0466 QIcon landscapeIcon(QPixmap(40, 35)); 0467 item->setProperty("source", landscapeIcon); 0468 grabImage(item); // basically just to force loading the pixmap 0469 0470 // expanded to fit IconItem size whilst keeping aspect ratio 0471 // width should be rounded to icon size, ie. 32 is next smallest 0472 QCOMPARE(item->property("paintedWidth").toInt(), 32); 0473 // height should still match aspect ratio, so *not* 24! 0474 QCOMPARE(item->property("paintedHeight").toInt(), 28); 0475 0476 QIcon portraitIcon(QPixmap(15, 40)); 0477 item->setProperty("source", portraitIcon); 0478 grabImage(item); 0479 0480 QCOMPARE(item->property("paintedWidth").toInt(), 12); 0481 QCOMPARE(item->property("paintedHeight").toInt(), 32); 0482 0483 item->setWidth(400); 0484 item->setHeight(400); 0485 0486 grabImage(item); 0487 0488 QCOMPARE(item->property("paintedWidth").toInt(), 150); 0489 QCOMPARE(item->property("paintedHeight").toInt(), 400); 0490 } 0491 0492 void IconItemTest::implicitSize() 0493 { 0494 KConfigGroup cg(KSharedConfig::openConfig(), "DialogIcons"); 0495 cg.writeEntry("Size", 22); 0496 cg.sync(); 0497 KIconLoader::global()->reconfigure(QString()); 0498 0499 QQuickItem *item = createIconItem(); 0500 0501 // qreal cast needed as QTest::qCompare<double, int> fails to link 0502 QCOMPARE(item->implicitWidth(), qreal(22)); 0503 QCOMPARE(item->implicitHeight(), qreal(22)); 0504 0505 QSignalSpy widthSpy(item, &QQuickItem::implicitWidthChanged); 0506 QVERIFY(widthSpy.isValid()); 0507 QSignalSpy heightSpy(item, &QQuickItem::implicitHeightChanged); 0508 QVERIFY(heightSpy.isValid()); 0509 0510 cg.writeEntry("Size", 64); 0511 cg.sync(); 0512 KIconLoader::global()->reconfigure(QString()); 0513 // merely changing the setting and calling reconfigure won't emit this signal, 0514 // the KCM uses a method "newIconLoader" method which does that but it's deprecated 0515 Q_EMIT KIconLoader::global()->iconLoaderSettingsChanged(); 0516 0517 QCOMPARE(widthSpy.count(), 1); 0518 QCOMPARE(heightSpy.count(), 1); 0519 0520 QCOMPARE(item->implicitWidth(), qreal(64)); 0521 QCOMPARE(item->implicitHeight(), qreal(64)); 0522 } 0523 0524 void IconItemTest::nonSquareImplicitSize() 0525 { 0526 QQuickItem *item1 = createIconItem(); 0527 0528 // Both file:///foo and /foo must behave the same 0529 item1->setProperty("source", QFINDTESTDATA("data/test_nonsquare.png")); 0530 0531 QCOMPARE(item1->implicitWidth(), qreal(150)); 0532 QCOMPARE(item1->implicitHeight(), qreal(50)); 0533 0534 QQuickItem *item2 = createIconItem(); 0535 0536 item2->setProperty("source", QUrl::fromLocalFile(QFINDTESTDATA("data/test_nonsquare.png"))); 0537 0538 QCOMPARE(item2->implicitWidth(), item1->implicitWidth()); 0539 QCOMPARE(item2->implicitHeight(), item1->implicitHeight()); 0540 } 0541 0542 void IconItemTest::roundToIconSize() 0543 { 0544 QQuickItem *item = createIconItem(); 0545 0546 item->setWidth(25); 0547 item->setHeight(25); 0548 QVERIFY(item->property("paintedWidth").toInt() != 25); 0549 QVERIFY(item->property("paintedHeight").toInt() != 25); 0550 0551 QSignalSpy paintedSizeSpy(item, SIGNAL(paintedSizeChanged())); 0552 QSignalSpy roundToIconSizeSpy(item, SIGNAL(roundToIconSizeChanged())); 0553 0554 item->setProperty("roundToIconSize", false); 0555 0556 QTRY_COMPARE(paintedSizeSpy.count(), 1); 0557 QTRY_COMPARE(roundToIconSizeSpy.count(), 1); 0558 QVERIFY(item->property("paintedWidth").toInt() == 25); 0559 QVERIFY(item->property("paintedHeight").toInt() == 25); 0560 } 0561 0562 QTEST_MAIN(IconItemTest) 0563 0564 #include "moc_iconitemtest.cpp"