/* SPDX-FileCopyrightText: 2009 Stephen Kelly SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef PROXY_MODEL_TEST_H #define PROXY_MODEL_TEST_H #include #include #include "dynamictreemodel.h" #include "indexfinder.h" #include "modelcommander.h" #include "modelspy.h" #include "persistentchangelist.h" #include "proxymodeltestsuite_export.h" typedef QList SignalList; Q_DECLARE_METATYPE(SignalList) enum Persistence { LazyPersistence, ImmediatePersistence, }; enum SourceModel { DynamicTree, IntermediateProxy, }; class PROXYMODELTESTSUITE_EXPORT BuiltinTestDataInterface { public: virtual ~BuiltinTestDataInterface() { } private: virtual void testInsertWhenEmptyData() = 0; virtual void testInsertInRootData() = 0; virtual void testInsertInTopLevelData() = 0; virtual void testInsertInSecondLevelData() = 0; virtual void testRemoveFromRootData() = 0; virtual void testRemoveFromTopLevelData() = 0; virtual void testRemoveFromSecondLevelData() = 0; virtual void testMoveFromRootData() = 0; virtual void testMoveFromTopLevelData() = 0; virtual void testMoveFromSecondLevelData() = 0; virtual void testModifyInRootData() = 0; virtual void testModifyInTopLevelData() = 0; virtual void testModifyInSecondLevelData() = 0; }; class PROXYMODELTESTSUITE_EXPORT BuiltinTestInterface : BuiltinTestDataInterface { public: ~BuiltinTestInterface() override { } private: virtual void testInsertWhenEmpty_data() = 0; virtual void testInsertWhenEmpty() = 0; virtual void testInsertInRoot_data() = 0; virtual void testInsertInRoot() = 0; virtual void testInsertInTopLevel_data() = 0; virtual void testInsertInTopLevel() = 0; virtual void testInsertInSecondLevel_data() = 0; virtual void testInsertInSecondLevel() = 0; virtual void testRemoveFromRoot_data() = 0; virtual void testRemoveFromRoot() = 0; virtual void testRemoveFromTopLevel_data() = 0; virtual void testRemoveFromTopLevel() = 0; virtual void testRemoveFromSecondLevel_data() = 0; virtual void testRemoveFromSecondLevel() = 0; virtual void testMoveFromRoot_data() = 0; virtual void testMoveFromRoot() = 0; virtual void testMoveFromTopLevel_data() = 0; virtual void testMoveFromTopLevel() = 0; virtual void testMoveFromSecondLevel_data() = 0; virtual void testMoveFromSecondLevel() = 0; virtual void testModifyInRoot_data() = 0; virtual void testModifyInRoot() = 0; virtual void testModifyInTopLevel_data() = 0; virtual void testModifyInTopLevel() = 0; virtual void testModifyInSecondLevel_data() = 0; virtual void testModifyInSecondLevel() = 0; }; class PROXYMODELTESTSUITE_EXPORT ProxyModelTest : public QObject, protected BuiltinTestInterface { Q_OBJECT public: ProxyModelTest(QObject *parent = nullptr); ~ProxyModelTest() override { } void setLazyPersistence(Persistence persistence); void setUseIntermediateProxy(SourceModel sourceModel); DynamicTreeModel *rootModel() const { return m_rootModel; } QAbstractItemModel *sourceModel() const { return m_sourceModel; } QAbstractProxyModel *proxyModel() const { return m_proxyModel; } ModelSpy *modelSpy() const { return m_modelSpy; } PersistentIndexChange getChange(IndexFinder sourceFinder, int start, int end, int difference, bool toInvalid = false); QVariantList noSignal() const { return QVariantList() << NoSignal; } QVariantList getSignal(SignalType type, IndexFinder parentFinder, int start, int end) const { return QVariantList() << type << QVariant::fromValue(parentFinder) << start << end; } QVariantList getSignal(SignalType type, IndexFinder srcFinder, int start, int end, IndexFinder destFinder, int destStart) const { return QVariantList() << type << QVariant::fromValue(srcFinder) << start << end << QVariant::fromValue(destFinder) << destStart; } QVariantList getSignal(SignalType type, IndexFinder topLeftFinder, IndexFinder bottomRightFinder) const { return QVariantList() << type << QVariant::fromValue(topLeftFinder) << QVariant::fromValue(bottomRightFinder); } protected: virtual QAbstractProxyModel *getProxy() = 0; void doCleanupTestCase() { cleanupTestCase(); } void doCleanup() { cleanup(); } Q_SIGNALS: void testInsertWhenEmptyData() override; void testInsertInRootData() override; void testInsertInTopLevelData() override; void testInsertInSecondLevelData() override; void testRemoveFromRootData() override; void testRemoveFromTopLevelData() override; void testRemoveFromSecondLevelData() override; void testMoveFromRootData() override; void testMoveFromTopLevelData() override; void testMoveFromSecondLevelData() override; void testModifyInRootData() override; void testModifyInTopLevelData() override; void testModifyInSecondLevelData() override; protected Q_SLOTS: void testMappings(); void verifyModel(const QModelIndex &parent, int start, int end); void verifyModel(const QModelIndex &parent, int start, int end, const QModelIndex &destParent, int dest); void verifyModel(const QModelIndex &topLeft, const QModelIndex &bottomRight); private Q_SLOTS: void init(); void cleanup(); void cleanupTestCase(); void testEmptyModel(); void testSourceReset(); void testDestroyModel(); void testInsertWhenEmpty_data() override { testInsertWhenEmptyData(); } void testInsertWhenEmpty() override { doTest(); } void testInsertInRoot_data() override { testInsertInRootData(); } void testInsertInRoot() override { doTest(); } void testInsertInTopLevel_data() override { testInsertInTopLevelData(); } void testInsertInTopLevel() override { doTest(); } void testInsertInSecondLevel_data() override { testInsertInSecondLevelData(); } void testInsertInSecondLevel() override { doTest(); } void testRemoveFromRoot_data() override { testRemoveFromRootData(); } void testRemoveFromRoot() override { doTest(); } void testRemoveFromTopLevel_data() override { testRemoveFromTopLevelData(); } void testRemoveFromTopLevel() override { doTest(); } void testRemoveFromSecondLevel_data() override { testRemoveFromSecondLevelData(); } void testRemoveFromSecondLevel() override { doTest(); } void testMoveFromRoot_data() override { testMoveFromRootData(); } void testMoveFromRoot() override { doTest(); } void testMoveFromTopLevel_data() override { testMoveFromTopLevelData(); } void testMoveFromTopLevel() override { doTest(); } void testMoveFromSecondLevel_data() override { testMoveFromSecondLevelData(); } void testMoveFromSecondLevel() override { doTest(); } void testModifyInRoot_data() override { testModifyInRootData(); } void testModifyInRoot() override { doTest(); } void testModifyInTopLevel_data() override { testModifyInTopLevelData(); } void testModifyInTopLevel() override { doTest(); } void testModifyInSecondLevel_data() override { testModifyInSecondLevelData(); } void testModifyInSecondLevel() override { doTest(); } protected: void connectTestSignals(QObject *receiver); void disconnectTestSignals(QObject *receiver); void connectProxy(QAbstractProxyModel *proxyModel); void doTestMappings(const QModelIndex &parent); void initRootModel(DynamicTreeModel *rootModel, const QString ¤tTest, const QString ¤tTag); void doTest(); void handleSignal(QVariantList expected); QVariantList getResultSignal(); int getChange(bool sameParent, int start, int end, int currentPosition, int destinationStart); QStringList dataTags() const { return m_dataTags; } void verifyExecutedTests(); private: DynamicTreeModel *m_rootModel; QAbstractItemModel *m_sourceModel; QAbstractProxyModel *m_proxyModel; QAbstractProxyModel *m_intermediateProxyModel; ModelSpy *m_modelSpy; ModelCommander *m_modelCommander; QStringList m_dataTags; QStringList m_modelCommanderTags; QString m_currentTest; }; class PROXYMODELTESTSUITE_EXPORT ProxyModelTestData : public QObject, BuiltinTestDataInterface { Q_OBJECT public: ProxyModelTestData(ProxyModelTest *parent = nullptr) : QObject(parent) , m_proxyModelTest(parent) { } static const char *failTag() { return "fail01"; } protected: void dummyTestData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); QTest::newRow(failTag()) << SignalList() << PersistentChangeList(); QSKIP("Test not implemented"); } void skipTestData(const QString &name) { processTestName(name); QTest::addColumn("signalList"); QTest::addColumn("changeList"); QTest::newRow(name.toLatin1()) << (SignalList() << (QVariantList() << "skip")) << PersistentChangeList(); } void processTestName(const QString &name) { if (m_currentTestFunction != QTest::currentTestFunction()) { m_testNames.clear(); m_currentTestFunction = QTest::currentTestFunction(); } m_testNames.insert(name); } QStringList namedTests() { return m_testNames.values(); } void noopTest(const QString &name) { processTestName(name); QTest::newRow(name.toLatin1()) << SignalList() << PersistentChangeList(); } void noopLayoutChangeTest(const QString &name) { processTestName(name); SignalList signalList; signalList << (QVariantList() << LayoutAboutToBeChanged); signalList << (QVariantList() << LayoutChanged); QTest::newRow(name.toLatin1()) << signalList << PersistentChangeList(); } void testForwardingInsertData(const IndexFinder &indexFinder) { QTest::addColumn("signalList"); QTest::addColumn("changeList"); newInsertTest(QStringLiteral("insert01"), indexFinder, 0, 0, 10); newInsertTest(QStringLiteral("insert02"), indexFinder, 0, 9, 10); newInsertTest(QStringLiteral("insert03"), indexFinder, 10, 10, 10); newInsertTest(QStringLiteral("insert04"), indexFinder, 10, 19, 10); newInsertTest(QStringLiteral("insert05"), indexFinder, 4, 4, 10); newInsertTest(QStringLiteral("insert06"), indexFinder, 4, 13, 10); newInsertTest(QStringLiteral("insert07"), indexFinder, 0, 0, 10); newInsertTest(QStringLiteral("insert08"), indexFinder, 10, 10, 10); newInsertTest(QStringLiteral("insert09"), indexFinder, 4, 4, 10); newInsertTest(QStringLiteral("insert10"), indexFinder, 0, 4, 10); newInsertTest(QStringLiteral("insert11"), indexFinder, 10, 14, 10); newInsertTest(QStringLiteral("insert12"), indexFinder, 4, 8, 10); QList rows = indexFinder.rows(); rows.append(0); newInsertTest(QStringLiteral("insert13"), rows, 0, 0, 0); newInsertTest(QStringLiteral("insert14"), rows, 0, 9, 0); newInsertTest(QStringLiteral("insert15"), rows, 0, 4, 0); rows = indexFinder.rows(); rows.append(9); newInsertTest(QStringLiteral("insert16"), rows, 0, 0, 0); newInsertTest(QStringLiteral("insert17"), rows, 0, 9, 0); newInsertTest(QStringLiteral("insert18"), rows, 0, 4, 0); } void testForwardingRemoveData(const IndexFinder &indexFinder) { QTest::addColumn("signalList"); QTest::addColumn("changeList"); newRemoveTest(QStringLiteral("remove01"), indexFinder, 0, 0, 10); newRemoveTest(QStringLiteral("remove02"), indexFinder, 0, 4, 10); newRemoveTest(QStringLiteral("remove03"), indexFinder, 9, 9, 10); } void testForwardingMoveData(const IndexFinder &srcFinder, const IndexFinder &destFinder) { QTest::addColumn("signalList"); QTest::addColumn("changeList"); // noopLayoutChangeTest("move01"); // noopLayoutChangeTest("move02"); // noopLayoutChangeTest("move03"); // noopLayoutChangeTest("move04"); // noopLayoutChangeTest("move05"); newMoveTest(QStringLiteral("move01"), srcFinder, 0, 0, 10, destFinder, 5); newMoveTest(QStringLiteral("move02"), srcFinder, 4, 4, 10, destFinder, 0); newMoveTest(QStringLiteral("move03"), srcFinder, 4, 4, 10, destFinder, 10); newMoveTest(QStringLiteral("move04"), srcFinder, 9, 9, 10, destFinder, 4); newMoveTest(QStringLiteral("move05"), srcFinder, 9, 9, 10, destFinder, 0); } void testForwardingModifyData(const IndexFinder &parentFinder) { QTest::addColumn("signalList"); QTest::addColumn("changeList"); newModifyTest(QStringLiteral("modify01"), parentFinder, 0, 0); newModifyTest(QStringLiteral("modify02"), parentFinder, 0, 4); newModifyTest(QStringLiteral("modify03"), parentFinder, 9, 9); newModifyTest(QStringLiteral("modify04"), parentFinder, 6, 9); newModifyTest(QStringLiteral("modify05"), parentFinder, 4, 4); newModifyTest(QStringLiteral("modify06"), parentFinder, 3, 7); newModifyTest(QStringLiteral("modify07"), parentFinder, 0, 9); } void newInsertTest(const QString &name, const IndexFinder &indexFinder, int start, int end, int rowCount) { processTestName(name); SignalList signalList; PersistentChangeList persistentList; signalList << m_proxyModelTest->getSignal(RowsAboutToBeInserted, indexFinder, start, end); signalList << m_proxyModelTest->getSignal(RowsInserted, indexFinder, start, end); if (rowCount - 1 + (end - start + 1) > end) { persistentList << m_proxyModelTest->getChange(indexFinder, start, rowCount - 1, end - start + 1); } QTest::newRow(name.toLatin1()) << signalList << persistentList; } void newRemoveTest(const QString &name, const IndexFinder &indexFinder, int start, int end, int rowCount) { processTestName(name); SignalList signalList; PersistentChangeList persistentList; signalList << m_proxyModelTest->getSignal(RowsAboutToBeRemoved, indexFinder, start, end); signalList << m_proxyModelTest->getSignal(RowsRemoved, indexFinder, start, end); persistentList << m_proxyModelTest->getChange(indexFinder, start, end, -1, true); if (rowCount - 1 != end) { persistentList << m_proxyModelTest->getChange(indexFinder, end + 1, rowCount - 1, -1 * (end - start + 1)); } QTest::newRow(name.toLatin1()) << signalList << persistentList; } void newMoveTest(const QString &name, const IndexFinder &srcFinder, int start, int end, int rowCount, const IndexFinder &destFinder, int destStart) { Q_UNUSED(rowCount) processTestName(name); SignalList signalList; PersistentChangeList persistentList; // signalList << m_proxyModelTest->getSignal(RowsAboutToBeMoved, srcFinder, start, end, destFinder, destStart); // signalList << ( QVariantList() << LayoutAboutToBeChanged ); // signalList << m_proxyModelTest->getSignal(RowsMoved, srcFinder, start, end, destFinder, destStart); // signalList << ( QVariantList() << LayoutChanged ); signalList << (QVariantList() << LayoutAboutToBeChanged); signalList << (QVariantList() << LayoutChanged); const bool sameParent = (srcFinder == destFinder); const bool movingUp = (start > destStart); if (sameParent) { if (movingUp) { persistentList << m_proxyModelTest->getChange(srcFinder, destStart, start - 1, end - start + 1); persistentList << m_proxyModelTest->getChange(srcFinder, start, end, -1 * (start - destStart)); } else { persistentList << m_proxyModelTest->getChange(srcFinder, start, end, destStart - end - 1); persistentList << m_proxyModelTest->getChange(srcFinder, end + 1, destStart - 1, -1 * (end - start + 1)); } } else { if (movingUp) { // TODO } } QTest::newRow(name.toLatin1()) << signalList << persistentList; } void newModifyTest(const QString &name, const IndexFinder &parentFinder, int top, int bottom) { processTestName(name); SignalList signalList; IndexFinder topLeftFinder = parentFinder; topLeftFinder.appendRow(top); IndexFinder bottomRightFinder = parentFinder; bottomRightFinder.appendRow(bottom); signalList << m_proxyModelTest->getSignal(DataChanged, topLeftFinder, bottomRightFinder); QTest::newRow(name.toLatin1()) << signalList << PersistentChangeList(); } void noop_testInsertWhenEmptyData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); noopTest(QStringLiteral("insert01")); noopTest(QStringLiteral("insert02")); noopTest(QStringLiteral("insert03")); } void noop_testInsertInRootData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); // These commands have no effect because this model shows children of selection. noopTest(QStringLiteral("insert01")); noopTest(QStringLiteral("insert02")); noopTest(QStringLiteral("insert03")); noopTest(QStringLiteral("insert04")); noopTest(QStringLiteral("insert05")); noopTest(QStringLiteral("insert06")); noopTest(QStringLiteral("insert07")); noopTest(QStringLiteral("insert08")); noopTest(QStringLiteral("insert09")); noopTest(QStringLiteral("insert10")); noopTest(QStringLiteral("insert11")); noopTest(QStringLiteral("insert12")); noopTest(QStringLiteral("insert13")); noopTest(QStringLiteral("insert14")); noopTest(QStringLiteral("insert15")); noopTest(QStringLiteral("insert16")); noopTest(QStringLiteral("insert17")); noopTest(QStringLiteral("insert18")); } void noop_testInsertInTopLevelData() { // Same test names etc. noop_testInsertInRootData(); } void noop_testInsertInSecondLevelData() { noop_testInsertInRootData(); } void noop_testRemoveFromRootData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); // These commands have no effect because this model shows children of selection. noopTest(QStringLiteral("remove01")); noopTest(QStringLiteral("remove02")); noopTest(QStringLiteral("remove03")); } void noop_testRemoveFromTopLevelData() { // Same test names etc. noop_testRemoveFromRootData(); } void noop_testRemoveFromSecondLevelData() { noop_testRemoveFromRootData(); } void noop_testMoveFromRootData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); // These commands have no effect because this model shows children of selection. noopLayoutChangeTest(QStringLiteral("move01")); noopLayoutChangeTest(QStringLiteral("move02")); noopLayoutChangeTest(QStringLiteral("move03")); noopLayoutChangeTest(QStringLiteral("move04")); noopLayoutChangeTest(QStringLiteral("move05")); } void noop_testMoveFromTopLevelData() { // Same test names etc. noop_testMoveFromRootData(); } void noop_testMoveFromSecondLevelData() { noop_testMoveFromRootData(); } void noop_testModifyInRootData() { QTest::addColumn("signalList"); QTest::addColumn("changeList"); noopTest(QStringLiteral("modify01")); noopTest(QStringLiteral("modify02")); noopTest(QStringLiteral("modify03")); noopTest(QStringLiteral("modify04")); noopTest(QStringLiteral("modify05")); noopTest(QStringLiteral("modify06")); noopTest(QStringLiteral("modify07")); } void noop_testModifyInTopLevelData() { // Same test names etc. noop_testModifyInRootData(); } void noop_testModifyInSecondLevelData() { noop_testModifyInRootData(); } ProxyModelTest *m_proxyModelTest; QString m_currentTestFunction; QSet m_testNames; }; PROXYMODELTESTSUITE_EXPORT uint qHash(const QVariant &var); /* clang-format off */ #define PROXYMODELTEST(TestData, TemplateArg, IntermediateProxy, LazyPersistence, Config) \ if (testObjects.isEmpty() || testObjects.contains(testNum)) { \ proxyModelTestClass->setTestData(new TestData(proxyModelTestClass)); \ proxyModelTestClass->setUseIntermediateProxy(IntermediateProxy); \ proxyModelTestClass->setLazyPersistence(LazyPersistence); \ qDebug() << "\n Running" << proxyModelTestClass->objectName().toLatin1() << testNum << ":\n" \ << " Source Model: " << #IntermediateProxy << "\n" \ << " Persistence: " << #LazyPersistence << "\n" Config; \ result = QTest::qExec(proxyModelTestClass, arguments); \ if (result != 0) \ return result; \ } \ ++testNum; #define PROXYMODELTEST_CUSTOM(TestData, IntermediateProxy, LazyPersistence, Config) \ if (testObjects.isEmpty() || testObjects.contains(testNum)) { \ proxyModelTestClass->setTestData(TestData); \ proxyModelTestClass->setUseIntermediateProxy(IntermediateProxy); \ proxyModelTestClass->setLazyPersistence(LazyPersistence); \ qDebug() << "\n Running" << proxyModelTestClass->objectName().toLatin1() << testNum << ":\n" \ << " Source Model: " << #IntermediateProxy << "\n" \ << " Persistence: " << #LazyPersistence << "\n" Config; \ result = QTest::qExec(proxyModelTestClass, arguments); \ if (result != 0) \ return result; \ } \ ++testNum; // The DynamicTreeModel uses a unique internalId for the first column of each row. // In the QSortFilterProxyModel the internalId is shared between all rows of the same parent. // We test the proxy on top of both so that we know it is not using the internalId of its source model // which will be different each time the test is run. #define COMPLETETEST(TestData, TemplateArg, Config) \ PROXYMODELTEST(TestData, TemplateArg, DynamicTree, ImmediatePersistence, Config) \ PROXYMODELTEST(TestData, TemplateArg, IntermediateProxy, ImmediatePersistence, Config) \ PROXYMODELTEST(TestData, TemplateArg, DynamicTree, LazyPersistence, Config) \ PROXYMODELTEST(TestData, TemplateArg, IntermediateProxy, LazyPersistence, Config) #define PROXYMODELTEST_MAIN(TestClass, Body) \ int main(int argc, char *argv[]) \ { \ QGuiApplication app(argc, argv); \ QList testObjects; \ QStringList arguments; \ bool ok; \ const auto lst = app.arguments(); \ for (const QString &arg : lst) { \ int testObject = arg.toInt(&ok); \ if (arg == "-count") \ continue; \ if (!ok) { \ arguments.append(arg); \ continue; \ } \ testObjects.append(testObject); \ } \ TestClass *proxyModelTestClass = new TestClass(); \ proxyModelTestClass->setObjectName(#TestClass); \ int result = 0; \ int testNum = 1; \ \ Body \ \ delete proxyModelTestClass; \ return result; \ } /* clang-format on */ #endif