File indexing completed on 2024-12-08 10:17:16
0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com> 0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT 0003 0004 // First included header is the public header of the class we are testing; 0005 // this forces the header to be self-contained. 0006 #include "swatchbook.h" 0007 // Second, the private implementation. 0008 #include "swatchbook_p.h" // IWYU pragma: keep 0009 0010 #include "constpropagatinguniquepointer.h" 0011 #include "helper.h" 0012 #include "helperqttypes.h" 0013 #include "rgbcolorspacefactory.h" 0014 #include <qbytearray.h> 0015 #include <qcolor.h> 0016 #include <qcolordialog.h> 0017 #include <qglobal.h> 0018 #include <qlist.h> 0019 #include <qnamespace.h> 0020 #include <qobject.h> 0021 #include <qpoint.h> 0022 #include <qsharedpointer.h> 0023 #include <qsize.h> 0024 #include <qstring.h> 0025 #include <qstyle.h> 0026 #include <qstylefactory.h> 0027 #include <qstyleoption.h> 0028 #include <qtest.h> 0029 #include <qtestcase.h> 0030 #include <qtestdata.h> 0031 #include <qtestkeyboard.h> 0032 #include <type_traits> 0033 #include <utility> 0034 0035 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0036 #include <qtmetamacros.h> 0037 #else 0038 #include <qobjectdefs.h> 0039 #include <qstringlist.h> 0040 #endif 0041 0042 namespace PerceptualColor 0043 { 0044 class RgbColorSpace; 0045 0046 class TestSwatchBook : public QObject 0047 { 0048 Q_OBJECT 0049 0050 public: 0051 explicit TestSwatchBook(QObject *parent = nullptr) 0052 : QObject(parent) 0053 { 0054 } 0055 0056 private: 0057 QSharedPointer<PerceptualColor::RgbColorSpace> m_rgbColorSpace = RgbColorSpaceFactory::createSrgb(); 0058 0059 void provideStyleNamesAsData() 0060 { 0061 QTest::addColumn<QString>("styleName"); 0062 const auto container = QStyleFactory::keys(); 0063 for (const QString ¤tStyleName : std::as_const(container)) { 0064 QTest::newRow(currentStyleName.toUtf8().constData()) // 0065 << currentStyleName; 0066 } 0067 } 0068 0069 private Q_SLOTS: 0070 void initTestCase() 0071 { 0072 // Called before the first test function is executed 0073 } 0074 0075 void cleanupTestCase() 0076 { 0077 // Called after the last test function was executed 0078 } 0079 0080 void init() 0081 { 0082 // Called before each test function is executed 0083 } 0084 0085 void cleanup() 0086 { 0087 // Called after every test function 0088 } 0089 0090 void testConstructorDestructor() 0091 { 0092 SwatchBook testObject(m_rgbColorSpace, // 0093 wcsBasicColors(m_rgbColorSpace), 0094 {}); 0095 } 0096 0097 void testConstructorDefaultValues() 0098 { 0099 SwatchBook testObject(m_rgbColorSpace, // 0100 wcsBasicColors(m_rgbColorSpace), 0101 {}); 0102 // Verify that initially one of the colors of the swatch book 0103 // is actually selected (no -1 as index): 0104 QVERIFY(testObject.d_pointer->m_selectedColumn >= 0); 0105 QVERIFY(testObject.d_pointer->m_selectedRow >= 0); 0106 // Verify that the initial color conforms to QColorDialog 0107 QColorDialog reference; 0108 QCOMPARE(testObject.currentColor(), reference.currentColor()); 0109 } 0110 0111 void testMinimalSizeHint() 0112 { 0113 SwatchBook testWidget(m_rgbColorSpace, // 0114 wcsBasicColors(m_rgbColorSpace), 0115 {}); 0116 QVERIFY2(testWidget.minimumSizeHint().width() > 0, // 0117 "minimalSizeHint width is implemented."); 0118 QVERIFY2(testWidget.minimumSizeHint().height() > 0, // 0119 "minimalSizeHint height is implemented."); 0120 } 0121 0122 void testSizeHint() 0123 { 0124 SwatchBook testWidget(m_rgbColorSpace, // 0125 wcsBasicColors(m_rgbColorSpace), 0126 {}); 0127 QVERIFY2( // 0128 testWidget.sizeHint().width() // 0129 >= testWidget.minimumSizeHint().width(), // 0130 "sizeHint width is bigger than or equal to minimalSizeHint width."); 0131 QVERIFY2( // 0132 testWidget.sizeHint().height() // 0133 >= testWidget.minimumSizeHint().height(), 0134 "sizeHint height is bigger than or equal to minimalSizeHint " 0135 "height."); 0136 } 0137 0138 #ifndef MSVC_DLL 0139 // The automatic export of otherwise private symbols on MSVC 0140 // shared libraries via CMake's WINDOWS_EXPORT_ALL_SYMBOLS property 0141 // does not work well for Qt meta objects, resulting in non-functional 0142 // signals. Since the following unit tests require signals, it cannot be 0143 // built for MSVC shared libraries. 0144 0145 void testCurrentColor() 0146 { 0147 SwatchBook testWidget(m_rgbColorSpace, // 0148 wcsBasicColors(m_rgbColorSpace), 0149 {}); 0150 // Prepare test 0151 QObject scopeMarker; 0152 QColor lastSignalColor; 0153 int signalCount = 0; 0154 connect( // 0155 &testWidget, 0156 &SwatchBook::currentColorChanged, 0157 &scopeMarker, 0158 [&lastSignalColor, &signalCount](const QColor &newCurrentColor) { 0159 lastSignalColor = newCurrentColor; 0160 ++signalCount; 0161 }); 0162 // Initialize the swatch book widget and lastSignalColor to a 0163 // defined state 0164 testWidget.d_pointer->selectSwatch(0, 0); 0165 0166 // Test 0167 const QColor oldColor = lastSignalColor; 0168 testWidget.d_pointer->selectSwatch(0, 1); 0169 QVERIFY(oldColor != lastSignalColor); // Signal has been emitted 0170 0171 testWidget.setCurrentColor(Qt::red); 0172 QCOMPARE(testWidget.currentColor(), Qt::red); 0173 QCOMPARE(lastSignalColor, Qt::red); 0174 const int oldSignalCount = signalCount; 0175 testWidget.setCurrentColor(Qt::green); 0176 QCOMPARE(testWidget.currentColor(), Qt::green); 0177 QCOMPARE(signalCount, oldSignalCount + 1); 0178 QCOMPARE(lastSignalColor, Qt::green); 0179 // Setting it again should not trigger a new signal 0180 testWidget.setCurrentColor(Qt::green); 0181 QCOMPARE(testWidget.currentColor(), Qt::green); 0182 QCOMPARE(signalCount, oldSignalCount + 1); 0183 QCOMPARE(lastSignalColor, Qt::green); 0184 0185 // Test conformance with QColorDialog when assigning invalid colors 0186 testWidget.setCurrentColor(Qt::blue); 0187 QColorDialog myQColorDialog; 0188 myQColorDialog.setCurrentColor(Qt::blue); 0189 testWidget.setCurrentColor(QColor()); 0190 myQColorDialog.setCurrentColor(QColor()); 0191 QCOMPARE(testWidget.currentColor(), myQColorDialog.currentColor()); 0192 QCOMPARE(lastSignalColor, myQColorDialog.currentColor()); 0193 } 0194 0195 #endif 0196 0197 void testPatchSpacingH_data() 0198 { 0199 provideStyleNamesAsData(); 0200 } 0201 0202 void testPatchSpacingH() 0203 { 0204 QFETCH(QString, styleName); 0205 QStyle *style = QStyleFactory::create(styleName); 0206 { 0207 // Own block to make sure style will be deleted _after_ testWidget 0208 // has been destroyed. 0209 SwatchBook testWidget(m_rgbColorSpace, // 0210 wcsBasicColors(m_rgbColorSpace), 0211 Qt::Orientation::Horizontal); 0212 testWidget.setStyle(style); 0213 QVERIFY(testWidget.d_pointer->horizontalPatchSpacing() > 0); 0214 QVERIFY(testWidget.d_pointer->verticalPatchSpacing() > 0); 0215 QVERIFY( // 0216 testWidget.d_pointer->horizontalPatchSpacing() // 0217 > testWidget.d_pointer->verticalPatchSpacing()); 0218 } 0219 delete style; 0220 } 0221 0222 void testPatchSpacingV_data() 0223 { 0224 provideStyleNamesAsData(); 0225 } 0226 0227 void testPatchSpacingV() 0228 { 0229 QFETCH(QString, styleName); 0230 QStyle *style = QStyleFactory::create(styleName); 0231 { 0232 // Own block to make sure style will be deleted _after_ testWidget 0233 // has been destroyed. 0234 SwatchBook testWidget(m_rgbColorSpace, // 0235 wcsBasicColors(m_rgbColorSpace), 0236 Qt::Orientation::Vertical); 0237 testWidget.setStyle(style); 0238 QVERIFY(testWidget.d_pointer->horizontalPatchSpacing() > 0); 0239 QVERIFY(testWidget.d_pointer->verticalPatchSpacing() > 0); 0240 QVERIFY( // 0241 testWidget.d_pointer->horizontalPatchSpacing() // 0242 < testWidget.d_pointer->verticalPatchSpacing()); 0243 } 0244 delete style; 0245 } 0246 0247 void testPatchSpacingNone_data() 0248 { 0249 provideStyleNamesAsData(); 0250 } 0251 0252 void testPatchSpacingNone() 0253 { 0254 QFETCH(QString, styleName); 0255 QStyle *style = QStyleFactory::create(styleName); 0256 { 0257 // Own block to make sure style will be deleted _after_ testWidget 0258 // has been destroyed. 0259 SwatchBook testWidget(m_rgbColorSpace, // 0260 wcsBasicColors(m_rgbColorSpace), 0261 {}); 0262 testWidget.setStyle(style); 0263 QVERIFY(testWidget.d_pointer->horizontalPatchSpacing() > 0); 0264 QVERIFY(testWidget.d_pointer->verticalPatchSpacing() > 0); 0265 QVERIFY( // 0266 testWidget.d_pointer->horizontalPatchSpacing() // 0267 == testWidget.d_pointer->verticalPatchSpacing()); 0268 } 0269 delete style; 0270 } 0271 0272 void testPatchSpacingBoth_data() 0273 { 0274 provideStyleNamesAsData(); 0275 } 0276 0277 void testPatchSpacingBoth() 0278 { 0279 QFETCH(QString, styleName); 0280 QStyle *style = QStyleFactory::create(styleName); 0281 { 0282 // Own block to make sure style will be deleted _after_ testWidget 0283 // has been destroyed. 0284 SwatchBook testWidget( // 0285 m_rgbColorSpace, // 0286 wcsBasicColors(m_rgbColorSpace), // 0287 Qt::Orientation::Horizontal | Qt::Orientation::Vertical); 0288 testWidget.setStyle(style); 0289 QVERIFY(testWidget.d_pointer->horizontalPatchSpacing() > 0); 0290 QVERIFY(testWidget.d_pointer->verticalPatchSpacing() > 0); 0291 QVERIFY( // 0292 testWidget.d_pointer->horizontalPatchSpacing() // 0293 == testWidget.d_pointer->verticalPatchSpacing()); 0294 } 0295 delete style; 0296 } 0297 0298 void testPatchSize_data() 0299 { 0300 provideStyleNamesAsData(); 0301 } 0302 0303 void testPatchSize() 0304 { 0305 QFETCH(QString, styleName); 0306 QStyle *style = QStyleFactory::create(styleName); 0307 { 0308 // Own block to make sure style will be deleted _after_ testWidget 0309 // has been destroyed. 0310 SwatchBook testWidget(m_rgbColorSpace, // 0311 wcsBasicColors(m_rgbColorSpace), // 0312 {}); 0313 testWidget.setStyle(style); 0314 QVERIFY(!testWidget.d_pointer->patchSizeInner().isEmpty()); 0315 QVERIFY(!testWidget.d_pointer->patchSizeOuter().isEmpty()); 0316 QVERIFY( // 0317 testWidget.d_pointer->patchSizeOuter().width() // 0318 >= testWidget.d_pointer->patchSizeInner().width()); 0319 QVERIFY( // 0320 testWidget.d_pointer->patchSizeOuter().height() // 0321 >= testWidget.d_pointer->patchSizeInner().height()); 0322 0323 // Test also some design properties: 0324 QVERIFY( // 0325 testWidget.d_pointer->patchSizeInner().width() // 0326 >= testWidget.d_pointer->horizontalPatchSpacing()); 0327 QVERIFY( // 0328 testWidget.d_pointer->patchSizeInner().height() // 0329 >= testWidget.d_pointer->verticalPatchSpacing()); 0330 } 0331 delete style; 0332 } 0333 0334 void testRetranslateUI() 0335 { 0336 SwatchBook testWidget(m_rgbColorSpace, // 0337 wcsBasicColors(m_rgbColorSpace), // 0338 {}); 0339 // Test that function call does not crash: 0340 testWidget.d_pointer->retranslateUi(); 0341 } 0342 0343 void testInitStyleOptions() 0344 { 0345 SwatchBook testWidget(m_rgbColorSpace, // 0346 wcsBasicColors(m_rgbColorSpace), // 0347 {}); 0348 0349 // Test that function call does not crash with regular object: 0350 QStyleOptionFrame temp; 0351 testWidget.d_pointer->initStyleOption(&temp); 0352 0353 // Test that function does not crash with nullptr: 0354 testWidget.d_pointer->initStyleOption(nullptr); 0355 } 0356 0357 void testOffset_data() 0358 { 0359 provideStyleNamesAsData(); 0360 } 0361 0362 void testOffset() 0363 { 0364 QFETCH(QString, styleName); 0365 QStyle *style = QStyleFactory::create(styleName); 0366 { 0367 // Own block to make sure style will be deleted _after_ testWidget 0368 // has been destroyed. 0369 SwatchBook testWidget(m_rgbColorSpace, // 0370 wcsBasicColors(m_rgbColorSpace), // 0371 {}); 0372 testWidget.setStyle(style); 0373 QStyleOptionFrame temp; 0374 testWidget.d_pointer->initStyleOption(&temp); 0375 QVERIFY(testWidget.d_pointer->offset(temp).x() >= 0); 0376 QVERIFY(testWidget.d_pointer->offset(temp).y() >= 0); 0377 } 0378 delete style; 0379 } 0380 0381 void testKeyboard() 0382 { 0383 SwatchBook testWidget(m_rgbColorSpace, wcsBasicColors(m_rgbColorSpace), {}); 0384 const QListSizeType count = // 0385 qMax(testWidget.d_pointer->m_swatches.iCount(), // 0386 testWidget.d_pointer->m_swatches.jCount()) 0387 // Add 1 to exceed the possible number of fields (crash test) 0388 + 1; 0389 0390 // Starting point is (0, 0) when no swatch was selected before 0391 testWidget.setCurrentColor( 0392 // A color that is not in the swatch book: 0393 QColor(1, 2, 3)); 0394 QTest::keyClick(&testWidget, Qt::Key_Left); 0395 QCOMPARE(testWidget.d_pointer->m_selectedColumn, 0); 0396 QCOMPARE(testWidget.d_pointer->m_selectedRow, 0); 0397 0398 // Test keys LTR 0399 testWidget.setLayoutDirection(Qt::LayoutDirection::LeftToRight); 0400 for (int i = 0; i < count; ++i) { 0401 QTest::keyClick(&testWidget, Qt::Key_Right); 0402 } 0403 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0404 testWidget.d_pointer->m_swatches.iCount() - 1); 0405 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0406 0); 0407 for (int i = 0; i < count; ++i) { 0408 QTest::keyClick(&testWidget, Qt::Key_Left); 0409 } 0410 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0411 0); 0412 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0413 0); 0414 QTest::keyClick(&testWidget, Qt::Key_End); 0415 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0416 testWidget.d_pointer->m_swatches.iCount() - 1); 0417 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0418 0); 0419 QTest::keyClick(&testWidget, Qt::Key_Home); 0420 for (int i = 0; i < count; ++i) { 0421 QTest::keyClick(&testWidget, Qt::Key_Left); 0422 } 0423 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0424 0); 0425 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0426 0); 0427 0428 // Key tests RTL 0429 testWidget.setLayoutDirection(Qt::LayoutDirection::RightToLeft); 0430 for (int i = 0; i < count; ++i) { 0431 QTest::keyClick(&testWidget, Qt::Key_Left); 0432 } 0433 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0434 testWidget.d_pointer->m_swatches.iCount() - 1); 0435 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0436 0); 0437 for (int i = 0; i < count; ++i) { 0438 QTest::keyClick(&testWidget, Qt::Key_Right); 0439 } 0440 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0441 0); 0442 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0443 0); 0444 QTest::keyClick(&testWidget, Qt::Key_End); 0445 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0446 testWidget.d_pointer->m_swatches.iCount() - 1); 0447 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0448 0); 0449 QTest::keyClick(&testWidget, Qt::Key_Home); 0450 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0451 0); 0452 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0453 0); 0454 0455 // Key tests vertical 0456 for (int i = 0; i < count; ++i) { 0457 QTest::keyClick(&testWidget, Qt::Key_Down); 0458 } 0459 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0460 0); 0461 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0462 testWidget.d_pointer->m_swatches.jCount() - 1); 0463 for (int i = 0; i < count; ++i) { 0464 QTest::keyClick(&testWidget, Qt::Key_Up); 0465 } 0466 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0467 0); 0468 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0469 0); 0470 QTest::keyClick(&testWidget, Qt::Key_PageDown); 0471 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0472 0); 0473 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0474 testWidget.d_pointer->m_swatches.jCount() - 1); 0475 QTest::keyClick(&testWidget, Qt::Key_PageUp); 0476 QCOMPARE(testWidget.d_pointer->m_selectedColumn, // 0477 0); 0478 QCOMPARE(testWidget.d_pointer->m_selectedRow, // 0479 0); 0480 } 0481 }; 0482 0483 } // namespace PerceptualColor 0484 0485 QTEST_MAIN(PerceptualColor::TestSwatchBook) 0486 0487 // The following “include” is necessary because we do not use a header file: 0488 #include "testswatchbook.moc"