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 &currentStyleName : 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"