From c1d5fdd04a8e57836fb3040c0d0fda843fc095a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Cie=C5=9Blak?= Date: Wed, 16 Nov 2022 14:37:23 +0100 Subject: [PATCH] feat(Storybook): sections decorator model impl and basic tests Proxy model extending source model by adding foldable sections based on "section" role from the source model. --- storybook/CMakeLists.txt | 8 +- storybook/sectionsdecoratormodel.cpp | 162 ++++++ storybook/sectionsdecoratormodel.h | 46 ++ .../tests/tst_SectionsDecoratorModel.cpp | 502 ++++++++++++++++++ 4 files changed, 717 insertions(+), 1 deletion(-) create mode 100644 storybook/sectionsdecoratormodel.cpp create mode 100644 storybook/sectionsdecoratormodel.h create mode 100644 storybook/tests/tst_SectionsDecoratorModel.cpp diff --git a/storybook/CMakeLists.txt b/storybook/CMakeLists.txt index c54e06481f..7265270872 100644 --- a/storybook/CMakeLists.txt +++ b/storybook/CMakeLists.txt @@ -17,7 +17,7 @@ endif() find_package( Qt5 - COMPONENTS Core Quick QuickControls2 WebEngine + COMPONENTS Core Quick QuickControls2 WebEngine Test REQUIRED) file(GLOB_RECURSE QML_FILES "stubs/*.qml" "mocks/*.qml" "pages/*.qml" "src/*.qml" "src/qmldir" "../ui/StatusQ/*.qml" "../ui/app/*.qml") @@ -28,6 +28,7 @@ add_executable( main.cpp cachecleaner.cpp cachecleaner.h directorieswatcher.cpp directorieswatcher.h + sectionsdecoratormodel.cpp sectionsdecoratormodel.h ${QML_FILES} main.qml ${JS_FILES} ) @@ -38,6 +39,11 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Quick Qt5::QuickControls2 Qt5::WebEngine SortFilterProxyModel) +enable_testing() +add_executable(SectionsDecoratorModelTest tests/tst_SectionsDecoratorModel.cpp sectionsdecoratormodel.cpp) +add_test(NAME SectionsDecoratorModelTest COMMAND SectionsDecoratorModelTest) +target_link_libraries(SectionsDecoratorModelTest PRIVATE Qt5::Test) + list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/StatusQ/src") list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/app") list(APPEND QML_DIRS "${CMAKE_SOURCE_DIR}/../ui/imports") diff --git a/storybook/sectionsdecoratormodel.cpp b/storybook/sectionsdecoratormodel.cpp new file mode 100644 index 0000000000..c41f4ba04f --- /dev/null +++ b/storybook/sectionsdecoratormodel.cpp @@ -0,0 +1,162 @@ +#include "sectionsdecoratormodel.h" + +#include + +SectionsDecoratorModel::SectionsDecoratorModel(QObject *parent) + : QAbstractListModel{parent} +{ +} + +void SectionsDecoratorModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + if (m_sourceModel != nullptr) { + qWarning("Changing source model is not supported!"); + return; + } + + m_sourceModel = sourceModel; + + initialize(); + + connect(sourceModel, &QAbstractItemModel::modelReset, this, &SectionsDecoratorModel::initialize); + connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &SectionsDecoratorModel::initialize); + connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, &SectionsDecoratorModel::initialize); + connect(sourceModel, &QAbstractItemModel::rowsMoved, this, &SectionsDecoratorModel::initialize); + + emit sourceModelChanged(); +} + +QAbstractItemModel* SectionsDecoratorModel::sourceModel() const +{ + return m_sourceModel; +} + +int SectionsDecoratorModel::rowCount(const QModelIndex &parent) const +{ + return m_rowsMetadata.size(); +} + +QVariant SectionsDecoratorModel::data(const QModelIndex &index, int role) const +{ + if (!checkIndex(index, CheckIndexOption::IndexIsValid)) + return {}; + + const int row = index.row(); + const RowMetadata &rowMetadata = m_rowsMetadata[row]; + + if (role == IsFoldedRole) { + return rowMetadata.folded; + } else if (role == SubitemsCountRole) { + return rowMetadata.count; + } else if (role == IsSectionRole) { + return rowMetadata.isSection; + } else if (role == m_sectionRole && rowMetadata.isSection) { + return rowMetadata.sectionName; + } + + if (!rowMetadata.isSection) { + return sourceModel()->data( + sourceModel()->index(row - rowMetadata.offset, 0), role); + } else { + return QVariant(); + } +} + +QHash SectionsDecoratorModel::roleNames() const +{ + auto roles = m_sourceModel ? m_sourceModel->roleNames() : QHash{}; + roles.insert(IsSectionRole, QByteArrayLiteral("isSection")); + roles.insert(IsFoldedRole, QByteArrayLiteral("isFolded")); + roles.insert(SubitemsCountRole, QByteArrayLiteral("subitemsCount")); + + return roles; +} + +void SectionsDecoratorModel::flipFolding(int index) +{ + auto &row = m_rowsMetadata[index]; + row.folded = !row.folded; + + const auto idx = this->index(index, 0, {}); + + if (row.folded) { + beginRemoveRows(QModelIndex(), index + 1, index + row.count); + m_rowsMetadata.erase(m_rowsMetadata.begin() + index + 1, + m_rowsMetadata.begin() + index + 1 + row.count); + calculateOffsets(); + endRemoveRows(); + } else { + beginInsertRows(QModelIndex(), index + 1, index + row.count); + m_rowsMetadata.insert(m_rowsMetadata.begin() + index + 1, row.count, + RowMetadata{false}); + calculateOffsets(); + endInsertRows(); + } + + emit dataChanged(idx, idx, { IsFoldedRole }); +} + +void SectionsDecoratorModel::calculateOffsets() +{ + std::for_each(m_rowsMetadata.begin(), m_rowsMetadata.end(), + [offset = 0](RowMetadata &row) mutable { + if (row.isSection) { + ++offset; + + if (row.folded) + offset -= row.count; + } else { + row.offset = offset; + } + }); +} + +std::optional SectionsDecoratorModel::findSectionRole() const +{ + const auto roleNames = m_sourceModel->roleNames(); + auto i = roleNames.constBegin(); + + while (i != roleNames.constEnd()) { + if (i.value() == QStringLiteral("section")) + return i.key(); + ++i; + } + + return std::nullopt; +} + +void SectionsDecoratorModel::initialize() +{ + beginResetModel(); + auto endResetModelGuard = qScopeGuard([this] { endResetModel(); }); + + m_rowsMetadata.clear(); + + const auto sectionRoleOpt = findSectionRole(); + + if (!sectionRoleOpt) { + qWarning("Section role not found!"); + return; + } + + m_sectionRole = *sectionRoleOpt; + QString prevSection; + int prevSectionIndex = 0; + + for (int i = 0; i < m_sourceModel->rowCount(); i++) { + const QVariant sectionVariant = m_sourceModel->data( + m_sourceModel->index(i, 0), m_sectionRole); + const QString section = sectionVariant.toString(); + + if (prevSection != section) { + m_rowsMetadata.push_back({true, section}); + prevSection = section; + prevSectionIndex = m_rowsMetadata.size() - 1; + } + + m_rowsMetadata.push_back({false}); + m_rowsMetadata[prevSectionIndex].count++; + } + + calculateOffsets(); +} diff --git a/storybook/sectionsdecoratormodel.h b/storybook/sectionsdecoratormodel.h new file mode 100644 index 0000000000..98e4f69988 --- /dev/null +++ b/storybook/sectionsdecoratormodel.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +class SectionsDecoratorModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel + WRITE setSourceModel NOTIFY sourceModelChanged) +public: + static constexpr int IsSectionRole = Qt::UserRole + 100; + static constexpr int IsFoldedRole = Qt::UserRole + 101; + static constexpr int SubitemsCountRole = Qt::UserRole + 102; + + explicit SectionsDecoratorModel(QObject *parent = nullptr); + + void setSourceModel(QAbstractItemModel *sourceModel); + QAbstractItemModel *sourceModel() const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QHash roleNames() const override; + + Q_INVOKABLE void flipFolding(int index); + +signals: + void sourceModelChanged(); + +private: + struct RowMetadata { + bool isSection = false; + QString sectionName; + bool folded = false; + int offset = 0; + int count = 0; + }; + + std::optional findSectionRole() const; + void initialize(); + void calculateOffsets(); + + QAbstractItemModel* m_sourceModel = nullptr; + std::vector m_rowsMetadata; + int m_sectionRole = 0; +}; diff --git a/storybook/tests/tst_SectionsDecoratorModel.cpp b/storybook/tests/tst_SectionsDecoratorModel.cpp new file mode 100644 index 0000000000..aa27fe31ee --- /dev/null +++ b/storybook/tests/tst_SectionsDecoratorModel.cpp @@ -0,0 +1,502 @@ +#include +#include + +#include +#include + +#include + +namespace { + +class TestSourceModel : public QAbstractListModel { + +public: + explicit TestSourceModel(QStringList sections) + : m_sections(std::move(sections)) + { + } + + static constexpr int TitleRole = Qt::UserRole + 1; + static constexpr int SectionRole = Qt::UserRole + 2; + + int rowCount(const QModelIndex &parent) const override { + return m_sections.size(); + } + + QVariant data(const QModelIndex &index, int role) const override { + if (!index.isValid()) + return {}; + + if (role == TitleRole) { + return QString("title %1").arg(index.row()); + } + + return m_sections.at(index.row()); + } + + QHash roleNames() const override { + QHash roles; + roles.insert(TitleRole, "title"); + roles.insert(SectionRole, "section"); + return roles; + } + + QStringList m_sections; +}; + +} // unnamed namespace + +class TestSectionsDecoratorModel: public QObject +{ + Q_OBJECT + +private slots: + void emptyModelTest() { + SectionsDecoratorModel model; + + QCOMPARE(model.rowCount(), 0); + QCOMPARE(model.roleNames().count(), 3); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::IsSectionRole)); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::IsFoldedRole)); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::SubitemsCountRole)); + } + + void emptySourceTest() { + TestSourceModel src(QStringList{}); + SectionsDecoratorModel model; + model.setSourceModel(&src); + + QCOMPARE(model.rowCount(), 0); + QCOMPARE(model.roleNames().count(), 5); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::IsSectionRole)); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::IsFoldedRole)); + QVERIFY(model.roleNames().contains(SectionsDecoratorModel::SubitemsCountRole)); + QVERIFY(model.roleNames().contains(TestSourceModel::TitleRole)); + QVERIFY(model.roleNames().contains(TestSourceModel::SectionRole)); + } + + void initialUnfoldedStateTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + SectionsDecoratorModel model; + model.setSourceModel(&src); + + QCOMPARE(model.rowCount(), 9); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(6, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(7, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(8, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + } + + void foldingFromTopToBottomTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + SectionsDecoratorModel model; + model.setSourceModel(&src); + + model.flipFolding(0); + + QCOMPARE(model.rowCount(), 6); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + + model.flipFolding(1); + QCOMPARE(model.rowCount(), 4); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + + model.flipFolding(2); + + QCOMPARE(model.rowCount(), 3); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole), QVariant{}); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + } + + void foldingFromBottomToTopTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + SectionsDecoratorModel model; + model.setSourceModel(&src); + model.flipFolding(7); + + QCOMPARE(model.rowCount(), 8); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(6, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(7, 0), TestSourceModel::TitleRole), QVariant{}); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + + model.flipFolding(4); + + QCOMPARE(model.rowCount(), 6); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole), QVariant{}); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + + model.flipFolding(0); + + QCOMPARE(model.rowCount(), 3); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole), QVariant{}); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), true); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + } + + void unfoldingTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + SectionsDecoratorModel model; + model.setSourceModel(&src); + + model.flipFolding(0); + model.flipFolding(1); + model.flipFolding(2); + + model.flipFolding(2); + model.flipFolding(1); + model.flipFolding(0); + + QCOMPARE(model.rowCount(), 9); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(6, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(7, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(8, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + + model.flipFolding(0); + model.flipFolding(1); + model.flipFolding(2); + + model.flipFolding(0); + model.flipFolding(4); + model.flipFolding(7); + + QCOMPARE(model.rowCount(), 9); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(6, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(7, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(8, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + } + + void basicFilteringTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + + QSortFilterProxyModel proxy; + proxy.setSourceModel(&src); + + SectionsDecoratorModel model; + model.setSourceModel(&proxy); + + QCOMPARE(model.rowCount(), 9); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 0")); + QCOMPARE(model.data(model.index(2, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + QCOMPARE(model.data(model.index(3, 0), TestSourceModel::TitleRole).toString(), QString("title 2")); + QCOMPARE(model.data(model.index(4, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(5, 0), TestSourceModel::TitleRole).toString(), QString("title 3")); + QCOMPARE(model.data(model.index(6, 0), TestSourceModel::TitleRole).toString(), QString("title 4")); + QCOMPARE(model.data(model.index(7, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(8, 0), TestSourceModel::TitleRole).toString(), QString("title 5")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(6, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 3); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(2, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(3, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(4, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 2); + QCOMPARE(model.data(model.index(5, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + QCOMPARE(model.data(model.index(7, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(8, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + } + + void filteringTest() { + TestSourceModel src(QStringList{"Section 1", "Section 1", "Section 1", "Section 2", "Section 2", "Section 3"}); + + QSortFilterProxyModel proxy; + proxy.setSourceModel(&src); + + SectionsDecoratorModel model; + model.setSourceModel(&proxy); + + QSignalSpy spy(&model, SIGNAL(modelReset())); + + proxy.setFilterRole(TestSourceModel::TitleRole); + proxy.setFilterWildcard("*1"); + + QVERIFY(spy.count() > 1); + + QCOMPARE(model.rowCount(), 2); + QCOMPARE(model.roleNames().count(), 5); + + QCOMPARE(model.data(model.index(0, 0), TestSourceModel::TitleRole), QVariant{}); + QCOMPARE(model.data(model.index(1, 0), TestSourceModel::TitleRole).toString(), QString("title 1")); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsSectionRole).toBool(), true); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsSectionRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::IsFoldedRole).toBool(), false); + + QCOMPARE(model.data(model.index(0, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 1); + QCOMPARE(model.data(model.index(1, 0), SectionsDecoratorModel::SubitemsCountRole).toInt(), 0); + } +}; + +// TODO: signals emission testing using QSignalSpy + +QTEST_MAIN(TestSectionsDecoratorModel) +#include "tst_SectionsDecoratorModel.moc"