diff options
Diffstat (limited to 'tests/auto/corelib/itemmodels')
8 files changed, 1931 insertions, 4 deletions
diff --git a/tests/auto/corelib/itemmodels/itemmodels.pro b/tests/auto/corelib/itemmodels/itemmodels.pro index cca350ad43..ffbda6ec40 100644 --- a/tests/auto/corelib/itemmodels/itemmodels.pro +++ b/tests/auto/corelib/itemmodels/itemmodels.pro @@ -5,9 +5,11 @@ SUBDIRS = qstringlistmodel qtHaveModule(gui): SUBDIRS += \ qabstractitemmodel \ qabstractproxymodel \ + qconcatenatetablesproxymodel \ qidentityproxymodel \ qitemselectionmodel \ qsortfilterproxymodel_recursive \ + qtransposeproxymodel \ qtHaveModule(widgets) { SUBDIRS += \ diff --git a/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/qconcatenatetablesproxymodel.pro b/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/qconcatenatetablesproxymodel.pro new file mode 100644 index 0000000000..ee4ea28b5b --- /dev/null +++ b/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/qconcatenatetablesproxymodel.pro @@ -0,0 +1,5 @@ +CONFIG += testcase +TARGET = tst_qconcatenatetablesproxymodel +QT = core gui testlib + +SOURCES = tst_qconcatenatetablesproxymodel.cpp diff --git a/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp b/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp new file mode 100644 index 0000000000..40617c1f7d --- /dev/null +++ b/tests/auto/corelib/itemmodels/qconcatenatetablesproxymodel/tst_qconcatenatetablesproxymodel.cpp @@ -0,0 +1,823 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QSignalSpy> +#include <QSortFilterProxyModel> +#include <QTest> +#include <QStandardItemModel> +#include <QIdentityProxyModel> +#include <QItemSelectionModel> +#include <QMimeData> +#include <QStringListModel> +#include <QAbstractItemModelTester> + +#include <qconcatenatetablesproxymodel.h> + +Q_DECLARE_METATYPE(QModelIndex) + +// Extracts a full row from a model as a string +// Works best if every cell contains only one character +static QString extractRowTexts(QAbstractItemModel *model, int row, const QModelIndex &parent = QModelIndex()) +{ + QString result; + const int colCount = model->columnCount(); + for (int col = 0; col < colCount; ++col) { + const QString txt = model->index(row, col, parent).data().toString(); + result += txt.isEmpty() ? QStringLiteral(" ") : txt; + } + return result; +} + +// Extracts a full column from a model as a string +// Works best if every cell contains only one character +static QString extractColumnTexts(QAbstractItemModel *model, int column, const QModelIndex &parent = QModelIndex()) +{ + QString result; + const int rowCount = model->rowCount(); + for (int row = 0; row < rowCount; ++row) { + const QString txt = model->index(row, column, parent).data().toString(); + result += txt.isEmpty() ? QStringLiteral(" ") : txt; + } + return result; +} + +static QString rowSpyToText(const QSignalSpy &spy) +{ + if (!spy.isValid()) + return QStringLiteral("THE SIGNALSPY IS INVALID!"); + QString str; + for (int i = 0; i < spy.count(); ++i) { + str += spy.at(i).at(1).toString() + QLatin1Char(',') + spy.at(i).at(2).toString(); + if (i + 1 < spy.count()) + str += QLatin1Char(';'); + } + return str; +} + +class tst_QConcatenateTablesProxyModel : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init(); + void shouldAggregateTwoModelsCorrectly(); + void shouldAggregateThenRemoveTwoEmptyModelsCorrectly(); + void shouldAggregateTwoEmptyModelsWhichThenGetFilled(); + void shouldHandleDataChanged(); + void shouldHandleSetData(); + void shouldHandleSetItemData(); + void shouldHandleRowInsertionAndRemoval(); + void shouldAggregateAnotherModelThenRemoveModels(); + void shouldUseSmallestColumnCount(); + void shouldIncreaseColumnCountWhenRemovingFirstModel(); + void shouldHandleColumnInsertionAndRemoval(); + void shouldPropagateLayoutChanged(); + void shouldReactToModelReset(); + void shouldUpdateColumnsOnModelReset(); + void shouldPropagateDropOnItem_data(); + void shouldPropagateDropOnItem(); + void shouldPropagateDropBetweenItems(); + void shouldPropagateDropBetweenItemsAtModelBoundary(); + void shouldPropagateDropAfterLastRow_data(); + void shouldPropagateDropAfterLastRow(); + +private: + QStandardItemModel mod; + QStandardItemModel mod2; + QStandardItemModel mod3; +}; + +void tst_QConcatenateTablesProxyModel::init() +{ + // Prepare some source models to use later on + mod.clear(); + mod.appendRow({ new QStandardItem(QStringLiteral("A")), new QStandardItem(QStringLiteral("B")), new QStandardItem(QStringLiteral("C")) }); + mod.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3")); + mod.setVerticalHeaderLabels(QStringList() << QStringLiteral("One")); + + mod2.clear(); + mod2.appendRow({ new QStandardItem(QStringLiteral("D")), new QStandardItem(QStringLiteral("E")), new QStandardItem(QStringLiteral("F")) }); + mod2.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3")); + mod2.setVerticalHeaderLabels(QStringList() << QStringLiteral("Two")); + + mod3.clear(); + mod3.appendRow({ new QStandardItem(QStringLiteral("1")), new QStandardItem(QStringLiteral("2")), new QStandardItem(QStringLiteral("3")) }); + mod3.appendRow({ new QStandardItem(QStringLiteral("4")), new QStandardItem(QStringLiteral("5")), new QStandardItem(QStringLiteral("6")) }); +} + +void tst_QConcatenateTablesProxyModel::shouldAggregateTwoModelsCorrectly() +{ + // Given a combining proxy + QConcatenateTablesProxyModel pm; + + // When adding two source models + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + + // Then the proxy should show 2 rows + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + + // ... and correct headers + QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QStringLiteral("H1")); + QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QStringLiteral("H2")); + QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QStringLiteral("H3")); + QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QStringLiteral("One")); + QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QStringLiteral("Two")); + + QVERIFY(!pm.canFetchMore(QModelIndex())); +} + +void tst_QConcatenateTablesProxyModel::shouldAggregateThenRemoveTwoEmptyModelsCorrectly() +{ + // Given a combining proxy + QConcatenateTablesProxyModel pm; + + // When adding two empty models + QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QIdentityProxyModel i1, i2; + pm.addSourceModel(&i1); + pm.addSourceModel(&i2); + + // Then the proxy should still be empty (and no signals emitted) + QCOMPARE(pm.rowCount(), 0); + QCOMPARE(pm.columnCount(), 0); + QCOMPARE(rowATBISpy.count(), 0); + QCOMPARE(rowInsertedSpy.count(), 0); + + // When removing the empty models + pm.removeSourceModel(&i1); + pm.removeSourceModel(&i2); + + // Then the proxy should still be empty (and no signals emitted) + QCOMPARE(pm.rowCount(), 0); + QCOMPARE(pm.columnCount(), 0); + QCOMPARE(rowATBRSpy.count(), 0); + QCOMPARE(rowRemovedSpy.count(), 0); +} + +void tst_QConcatenateTablesProxyModel::shouldAggregateTwoEmptyModelsWhichThenGetFilled() +{ + // Given a combining proxy with two empty models + QConcatenateTablesProxyModel pm; + QIdentityProxyModel i1, i2; + pm.addSourceModel(&i1); + pm.addSourceModel(&i2); + + // When filling them afterwards + i1.setSourceModel(&mod); + i2.setSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + + // Then the proxy should show 2 rows + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(pm.columnCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + + // ... and correct headers + QCOMPARE(pm.headerData(0, Qt::Horizontal).toString(), QStringLiteral("H1")); + QCOMPARE(pm.headerData(1, Qt::Horizontal).toString(), QStringLiteral("H2")); + QCOMPARE(pm.headerData(2, Qt::Horizontal).toString(), QStringLiteral("H3")); + QCOMPARE(pm.headerData(0, Qt::Vertical).toString(), QStringLiteral("One")); + QCOMPARE(pm.headerData(1, Qt::Vertical).toString(), QStringLiteral("Two")); + + QVERIFY(!pm.canFetchMore(QModelIndex())); +} + +void tst_QConcatenateTablesProxyModel::shouldHandleDataChanged() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // When a cell in a source model changes + mod.item(0, 0)->setData("a", Qt::EditRole); + + // Then the change should be notified to the proxy + QCOMPARE(dataChangedSpy.count(), 1); + QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 0)); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("aBC")); + + // Same test with the other model + mod2.item(0, 2)->setData("f", Qt::EditRole); + + QCOMPARE(dataChangedSpy.count(), 2); + QCOMPARE(dataChangedSpy.at(1).at(0).toModelIndex(), pm.index(1, 2)); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEf")); +} + +void tst_QConcatenateTablesProxyModel::shouldHandleSetData() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // When changing a cell using setData + pm.setData(pm.index(0, 0), "a"); + + // Then the change should be notified to the proxy + QCOMPARE(dataChangedSpy.count(), 1); + QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 0)); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("aBC")); + + // Same test with the other model + pm.setData(pm.index(1, 2), "f"); + + QCOMPARE(dataChangedSpy.count(), 2); + QCOMPARE(dataChangedSpy.at(1).at(0).toModelIndex(), pm.index(1, 2)); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEf")); +} + +void tst_QConcatenateTablesProxyModel::shouldHandleSetItemData() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex))); + + // When changing a cell using setData + pm.setItemData(pm.index(0, 0), QMap<int, QVariant>{ std::make_pair<int, QVariant>(Qt::DisplayRole, QStringLiteral("X")), + std::make_pair<int, QVariant>(Qt::UserRole, 88) }); + + // Then the change should be notified to the proxy + QCOMPARE(dataChangedSpy.count(), 1); + QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 0)); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("XBC")); + QCOMPARE(pm.index(0, 0).data(Qt::UserRole).toInt(), 88); + + // Same test with the other model + pm.setItemData(pm.index(1, 2), QMap<int, QVariant>{ std::make_pair<int, QVariant>(Qt::DisplayRole, QStringLiteral("Y")), + std::make_pair<int, QVariant>(Qt::UserRole, 89) }); + + QCOMPARE(dataChangedSpy.count(), 2); + QCOMPARE(dataChangedSpy.at(1).at(0).toModelIndex(), pm.index(1, 2)); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEY")); + QCOMPARE(pm.index(1, 2).data(Qt::UserRole).toInt(), 89); +} + +void tst_QConcatenateTablesProxyModel::shouldHandleRowInsertionAndRemoval() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int))); + + // When a source model inserts a new row + QList<QStandardItem *> row; + row.append(new QStandardItem(QStringLiteral("1"))); + row.append(new QStandardItem(QStringLiteral("2"))); + row.append(new QStandardItem(QStringLiteral("3"))); + mod2.insertRow(0, row); + + // Then the proxy should notify its users and show changes + QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("1,1")); + QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("1,1")); + QCOMPARE(pm.rowCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF")); + + // When removing that row + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + mod2.removeRow(0); + + // Then the proxy should notify its users and show changes + QCOMPARE(rowATBRSpy.count(), 1); + QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1); + QCOMPARE(rowRemovedSpy.count(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1); + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + + // When removing the last row from mod2 + rowATBRSpy.clear(); + rowRemovedSpy.clear(); + mod2.removeRow(0); + + // Then the proxy should notify its users and show changes + QCOMPARE(rowATBRSpy.count(), 1); + QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1); + QCOMPARE(rowRemovedSpy.count(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1); + QCOMPARE(pm.rowCount(), 1); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); +} + +void tst_QConcatenateTablesProxyModel::shouldAggregateAnotherModelThenRemoveModels() +{ + // Given two models combined, and a third model + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + + QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int))); + + // When adding the new source model + pm.addSourceModel(&mod3); + + // Then the proxy should notify its users about the two rows inserted + QCOMPARE(rowSpyToText(rowATBISpy), QStringLiteral("2,3")); + QCOMPARE(rowSpyToText(rowInsertedSpy), QStringLiteral("2,3")); + QCOMPARE(pm.rowCount(), 4); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("456")); + + // When removing that source model again + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + pm.removeSourceModel(&mod3); + + // Then the proxy should notify its users about the row removed + QCOMPARE(rowATBRSpy.count(), 1); + QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 2); + QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 3); + QCOMPARE(rowRemovedSpy.count(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 2); + QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 3); + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + + // When removing model 2 + rowATBRSpy.clear(); + rowRemovedSpy.clear(); + pm.removeSourceModel(&mod2); + QCOMPARE(rowATBRSpy.count(), 1); + QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 1); + QCOMPARE(rowRemovedSpy.count(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 1); + QCOMPARE(pm.rowCount(), 1); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + + // When removing model 1 + rowATBRSpy.clear(); + rowRemovedSpy.clear(); + pm.removeSourceModel(&mod); + QCOMPARE(rowATBRSpy.count(), 1); + QCOMPARE(rowATBRSpy.at(0).at(1).toInt(), 0); + QCOMPARE(rowATBRSpy.at(0).at(2).toInt(), 0); + QCOMPARE(rowRemovedSpy.count(), 1); + QCOMPARE(rowRemovedSpy.at(0).at(1).toInt(), 0); + QCOMPARE(rowRemovedSpy.at(0).at(2).toInt(), 0); + QCOMPARE(pm.rowCount(), 0); +} + +void tst_QConcatenateTablesProxyModel::shouldUseSmallestColumnCount() +{ + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + pm.addSourceModel(&mod2); + mod2.setColumnCount(1); + pm.addSourceModel(&mod3); + QAbstractItemModelTester modelTest(&pm, this); + + QCOMPARE(pm.rowCount(), 4); + QCOMPARE(pm.columnCount(), 1); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("A")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("D")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("1")); + QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("4")); + + const QModelIndex indexA = pm.mapFromSource(mod.index(0, 0)); + QVERIFY(indexA.isValid()); + QCOMPARE(indexA, pm.index(0, 0)); + + const QModelIndex indexB = pm.mapFromSource(mod.index(0, 1)); + QVERIFY(!indexB.isValid()); + + const QModelIndex indexD = pm.mapFromSource(mod2.index(0, 0)); + QVERIFY(indexD.isValid()); + QCOMPARE(indexD, pm.index(1, 0)); +} + +void tst_QConcatenateTablesProxyModel::shouldIncreaseColumnCountWhenRemovingFirstModel() +{ + // Given a model with 2 columns and one with 3 columns + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + QAbstractItemModelTester modelTest(&pm, this); + mod.setColumnCount(2); + pm.addSourceModel(&mod2); + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(pm.columnCount(), 2); + + QSignalSpy colATBISpy(&pm, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy colInsertedSpy(&pm, SIGNAL(columnsInserted(QModelIndex,int,int))); + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + + // When removing the first source model + pm.removeSourceModel(&mod); + + // Then the proxy should notify its users about the row removed, and the column added + QCOMPARE(pm.rowCount(), 1); + QCOMPARE(pm.columnCount(), 3); + QCOMPARE(rowSpyToText(rowATBRSpy), QStringLiteral("0,0")); + QCOMPARE(rowSpyToText(rowRemovedSpy), QStringLiteral("0,0")); + QCOMPARE(rowSpyToText(colATBISpy), QStringLiteral("2,2")); + QCOMPARE(rowSpyToText(colInsertedSpy), QStringLiteral("2,2")); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("DEF")); +} + +void tst_QConcatenateTablesProxyModel::shouldHandleColumnInsertionAndRemoval() +{ + // Given two models combined, one with 2 columns and one with 3 + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + QAbstractItemModelTester modelTest(&pm, this); + mod.setColumnCount(2); + pm.addSourceModel(&mod2); + QSignalSpy colATBISpy(&pm, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy colInsertedSpy(&pm, SIGNAL(columnsInserted(QModelIndex,int,int))); + QSignalSpy colATBRSpy(&pm, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy colRemovedSpy(&pm, SIGNAL(columnsRemoved(QModelIndex,int,int))); + + // When the first source model inserts a new column + QCOMPARE(mod.columnCount(), 2); + mod.setColumnCount(3); + + // Then the proxy should notify its users and show changes + QCOMPARE(rowSpyToText(colATBISpy), QStringLiteral("2,2")); + QCOMPARE(rowSpyToText(colInsertedSpy), QStringLiteral("2,2")); + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(pm.columnCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("AB ")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + + // And when removing two columns + mod.setColumnCount(1); + + // Then the proxy should notify its users and show changes + QCOMPARE(rowSpyToText(colATBRSpy), QStringLiteral("1,2")); + QCOMPARE(rowSpyToText(colRemovedSpy), QStringLiteral("1,2")); + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(pm.columnCount(), 1); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("A")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("D")); +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateLayoutChanged() +{ + // Given two source models, the second one being a QSFPM + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + QAbstractItemModelTester modelTest(&pm, this); + + QSortFilterProxyModel qsfpm; + qsfpm.setSourceModel(&mod3); + pm.addSourceModel(&qsfpm); + + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("456")); + + // And a selection (row 1) + QItemSelectionModel selection(&pm); + selection.select(pm.index(1, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + const QModelIndexList lst = selection.selectedIndexes(); + QCOMPARE(lst.count(), 3); + for (int col = 0; col < lst.count(); ++col) { + QCOMPARE(lst.at(col).row(), 1); + QCOMPARE(lst.at(col).column(), col); + } + + QSignalSpy layoutATBCSpy(&pm, SIGNAL(layoutAboutToBeChanged())); + QSignalSpy layoutChangedSpy(&pm, SIGNAL(layoutChanged())); + + // When changing the sorting in the QSFPM + qsfpm.sort(0, Qt::DescendingOrder); + + // Then the proxy should emit the layoutChanged signals, and show re-sorted data + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("123")); + QCOMPARE(layoutATBCSpy.count(), 1); + QCOMPARE(layoutChangedSpy.count(), 1); + + // And the selection should be updated accordingly (it became row 2) + const QModelIndexList lstAfter = selection.selectedIndexes(); + QCOMPARE(lstAfter.count(), 3); + for (int col = 0; col < lstAfter.count(); ++col) { + QCOMPARE(lstAfter.at(col).row(), 2); + QCOMPARE(lstAfter.at(col).column(), col); + } +} + +void tst_QConcatenateTablesProxyModel::shouldReactToModelReset() +{ + // Given two source models, the second one being a QSFPM + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod); + QAbstractItemModelTester modelTest(&pm, this); + + QSortFilterProxyModel qsfpm; + qsfpm.setSourceModel(&mod3); + pm.addSourceModel(&qsfpm); + + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("456")); + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy colATBRSpy(&pm, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy colRemovedSpy(&pm, SIGNAL(columnsRemoved(QModelIndex,int,int))); + QSignalSpy modelATBResetSpy(&pm, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&pm, SIGNAL(modelReset())); + + // When changing the source model of the QSFPM + qsfpm.setSourceModel(&mod2); + + // Then the proxy should emit the reset signals, and show the new data + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABC")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + QCOMPARE(rowATBRSpy.count(), 0); + QCOMPARE(rowRemovedSpy.count(), 0); + QCOMPARE(rowATBISpy.count(), 0); + QCOMPARE(rowInsertedSpy.count(), 0); + QCOMPARE(colATBRSpy.count(), 0); + QCOMPARE(colRemovedSpy.count(), 0); + QCOMPARE(modelATBResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); +} + +void tst_QConcatenateTablesProxyModel::shouldUpdateColumnsOnModelReset() +{ + // Given two source models, the first one being a QSFPM + QConcatenateTablesProxyModel pm; + + QSortFilterProxyModel qsfpm; + qsfpm.setSourceModel(&mod3); + pm.addSourceModel(&qsfpm); + pm.addSourceModel(&mod); + QAbstractItemModelTester modelTest(&pm, this); + + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("ABC")); + + // ... and a model with only 2 columns + QStandardItemModel mod2Columns; + mod2Columns.appendRow({ new QStandardItem(QStringLiteral("W")), new QStandardItem(QStringLiteral("X")) }); + + QSignalSpy rowATBRSpy(&pm, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy rowRemovedSpy(&pm, SIGNAL(rowsRemoved(QModelIndex,int,int))); + QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int))); + QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int))); + QSignalSpy colATBRSpy(&pm, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int))); + QSignalSpy colRemovedSpy(&pm, SIGNAL(columnsRemoved(QModelIndex,int,int))); + QSignalSpy modelATBResetSpy(&pm, SIGNAL(modelAboutToBeReset())); + QSignalSpy modelResetSpy(&pm, SIGNAL(modelReset())); + + // When changing the source model of the QSFPM + qsfpm.setSourceModel(&mod2Columns); + + // Then the proxy should reset, and show the new data + QCOMPARE(modelATBResetSpy.count(), 1); + QCOMPARE(modelResetSpy.count(), 1); + QCOMPARE(rowATBRSpy.count(), 0); + QCOMPARE(rowRemovedSpy.count(), 0); + QCOMPARE(rowATBISpy.count(), 0); + QCOMPARE(rowInsertedSpy.count(), 0); + QCOMPARE(colATBRSpy.count(), 0); + QCOMPARE(colRemovedSpy.count(), 0); + + QCOMPARE(pm.rowCount(), 2); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("WX")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("AB")); +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropOnItem_data() +{ + QTest::addColumn<int>("sourceRow"); + QTest::addColumn<int>("destRow"); + QTest::addColumn<QString>("expectedResult"); + + QTest::newRow("0-3") << 0 << 3 << QStringLiteral("ABCA"); + QTest::newRow("1-2") << 1 << 2 << QStringLiteral("ABBD"); + QTest::newRow("2-1") << 2 << 1 << QStringLiteral("ACCD"); + QTest::newRow("3-0") << 3 << 0 << QStringLiteral("DBCD"); + +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropOnItem() +{ + // Given two source models who handle drops + + // Note: QStandardItemModel handles drop onto items by inserting child rows, + // which is good for QTreeView but not for QTableView or QConcatenateTablesProxyModel. + // So we use QStringListModel here instead. + QConcatenateTablesProxyModel pm; + QStringListModel model1({QStringLiteral("A"), QStringLiteral("B")}); + QStringListModel model2({QStringLiteral("C"), QStringLiteral("D")}); + pm.addSourceModel(&model1); + pm.addSourceModel(&model2); + QAbstractItemModelTester modelTest(&pm, this); + QCOMPARE(extractColumnTexts(&pm, 0), QStringLiteral("ABCD")); + + // When dragging one item + QFETCH(int, sourceRow); + QMimeData* mimeData = pm.mimeData({pm.index(sourceRow, 0)}); + QVERIFY(mimeData); + + // and dropping onto another item + QFETCH(int, destRow); + QVERIFY(pm.canDropMimeData(mimeData, Qt::CopyAction, -1, -1, pm.index(destRow, 0))); + QVERIFY(pm.dropMimeData(mimeData, Qt::CopyAction, -1, -1, pm.index(destRow, 0))); + delete mimeData; + + // Then the result should be as expected + QFETCH(QString, expectedResult); + QCOMPARE(extractColumnTexts(&pm, 0), expectedResult); +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropBetweenItems() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod3); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QCOMPARE(pm.rowCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF")); + + // When dragging the last row + QModelIndexList indexes; + indexes.reserve(pm.columnCount()); + for (int col = 0; col < pm.columnCount(); ++col) { + indexes.append(pm.index(2, col)); + } + QMimeData* mimeData = pm.mimeData(indexes); + QVERIFY(mimeData); + + // and dropping it before row 1 + const int destRow = 1; + QVERIFY(pm.canDropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + QVERIFY(pm.dropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + delete mimeData; + + // Then a new row should be inserted + QCOMPARE(pm.rowCount(), 4); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("DEF")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("DEF")); +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropBetweenItemsAtModelBoundary() +{ + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod3); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QCOMPARE(pm.rowCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF")); + + // When dragging the first row + QModelIndexList indexes; + indexes.reserve(pm.columnCount()); + for (int col = 0; col < pm.columnCount(); ++col) { + indexes.append(pm.index(0, col)); + } + QMimeData* mimeData = pm.mimeData(indexes); + QVERIFY(mimeData); + + // and dropping it before row 2 + const int destRow = 2; + QVERIFY(pm.canDropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + QVERIFY(pm.dropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + delete mimeData; + + // Then a new row should be inserted + QCOMPARE(pm.rowCount(), 4); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("DEF")); + + // and it should be part of the second model + QCOMPARE(mod2.rowCount(), 2); +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropAfterLastRow_data() +{ + QTest::addColumn<int>("destRow"); + + // Dropping after the last row is documented to be done with destRow == -1. + QTest::newRow("-1") << -1; + // However, sometimes QTreeView calls dropMimeData with destRow == rowCount... + // Not sure if that's a bug or not, but let's support it in the model, just in case. + QTest::newRow("3") << 3; +} + +void tst_QConcatenateTablesProxyModel::shouldPropagateDropAfterLastRow() +{ + QFETCH(int, destRow); + + // Given two models combined + QConcatenateTablesProxyModel pm; + pm.addSourceModel(&mod3); + pm.addSourceModel(&mod2); + QAbstractItemModelTester modelTest(&pm, this); + QCOMPARE(pm.rowCount(), 3); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF")); + + // When dragging the second row + QModelIndexList indexes; + indexes.reserve(pm.columnCount()); + for (int col = 0; col < pm.columnCount(); ++col) { + indexes.append(pm.index(1, col)); + } + QMimeData* mimeData = pm.mimeData(indexes); + QVERIFY(mimeData); + + // and dropping it after the last row + QVERIFY(pm.canDropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + QVERIFY(pm.dropMimeData(mimeData, Qt::CopyAction, destRow, 0, QModelIndex())); + delete mimeData; + + // Then a new row should be inserted at the end + QCOMPARE(pm.rowCount(), 4); + QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("123")); + QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("456")); + QCOMPARE(extractRowTexts(&pm, 2), QStringLiteral("DEF")); + QCOMPARE(extractRowTexts(&pm, 3), QStringLiteral("456")); + +} + +QTEST_GUILESS_MAIN(tst_QConcatenateTablesProxyModel) + +#include "tst_qconcatenatetablesproxymodel.moc" diff --git a/tests/auto/corelib/itemmodels/qitemmodel/modelstotest.cpp b/tests/auto/corelib/itemmodels/qitemmodel/modelstotest.cpp index 6ea7a38137..dbc7173028 100644 --- a/tests/auto/corelib/itemmodels/qitemmodel/modelstotest.cpp +++ b/tests/auto/corelib/itemmodels/qitemmodel/modelstotest.cpp @@ -251,7 +251,7 @@ QModelIndex ModelsToTest::populateTestArea(QAbstractItemModel *model) QString val = xval + QString::number(y) + QString::number(i); QModelIndex index = model->index(x, y, parent); model->setData(index, val); - model->setData(index, blue, Qt::TextColorRole); + model->setData(index, blue, Qt::ForegroundRole); } } */ @@ -276,7 +276,7 @@ QModelIndex ModelsToTest::populateTestArea(QAbstractItemModel *model) QString val = xval + QString::number(y) + QString::number(i); QModelIndex index = realModel->index(x, y, parent); realModel->setData(index, val); - realModel->setData(index, blue, Qt::TextColorRole); + realModel->setData(index, blue, Qt::ForegroundRole); } } */ diff --git a/tests/auto/corelib/itemmodels/qitemmodel/tst_qitemmodel.cpp b/tests/auto/corelib/itemmodels/qitemmodel/tst_qitemmodel.cpp index 7cd220e684..da13b9f33f 100644 --- a/tests/auto/corelib/itemmodels/qitemmodel/tst_qitemmodel.cpp +++ b/tests/auto/corelib/itemmodels/qitemmodel/tst_qitemmodel.cpp @@ -576,12 +576,12 @@ void tst_QItemModel::data() alignment == Qt::AlignJustify); } - QVariant colorVariant = currentModel->data(currentModel->index(0,0), Qt::BackgroundColorRole); + QVariant colorVariant = currentModel->data(currentModel->index(0,0), Qt::BackgroundRole); if (colorVariant.isValid()) { QVERIFY(colorVariant.canConvert<QColor>()); } - colorVariant = currentModel->data(currentModel->index(0,0), Qt::TextColorRole); + colorVariant = currentModel->data(currentModel->index(0,0), Qt::ForegroundRole); if (colorVariant.isValid()) { QVERIFY(colorVariant.canConvert<QColor>()); } diff --git a/tests/auto/corelib/itemmodels/qstringlistmodel/tst_qstringlistmodel.cpp b/tests/auto/corelib/itemmodels/qstringlistmodel/tst_qstringlistmodel.cpp index 9a54c0a70d..16e5170a47 100644 --- a/tests/auto/corelib/itemmodels/qstringlistmodel/tst_qstringlistmodel.cpp +++ b/tests/auto/corelib/itemmodels/qstringlistmodel/tst_qstringlistmodel.cpp @@ -82,8 +82,116 @@ private slots: void setData_emits_both_roles(); void supportedDragDropActions(); + + void moveRows_data(); + void moveRows(); + void moveRowsInvalid_data(); + void moveRowsInvalid(); + + void itemData(); + void setItemData(); }; +void tst_QStringListModel::moveRowsInvalid_data() +{ + QTest::addColumn<QStringListModel*>("baseModel"); + QTest::addColumn<QModelIndex>("startParent"); + QTest::addColumn<int>("startRow"); + QTest::addColumn<int>("count"); + QTest::addColumn<QModelIndex>("destinationParent"); + QTest::addColumn<int>("destination"); + + QStringListModel* tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("destination_equal_source") << tempModel << QModelIndex() << 0 << 1 << QModelIndex() << 1; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("count_equal_0") << tempModel << QModelIndex() << 0 << 0 << QModelIndex() << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("move_child") << tempModel << tempModel->index(0, 0) << 0 << 1 << QModelIndex() << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("move_to_child") << tempModel << QModelIndex() << 0 << 1 << tempModel->index(0, 0) << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("negative_count") << tempModel << QModelIndex() << 0 << -1 << QModelIndex() << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("negative_source_row") << tempModel << QModelIndex() << -1 << 1 << QModelIndex() << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("negative_destination_row") << tempModel << QModelIndex() << 0 << 1 << QModelIndex() << -1; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("source_row_equal_rowCount") << tempModel << QModelIndex() << tempModel->rowCount() << 1 << QModelIndex() << 1; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("destination_row_greater_rowCount") << tempModel << QModelIndex() << 0 << 1 << QModelIndex() << tempModel->rowCount() + 1; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("move_row_within_source_range") << tempModel << QModelIndex() << 0 << 3 << QModelIndex() << 2; + tempModel = new QStringListModel(QStringList{"A", "B", "C", "D", "E", "F"}, this); + QTest::addRow("destination_row_before_0") << tempModel << QModelIndex() << 1 << 1 << QModelIndex() << 0; +} + +void tst_QStringListModel::moveRowsInvalid() +{ + QFETCH(QStringListModel* const, baseModel); + QFETCH(const QModelIndex, startParent); + QFETCH(const int, startRow); + QFETCH(const int, count); + QFETCH(const QModelIndex, destinationParent); + QFETCH(const int, destination); + + QSignalSpy rowMovedSpy(baseModel, &QAbstractItemModel::rowsMoved); + QSignalSpy rowAboutMovedSpy(baseModel, &QAbstractItemModel::rowsAboutToBeMoved); + QVERIFY(rowMovedSpy.isValid()); + QVERIFY(rowAboutMovedSpy.isValid()); + QVERIFY(!baseModel->moveRows(startParent, startRow, count, destinationParent, destination)); + QCOMPARE(rowMovedSpy.size(), 0); + QCOMPARE(rowAboutMovedSpy.size(), 0); + delete baseModel; +} + +void tst_QStringListModel::moveRows_data() +{ + QTest::addColumn<int>("startRow"); + QTest::addColumn<int>("count"); + QTest::addColumn<int>("destination"); + QTest::addColumn<QStringList>("expected"); + + QTest::newRow("1_Item_from_top_to_middle") << 0 << 1 << 3 << QStringList{"B", "C", "A", "D", "E", "F"}; + QTest::newRow("1_Item_from_top_to_bottom") << 0 << 1 << 6 << QStringList{"B", "C", "D", "E", "F", "A"}; + QTest::newRow("1_Item_from_middle_to_top") << 2 << 1 << 1 << QStringList{"C", "A", "B", "D", "E", "F"}; + QTest::newRow("1_Item_from_bottom_to_middle") << 5 << 1 << 3 << QStringList{"A", "B", "F", "C", "D", "E"}; + QTest::newRow("1_Item_from_bottom to_top") << 5 << 1 << 1 << QStringList{"F", "A", "B", "C", "D", "E"}; + QTest::newRow("1_Item_from_middle_to_bottom") << 2 << 1 << 6 << QStringList{"A", "B", "D", "E", "F", "C"}; + QTest::newRow("1_Item_from_middle_to_middle_before") << 2 << 1 << 1 << QStringList{"C", "A", "B", "D", "E", "F"}; + QTest::newRow("1_Item_from_middle_to_middle_after") << 2 << 1 << 4 << QStringList{"A", "B", "D", "C", "E", "F"}; + + QTest::newRow("2_Items_from_top_to_middle") << 0 << 2 << 3 << QStringList{"C", "A", "B", "D", "E", "F"}; + QTest::newRow("2_Items_from_top_to_bottom") << 0 << 2 << 6 << QStringList{"C", "D", "E", "F", "A", "B"}; + QTest::newRow("2_Items_from_middle_to_top") << 2 << 2 << 1 << QStringList{"C", "D", "A", "B", "E", "F"}; + QTest::newRow("2_Items_from_bottom_to_middle") << 4 << 2 << 3 << QStringList{"A", "B", "E", "F", "C", "D"}; + QTest::newRow("2_Items_from_bottom_to_top") << 4 << 2 << 1 << QStringList{"E", "F", "A", "B", "C", "D"}; + QTest::newRow("2_Items_from_middle_to_bottom") << 2 << 2 << 6 << QStringList{"A", "B", "E", "F", "C", "D"}; + QTest::newRow("2_Items_from_middle_to_middle_before") << 3 << 2 << 2 << QStringList{"A", "D", "E", "B", "C", "F"}; + QTest::newRow("2_Items_from_middle_to_middle_after") << 1 << 2 << 5 << QStringList{"A", "D", "E", "B", "C", "F"}; +} + +void tst_QStringListModel::moveRows() +{ + QFETCH(const int, startRow); + QFETCH(const int, count); + QFETCH(const int, destination); + QFETCH(const QStringList, expected); + QStringListModel baseModel(QStringList{"A", "B", "C", "D", "E", "F"}); + QSignalSpy rowMovedSpy(&baseModel, &QAbstractItemModel::rowsMoved); + QSignalSpy rowAboutMovedSpy(&baseModel, &QAbstractItemModel::rowsAboutToBeMoved); + QVERIFY(baseModel.moveRows(QModelIndex(), startRow, count, QModelIndex(), destination)); + QCOMPARE(baseModel.stringList(), expected); + QCOMPARE(rowMovedSpy.size(), 1); + QCOMPARE(rowAboutMovedSpy.size(), 1); + for (const QList<QVariant> &signalArgs : {rowMovedSpy.first(), rowAboutMovedSpy.first()}){ + QVERIFY(!signalArgs.at(0).value<QModelIndex>().isValid()); + QCOMPARE(signalArgs.at(1).toInt(), startRow); + QCOMPARE(signalArgs.at(2).toInt(), startRow + count - 1); + QVERIFY(!signalArgs.at(3).value<QModelIndex>().isValid()); + QCOMPARE(signalArgs.at(4).toInt(), destination); + } +} + void tst_QStringListModel::rowsAboutToBeRemoved_rowsRemoved_data() { QTest::addColumn<QStringList>("input"); @@ -246,6 +354,74 @@ void tst_QStringListModel::setData_emits_both_roles() expected); } +void tst_QStringListModel::itemData() +{ + QStringListModel testModel{ QStringList { + QStringLiteral("One"), + QStringLiteral("Two"), + QStringLiteral("Three"), + QStringLiteral("Four"), + QStringLiteral("Five") + }}; + QMap<int, QVariant> compareMap; + QCOMPARE(testModel.itemData(QModelIndex()), compareMap); + compareMap.insert(Qt::DisplayRole, QStringLiteral("Two")); + compareMap.insert(Qt::EditRole, QStringLiteral("Two")); + QCOMPARE(testModel.itemData(testModel.index(1, 0)), compareMap); +} + +void tst_QStringListModel::setItemData() +{ + QStringListModel testModel{ QStringList { + QStringLiteral("One"), + QStringLiteral("Two"), + QStringLiteral("Three"), + QStringLiteral("Four"), + QStringLiteral("Five") + }}; + QSignalSpy dataChangedSpy(&testModel, &QAbstractItemModel::dataChanged); + QModelIndex changeIndex = testModel.index(1, 0); + const QVector<int> changeRoles{Qt::DisplayRole, Qt::EditRole}; + const QString changedString("Changed"); + QMap<int, QVariant> newItemData{std::make_pair<int>(Qt::DisplayRole, changedString)}; + // invalid index does nothing and returns false + QVERIFY(!testModel.setItemData(QModelIndex(), newItemData)); + // valid data is set, return value is true and dataChanged is emitted once + QVERIFY(testModel.setItemData(changeIndex, newItemData)); + QCOMPARE(changeIndex.data(Qt::DisplayRole).toString(), changedString); + QCOMPARE(changeIndex.data(Qt::EditRole).toString(), changedString); + QCOMPARE(dataChangedSpy.size(), 1); + QVariantList dataChangedArguments = dataChangedSpy.takeFirst(); + QCOMPARE(dataChangedArguments.at(0).value<QModelIndex>(), changeIndex); + QCOMPARE(dataChangedArguments.at(1).value<QModelIndex>(), changeIndex); + QCOMPARE(dataChangedArguments.at(2).value<QVector<int> >(), changeRoles); + // Unsupported roles do nothing return false + newItemData.clear(); + newItemData.insert(Qt::UserRole, changedString); + QVERIFY(!testModel.setItemData(changeIndex, newItemData)); + QCOMPARE(dataChangedSpy.size(), 0); + // If some but not all the roles are supported it returns false and does nothing + newItemData.insert(Qt::EditRole, changedString); + changeIndex = testModel.index(2, 0); + QVERIFY(!testModel.setItemData(changeIndex, newItemData)); + QCOMPARE(changeIndex.data(Qt::DisplayRole).toString(), QStringLiteral("Three")); + QCOMPARE(changeIndex.data(Qt::EditRole).toString(), QStringLiteral("Three")); + QCOMPARE(dataChangedSpy.size(), 0); + // Qt::EditRole and Qt::DisplayRole are both set, Qt::EditRole takes precedence + newItemData.clear(); + newItemData.insert(Qt::EditRole, changedString); + newItemData.insert(Qt::DisplayRole, QStringLiteral("Ignored")); + changeIndex = testModel.index(3, 0); + QVERIFY(testModel.setItemData(changeIndex, newItemData)); + QCOMPARE(changeIndex.data(Qt::DisplayRole).toString(), changedString); + QCOMPARE(changeIndex.data(Qt::EditRole).toString(), changedString); + QCOMPARE(dataChangedSpy.size(), 1); + dataChangedArguments = dataChangedSpy.takeFirst(); + QCOMPARE(dataChangedArguments.at(0).value<QModelIndex>(), changeIndex); + QCOMPARE(dataChangedArguments.at(1).value<QModelIndex>(), changeIndex); + QCOMPARE(dataChangedArguments.at(2).value<QVector<int> >(), changeRoles); +} + void tst_QStringListModel::supportedDragDropActions() { QStringListModel model; diff --git a/tests/auto/corelib/itemmodels/qtransposeproxymodel/qtransposeproxymodel.pro b/tests/auto/corelib/itemmodels/qtransposeproxymodel/qtransposeproxymodel.pro new file mode 100644 index 0000000000..3834add115 --- /dev/null +++ b/tests/auto/corelib/itemmodels/qtransposeproxymodel/qtransposeproxymodel.pro @@ -0,0 +1,6 @@ +CONFIG += testcase +TARGET = tst_qtransposeproxymodel +QT = core gui testlib + +SOURCES = tst_qtransposeproxymodel.cpp + diff --git a/tests/auto/corelib/itemmodels/qtransposeproxymodel/tst_qtransposeproxymodel.cpp b/tests/auto/corelib/itemmodels/qtransposeproxymodel/tst_qtransposeproxymodel.cpp new file mode 100644 index 0000000000..a30ac46571 --- /dev/null +++ b/tests/auto/corelib/itemmodels/qtransposeproxymodel/tst_qtransposeproxymodel.cpp @@ -0,0 +1,915 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Luca Beldi <v.ronin@yahoo.it> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtCore module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QTest> +#include <QSignalSpy> +#include <QStandardItemModel> +#include <QStringListModel> +#include <QAbstractItemModelTester> +#include <random> + +#include <qtransposeproxymodel.h> + +class tst_QTransposeProxyModel : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void index(); + void data(); + void setData_data(); + void setData(); + void parent(); + void mapToSource(); + void mapFromSource(); + void basicTest_data(); + void basicTest(); + void sort(); + void insertRowBase_data(); + void insertRowBase(); + void insertColumnBase_data(); + void insertColumnBase(); + void insertColumnProxy_data(); + void insertColumnProxy(); + void insertRowProxy_data(); + void insertRowProxy(); + void removeRowBase_data(); + void removeRowBase(); + void removeColumnBase_data(); + void removeColumnBase(); + void removeColumnProxy_data(); + void removeColumnProxy(); + void removeRowProxy_data(); + void removeRowProxy(); + void headerData(); + void setHeaderData(); + void span(); + void itemData(); + void setItemData(); + void moveRowsBase(); + void moveColumnsProxy(); +private: + void testTransposed( + const QAbstractItemModel *const baseModel, + const QAbstractItemModel *const transposed, + const QModelIndex &baseParent = QModelIndex(), + const QModelIndex &transposedParent = QModelIndex() + ); + QAbstractItemModel *createListModel(QObject *parent); + QAbstractItemModel *createTableModel(QObject *parent); + QAbstractItemModel *createTreeModel(QObject *parent); +}; + +QAbstractItemModel *tst_QTransposeProxyModel::createListModel(QObject *parent) +{ + QStringList sequence; + sequence.reserve(10); + for (int i = 0; i < 10; ++i) + sequence.append(QString::number(i)); + return new QStringListModel(sequence, parent); +} + +QAbstractItemModel *tst_QTransposeProxyModel::createTableModel(QObject *parent) +{ + QAbstractItemModel *model = new QStandardItemModel(parent); + model->insertRows(0, 5); + model->insertColumns(0, 4); + for (int i = 0; i < model->rowCount(); ++i) { + for (int j = 0; j < model->columnCount(); ++j) { + model->setData(model->index(i, j), QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); + model->setData(model->index(i, j), i, Qt::UserRole); + model->setData(model->index(i, j), j, Qt::UserRole + 1); + } + } + return model; +} + +QAbstractItemModel *tst_QTransposeProxyModel::createTreeModel(QObject *parent) +{ + QAbstractItemModel *model = new QStandardItemModel(parent); + model->insertRows(0, 5); + model->insertColumns(0, 4); + for (int i = 0; i < model->rowCount(); ++i) { + for (int j = 0; j < model->columnCount(); ++j) { + const QModelIndex parIdx = model->index(i, j); + model->setData(parIdx, QStringLiteral("%1,%2").arg(i).arg(j), Qt::EditRole); + model->setData(parIdx, i, Qt::UserRole); + model->setData(parIdx, j, Qt::UserRole + 1); + model->insertRows(0, 3, parIdx); + model->insertColumns(0, 2, parIdx); + for (int h = 0; h < model->rowCount(parIdx); ++h) { + for (int k = 0; k < model->columnCount(parIdx); ++k) { + const QModelIndex childIdx = model->index(h, k, parIdx); + model->setData(childIdx, QStringLiteral("%1,%2,%3,%4").arg(i).arg(j).arg(h).arg(k), Qt::EditRole); + model->setData(childIdx, i, Qt::UserRole); + model->setData(childIdx, j, Qt::UserRole + 1); + model->setData(childIdx, h, Qt::UserRole + 2); + model->setData(childIdx, k, Qt::UserRole + 3); + } + } + } + } + return model; +} + +void tst_QTransposeProxyModel::testTransposed( + const QAbstractItemModel *const baseModel, + const QAbstractItemModel *const transposed, + const QModelIndex &baseParent, + const QModelIndex &transposedParent +) +{ + QCOMPARE(transposed->hasChildren(transposedParent), baseModel->hasChildren(baseParent)); + QCOMPARE(transposed->columnCount(transposedParent), baseModel->rowCount(baseParent)); + QCOMPARE(transposed->rowCount(transposedParent), baseModel->columnCount(baseParent)); + for (int i = 0, maxRow = baseModel->rowCount(baseParent); i < maxRow; ++i) { + for (int j = 0, maxCol = baseModel->columnCount(baseParent); j < maxCol; ++j) { + const QModelIndex baseIdx = baseModel->index(i, j, baseParent); + const QModelIndex transIdx = transposed->index(j, i, transposedParent); + QCOMPARE(transIdx.data(), baseIdx.data()); + QCOMPARE(transIdx.data(Qt::UserRole), baseIdx.data(Qt::UserRole)); + QCOMPARE(transIdx.data(Qt::UserRole + 1), baseIdx.data(Qt::UserRole + 1)); + QCOMPARE(transIdx.data(Qt::UserRole + 2), baseIdx.data(Qt::UserRole + 2)); + QCOMPARE(transIdx.data(Qt::UserRole + 3), baseIdx.data(Qt::UserRole + 3)); + if (baseModel->hasChildren(baseIdx)) { + testTransposed(baseModel, transposed, baseIdx, transIdx); + } + } + } +} + +void tst_QTransposeProxyModel::initTestCase() +{ + qRegisterMetaType<QList<QPersistentModelIndex> >(); + qRegisterMetaType<QAbstractItemModel::LayoutChangeHint>(); +} + +void tst_QTransposeProxyModel::index() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QVERIFY(!proxy.index(0, -1).isValid()); + QVERIFY(!proxy.index(0, -1).isValid()); + QVERIFY(!proxy.index(-1, -1).isValid()); + QVERIFY(!proxy.index(0, proxy.columnCount()).isValid()); + QVERIFY(!proxy.index(proxy.rowCount(), 0).isValid()); + QVERIFY(!proxy.index(proxy.rowCount(), proxy.columnCount()).isValid()); + QModelIndex tempIdx = proxy.index(0, 1); + QVERIFY(tempIdx.isValid()); + QCOMPARE(tempIdx.row(), 0); + QCOMPARE(tempIdx.column(), 1); + tempIdx = proxy.index(0, 1, tempIdx); + QVERIFY(tempIdx.isValid()); + QCOMPARE(tempIdx.row(), 0); + QCOMPARE(tempIdx.column(), 1); + delete model; +} + +void tst_QTransposeProxyModel::data() +{ + QStringListModel model{QStringList{"A", "B"}}; + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(&model); + QCOMPARE(proxy.index(0, 1).data().toString(), QStringLiteral("B")); +} + +void tst_QTransposeProxyModel::parent() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + const QModelIndex parentIdx = proxy.index(0, 0); + const QModelIndex childIdx = proxy.index(0, 0, parentIdx); + QVERIFY(parentIdx.isValid()); + QVERIFY(childIdx.isValid()); + QCOMPARE(childIdx.parent(), parentIdx); + delete model; +} + +void tst_QTransposeProxyModel::mapToSource() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QVERIFY(!proxy.mapToSource(QModelIndex()).isValid()); + QCOMPARE(proxy.mapToSource(proxy.index(0, 0)), model->index(0, 0)); + QCOMPARE(proxy.mapToSource(proxy.index(1, 0)), model->index(0, 1)); + QCOMPARE(proxy.mapToSource(proxy.index(0, 1)), model->index(1, 0)); + const QModelIndex proxyParent = proxy.index(1, 0); + const QModelIndex sourceParent = model->index(0, 1); + QCOMPARE(proxy.mapToSource(proxy.index(0, 0, proxyParent)), model->index(0, 0, sourceParent)); + QCOMPARE(proxy.mapToSource(proxy.index(1, 0, proxyParent)), model->index(0, 1, sourceParent)); + QCOMPARE(proxy.mapToSource(proxy.index(0, 1, proxyParent)), model->index(1, 0, sourceParent)); + delete model; +} + +void tst_QTransposeProxyModel::mapFromSource() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QVERIFY(!proxy.mapFromSource(QModelIndex()).isValid()); + QCOMPARE(proxy.mapFromSource(model->index(0, 0)), proxy.index(0, 0)); + QCOMPARE(proxy.mapFromSource(model->index(0, 1)), proxy.index(1, 0)); + QCOMPARE(proxy.mapFromSource(model->index(1, 0)), proxy.index(0, 1)); + const QModelIndex proxyParent = proxy.index(1, 0); + const QModelIndex sourceParent = model->index(0, 1); + QCOMPARE(proxy.mapToSource(proxy.index(0, 0, proxyParent)), model->index(0, 0, sourceParent)); + QCOMPARE(proxy.mapFromSource(model->index(1, 0, sourceParent)), proxy.index(0, 1, proxyParent)); + QCOMPARE(proxy.mapFromSource(model->index(0, 1, sourceParent)), proxy.index(1, 0, proxyParent)); + delete model; +} + +void tst_QTransposeProxyModel::basicTest_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::newRow("List") << createListModel(this); + QTest::newRow("Table") << createTableModel(this); + QTest::newRow("Tree") << createTreeModel(this); +} + +void tst_QTransposeProxyModel::basicTest() +{ + QFETCH(QAbstractItemModel *, model); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + testTransposed(model, &proxy); + delete model; +} + +void tst_QTransposeProxyModel::sort() +{ + QStringList sequence; + sequence.reserve(100); + for (int i = 0; i < 100; ++i) + sequence.append(QStringLiteral("%1").arg(i, 3, 10, QLatin1Char('0'))); + std::shuffle(sequence.begin(), sequence.end(), std::mt19937(88)); + const QString firstItemBeforeSort = sequence.first(); + QStringListModel baseModel(sequence); + QTransposeProxyModel proxyModel; + new QAbstractItemModelTester(&proxyModel, &proxyModel); + proxyModel.setSourceModel(&baseModel); + QSignalSpy layoutChangedSpy(&proxyModel, &QAbstractItemModel::layoutChanged); + QVERIFY(layoutChangedSpy.isValid()); + QSignalSpy layoutAboutToBeChangedSpy(&proxyModel, &QAbstractItemModel::layoutAboutToBeChanged); + QVERIFY(layoutAboutToBeChangedSpy.isValid()); + QPersistentModelIndex firstIndexBeforeSort = proxyModel.index(0, 0); + baseModel.sort(0, Qt::AscendingOrder); + QCOMPARE(layoutChangedSpy.count(), 1); + QCOMPARE(layoutAboutToBeChangedSpy.count(), 1); + QCOMPARE(layoutChangedSpy.takeFirst().at(1).toInt(), int(QAbstractItemModel::HorizontalSortHint)); + QCOMPARE(firstIndexBeforeSort.data().toString(), firstItemBeforeSort); + for (int i = 0; i < 100; ++i) + QCOMPARE(proxyModel.index(0, i).data().toInt(), i); +} + +void tst_QTransposeProxyModel::removeColumnBase_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<QModelIndex>("parent"); + QTest::newRow("Table") << createTableModel(this) << QModelIndex(); + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << QModelIndex(); + QAbstractItemModel *model = createTreeModel(this); + QTest::newRow("Tree_Child_Item") << model << model->index(0, 0); +} + +void tst_QTransposeProxyModel::removeColumnBase() +{ + QFETCH(QAbstractItemModel * const, model); + QFETCH(const QModelIndex, parent); + QTransposeProxyModel proxy; + QSignalSpy rowRemoveSpy(&proxy, &QAbstractItemModel::rowsRemoved); + QVERIFY(rowRemoveSpy.isValid()); + QSignalSpy rowAboutToBeRemoveSpy(&proxy, &QAbstractItemModel::rowsAboutToBeRemoved); + QVERIFY(rowAboutToBeRemoveSpy.isValid()); + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + const int oldRowCount = proxy.rowCount(proxy.mapFromSource(parent)); + const QVariant expectedNewVal = model->index(0, 2, parent).data(); + QVERIFY(model->removeColumn(1, parent)); + QCOMPARE(proxy.rowCount(proxy.mapFromSource(parent)), oldRowCount - 1); + QCOMPARE(proxy.index(1, 0, proxy.mapFromSource(parent)).data(), expectedNewVal); + QCOMPARE(rowRemoveSpy.count(), 1); + QCOMPARE(rowAboutToBeRemoveSpy.count(), 1); + for (const auto &spyArgs : {rowRemoveSpy.takeFirst(), + rowAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxy.mapFromSource(parent)); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::insertColumnBase_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<QModelIndex>("parent"); + QTest::newRow("Table") << createTableModel(this) << QModelIndex(); + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << QModelIndex(); + QAbstractItemModel *model = createTreeModel(this); + QTest::newRow("Tree_Child_Item") << model << model->index(0, 0); +} + +void tst_QTransposeProxyModel::insertColumnBase() +{ + QFETCH(QAbstractItemModel * const, model); + QFETCH(const QModelIndex, parent); + QTransposeProxyModel proxy; + QSignalSpy rowInsertSpy(&proxy, &QAbstractItemModel::rowsInserted); + QVERIFY(rowInsertSpy.isValid()); + QSignalSpy rowAboutToBeInsertSpy(&proxy, &QAbstractItemModel::rowsAboutToBeInserted); + QVERIFY(rowAboutToBeInsertSpy.isValid()); + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + const int oldRowCount = proxy.rowCount(proxy.mapFromSource(parent)); + QVERIFY(model->insertColumn(1, parent)); + QCOMPARE(proxy.rowCount(proxy.mapFromSource(parent)), oldRowCount + 1); + QVERIFY(!proxy.index(1, 0, proxy.mapFromSource(parent)).data().isValid()); + QCOMPARE(rowInsertSpy.count(), 1); + QCOMPARE(rowAboutToBeInsertSpy.count(), 1); + for (const auto &spyArgs : {rowInsertSpy.takeFirst(), + rowAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxy.mapFromSource(parent)); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::removeRowBase_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<QModelIndex>("parent"); + QTest::newRow("List") << createListModel(this) << QModelIndex(); + QTest::newRow("Table") << createTableModel(this) << QModelIndex(); + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << QModelIndex(); + QAbstractItemModel *model = createTreeModel(this); + QTest::newRow("Tree_Child_Item") << model << model->index(0, 0); +} + +void tst_QTransposeProxyModel::removeRowBase() +{ + QFETCH(QAbstractItemModel * const, model); + QFETCH(const QModelIndex, parent); + QTransposeProxyModel proxy; + QSignalSpy columnsRemoveSpy(&proxy, &QAbstractItemModel::columnsRemoved); + QVERIFY(columnsRemoveSpy.isValid()); + QSignalSpy columnsAboutToBeRemoveSpy(&proxy, &QAbstractItemModel::columnsAboutToBeRemoved); + QVERIFY(columnsAboutToBeRemoveSpy.isValid()); + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + const int oldColCount = proxy.columnCount(proxy.mapFromSource(parent)); + const QVariant expectedNewVal = model->index(2, 0, parent).data(); + QVERIFY(model->removeRow(1, parent)); + QCOMPARE(proxy.columnCount(proxy.mapFromSource(parent)), oldColCount - 1); + QCOMPARE(proxy.index(0, 1, proxy.mapFromSource(parent)).data(), expectedNewVal); + QCOMPARE(columnsRemoveSpy.count(), 1); + QCOMPARE(columnsAboutToBeRemoveSpy.count(), 1); + for (const auto &spyArgs : {columnsRemoveSpy.takeFirst(), + columnsAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxy.mapFromSource(parent)); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::insertRowBase_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<QModelIndex>("parent"); + QTest::newRow("List") << createListModel(this) << QModelIndex(); + QTest::newRow("Table") << createTableModel(this) << QModelIndex(); + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << QModelIndex(); + QAbstractItemModel *model = createTreeModel(this); + QTest::newRow("Tree_Child_Item") << model << model->index(0, 0); +} + +void tst_QTransposeProxyModel::insertRowBase() +{ + QFETCH(QAbstractItemModel * const, model); + QFETCH(const QModelIndex, parent); + QTransposeProxyModel proxy; + QSignalSpy columnsInsertSpy(&proxy, &QAbstractItemModel::columnsInserted); + QVERIFY(columnsInsertSpy.isValid()); + QSignalSpy columnsAboutToBeInsertSpy(&proxy, &QAbstractItemModel::columnsAboutToBeInserted); + QVERIFY(columnsAboutToBeInsertSpy.isValid()); + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + const int oldColCount = proxy.columnCount(proxy.mapFromSource(parent)); + QVERIFY(model->insertRow(1, parent)); + QCOMPARE(proxy.columnCount(proxy.mapFromSource(parent)), oldColCount + 1); + QVERIFY(proxy.index(0, 1, proxy.mapFromSource(parent)).data().isNull()); + QCOMPARE(columnsInsertSpy.count(), 1); + QCOMPARE(columnsAboutToBeInsertSpy.count(), 1); + for (const auto &spyArgs : {columnsInsertSpy.takeFirst(), + columnsAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxy.mapFromSource(parent)); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::removeColumnProxy_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<bool>("rootItem"); + QTest::newRow("List") << createListModel(this) << true; + QTest::newRow("Table") << createTableModel(this) << true; + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << true; + QTest::newRow("Tree_Child_Item") << createTreeModel(this) << false; +} + +void tst_QTransposeProxyModel::removeColumnProxy() +{ + QFETCH(QAbstractItemModel *, model); + QFETCH(bool, rootItem); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + QSignalSpy columnsRemoveSpy(&proxy, &QAbstractItemModel::columnsRemoved); + QVERIFY(columnsRemoveSpy.isValid()); + QSignalSpy columnsAboutToBeRemoveSpy(&proxy, &QAbstractItemModel::columnsAboutToBeRemoved); + QVERIFY(columnsAboutToBeRemoveSpy.isValid()); + QSignalSpy rowsRemoveSpy(model, &QAbstractItemModel::rowsRemoved); + QVERIFY(rowsRemoveSpy.isValid()); + QSignalSpy rowsAboutToBeRemoveSpy(model, &QAbstractItemModel::rowsAboutToBeRemoved); + QVERIFY(rowsAboutToBeRemoveSpy.isValid()); + proxy.setSourceModel(model); + const QModelIndex proxyParent = rootItem ? QModelIndex() : proxy.index(0, 1); + const QModelIndex sourceParent = proxy.mapToSource(proxyParent); + const int oldColCount = proxy.columnCount(proxyParent); + const int oldRowCount = model->rowCount(sourceParent); + const QVariant expectedNewVal = proxy.index(0, 2, proxyParent).data(); + QVERIFY(proxy.removeColumn(1, proxyParent)); + QCOMPARE(proxy.columnCount(proxyParent), oldColCount - 1); + QCOMPARE(model->rowCount(sourceParent), oldRowCount - 1); + QCOMPARE(proxy.index(0, 1, proxyParent).data(), expectedNewVal); + QCOMPARE(model->index(1, 0, sourceParent).data(), expectedNewVal); + QCOMPARE(columnsRemoveSpy.count(), 1); + QCOMPARE(columnsAboutToBeRemoveSpy.count(), 1); + QCOMPARE(rowsRemoveSpy.count(), 1); + QCOMPARE(rowsAboutToBeRemoveSpy.count(), 1); + for (const auto &spyArgs : {columnsRemoveSpy.takeFirst(), + columnsAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxyParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + for (const auto &spyArgs : {rowsRemoveSpy.takeFirst(), + rowsAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), sourceParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::insertColumnProxy_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<bool>("rootItem"); + QTest::newRow("List") << createListModel(this) << true; + QTest::newRow("Table") << createTableModel(this) << true; + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << true; + QTest::newRow("Tree_Child_Item") << createTreeModel(this) << false; +} + +void tst_QTransposeProxyModel::insertColumnProxy() +{ + QFETCH(QAbstractItemModel *, model); + QFETCH(bool, rootItem); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + QSignalSpy columnsInsertSpy(&proxy, &QAbstractItemModel::columnsInserted); + QVERIFY(columnsInsertSpy.isValid()); + QSignalSpy columnsAboutToBeInsertSpy(&proxy, &QAbstractItemModel::columnsAboutToBeInserted); + QVERIFY(columnsAboutToBeInsertSpy.isValid()); + QSignalSpy rowsInsertSpy(model, &QAbstractItemModel::rowsInserted); + QVERIFY(rowsInsertSpy.isValid()); + QSignalSpy rowsAboutToBeInsertSpy(model, &QAbstractItemModel::rowsAboutToBeInserted); + QVERIFY(rowsAboutToBeInsertSpy.isValid()); + proxy.setSourceModel(model); + const QModelIndex proxyParent = rootItem ? QModelIndex() : proxy.index(0, 1); + const QModelIndex sourceParent = proxy.mapToSource(proxyParent); + const int oldColCount = proxy.columnCount(proxyParent); + const int oldRowCount = model->rowCount(sourceParent); + QVERIFY(proxy.insertColumn(1, proxyParent)); + QCOMPARE(proxy.columnCount(proxyParent), oldColCount + 1); + QCOMPARE(model->rowCount(sourceParent), oldRowCount + 1); + QVERIFY(proxy.index(0, 1, proxyParent).data().isNull()); + QVERIFY(model->index(1, 0, sourceParent).data().isNull()); + QCOMPARE(columnsInsertSpy.count(), 1); + QCOMPARE(columnsAboutToBeInsertSpy.count(), 1); + QCOMPARE(rowsInsertSpy.count(), 1); + QCOMPARE(rowsAboutToBeInsertSpy.count(), 1); + for (const auto &spyArgs : {columnsInsertSpy.takeFirst(), + columnsAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxyParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + for (const auto &spyArgs : {rowsInsertSpy.takeFirst(), + rowsAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), sourceParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::removeRowProxy_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<bool>("rootItem"); + QTest::newRow("Table") << createTableModel(this) << true; + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << true; + QTest::newRow("Tree_Child_Item") << createTreeModel(this) << false; +} + +void tst_QTransposeProxyModel::removeRowProxy() +{ + QFETCH(QAbstractItemModel *, model); + QFETCH(bool, rootItem); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + QSignalSpy rowsRemoveSpy(&proxy, &QAbstractItemModel::rowsRemoved); + QVERIFY(rowsRemoveSpy.isValid()); + QSignalSpy rowsAboutToBeRemoveSpy(&proxy, &QAbstractItemModel::rowsAboutToBeRemoved); + QVERIFY(rowsAboutToBeRemoveSpy.isValid()); + QSignalSpy columnsRemoveSpy(model, &QAbstractItemModel::columnsRemoved); + QVERIFY(columnsRemoveSpy.isValid()); + QSignalSpy columnsAboutToBeRemoveSpy(model, &QAbstractItemModel::columnsAboutToBeRemoved); + QVERIFY(columnsAboutToBeRemoveSpy.isValid()); + proxy.setSourceModel(model); + const QModelIndex proxyParent = rootItem ? QModelIndex() : proxy.index(0, 1); + const QModelIndex sourceParent = proxy.mapToSource(proxyParent); + const int oldRowCount = proxy.rowCount(proxyParent); + const int oldColCount = model->columnCount(sourceParent); + const QVariant expectedNewVal = proxy.index(2, 0, proxyParent).data(); + QVERIFY(proxy.removeRow(1, proxyParent)); + QCOMPARE(proxy.rowCount(proxyParent), oldRowCount - 1); + QCOMPARE(model->columnCount(sourceParent), oldColCount - 1); + QCOMPARE(proxy.index(1, 0, proxyParent).data(), expectedNewVal); + QCOMPARE(model->index(0, 1, sourceParent).data(), expectedNewVal); + QCOMPARE(columnsRemoveSpy.count(), 1); + QCOMPARE(columnsAboutToBeRemoveSpy.count(), 1); + QCOMPARE(rowsRemoveSpy.count(), 1); + QCOMPARE(rowsAboutToBeRemoveSpy.count(), 1); + for (const auto &spyArgs : {columnsRemoveSpy.takeFirst(), + columnsAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), sourceParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + for (const auto &spyArgs : {rowsRemoveSpy.takeFirst(), + rowsAboutToBeRemoveSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxyParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::insertRowProxy_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<bool>("rootItem"); + QTest::newRow("Table") << createTableModel(this) << true; + QTest::newRow("Tree_Root_Item") << createTreeModel(this) << true; + QTest::newRow("Tree_Child_Item") << createTreeModel(this) << false; +} + +void tst_QTransposeProxyModel::insertRowProxy() +{ + QFETCH(QAbstractItemModel *, model); + QFETCH(bool, rootItem); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + QSignalSpy rowsInsertSpy(&proxy, &QAbstractItemModel::rowsInserted); + QVERIFY(rowsInsertSpy.isValid()); + QSignalSpy rowsAboutToBeInsertSpy(&proxy, &QAbstractItemModel::rowsAboutToBeInserted); + QVERIFY(rowsAboutToBeInsertSpy.isValid()); + QSignalSpy columnsInsertSpy(model, &QAbstractItemModel::columnsInserted); + QVERIFY(columnsInsertSpy.isValid()); + QSignalSpy columnsAboutToBeInsertSpy(model, &QAbstractItemModel::columnsAboutToBeInserted); + QVERIFY(columnsAboutToBeInsertSpy.isValid()); + proxy.setSourceModel(model); + const QModelIndex proxyParent = rootItem ? QModelIndex() : proxy.index(0, 1); + const QModelIndex sourceParent = proxy.mapToSource(proxyParent); + const int oldRowCount = proxy.rowCount(proxyParent); + const int oldColCount = model->columnCount(sourceParent); + QVERIFY(proxy.insertRow(1, proxyParent)); + QCOMPARE(proxy.rowCount(proxyParent), oldRowCount + 1); + QCOMPARE(model->columnCount(sourceParent), oldColCount + 1); + QVERIFY(proxy.index(1, 0, proxyParent).data().isNull()); + QVERIFY(model->index(0, 1, sourceParent).data().isNull()); + QCOMPARE(columnsInsertSpy.count(), 1); + QCOMPARE(columnsAboutToBeInsertSpy.count(), 1); + QCOMPARE(rowsInsertSpy.count(), 1); + QCOMPARE(rowsAboutToBeInsertSpy.count(), 1); + for (const auto &spyArgs : {columnsInsertSpy.takeFirst(), + columnsAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), sourceParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + for (const auto &spyArgs : {rowsInsertSpy.takeFirst(), + rowsAboutToBeInsertSpy.takeFirst()}) { + QCOMPARE(spyArgs.at(0).value<QModelIndex>(), proxyParent); + QCOMPARE(spyArgs.at(1).toInt(), 1); + QCOMPARE(spyArgs.at(2).toInt(), 1); + } + delete model; +} + +void tst_QTransposeProxyModel::headerData() +{ + QStandardItemModel model; + model.insertRows(0, 3); + model.insertColumns(0, 5); + for (int i = 0; i < model.rowCount(); ++i) + model.setHeaderData(i, Qt::Horizontal, QChar('A' + i)); + for (int i = 1; i <= model.columnCount(); ++i) + model.setHeaderData(i, Qt::Vertical, i); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(&model); + for (int i = 0; i < model.rowCount(); ++i) + QCOMPARE(model.headerData(i, Qt::Horizontal), proxy.headerData(i, Qt::Vertical)); + for (int i = 0; i < model.columnCount(); ++i) + QCOMPARE(model.headerData(i, Qt::Vertical), proxy.headerData(i, Qt::Horizontal)); +} + +void tst_QTransposeProxyModel::setHeaderData() +{ + QStandardItemModel model; + model.insertRows(0, 3); + model.insertColumns(0, 5); + for (int i = 0; i < model.rowCount(); ++i) + model.setHeaderData(i, Qt::Horizontal, QChar('A' + i)); + for (int i = 1; i <= model.columnCount(); ++i) + model.setHeaderData(i, Qt::Vertical, i); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(&model); + QVERIFY(proxy.setHeaderData(1, Qt::Horizontal, 99)); + QCOMPARE(model.headerData(1, Qt::Vertical).toInt(), 99); + QVERIFY(proxy.setHeaderData(1, Qt::Vertical, QChar('Z'))); + QCOMPARE(model.headerData(1, Qt::Horizontal).toChar(), QChar('Z')); +} + +void tst_QTransposeProxyModel::span() +{ + class SpanModel : public QStandardItemModel + { + Q_DISABLE_COPY(SpanModel) + public: + SpanModel(int rows, int columns, QObject *parent = nullptr) + : QStandardItemModel(rows, columns, parent) + {} + QSize span(const QModelIndex &index) const override + { + Q_UNUSED(index) + return QSize(2, 1); + } + }; + SpanModel model(3, 5); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(&model); + QCOMPARE(proxy.span(proxy.index(0, 0)), QSize(1, 2)); +} + +void tst_QTransposeProxyModel::itemData() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QMap<int, QVariant> itmData = proxy.itemData(proxy.index(0, 1)); + QCOMPARE(itmData.value(Qt::DisplayRole).toString(), QStringLiteral("1,0")); + QCOMPARE(itmData.value(Qt::UserRole).toInt(), 1); + QCOMPARE(itmData.value(Qt::UserRole + 1).toInt(), 0); + itmData = proxy.itemData(proxy.index(1, 2, proxy.index(0, 1))); + QCOMPARE(itmData.value(Qt::DisplayRole).toString(), QStringLiteral("1,0,2,1")); + QCOMPARE(itmData.value(Qt::UserRole).toInt(), 1); + QCOMPARE(itmData.value(Qt::UserRole + 1).toInt(), 0); + QCOMPARE(itmData.value(Qt::UserRole + 2).toInt(), 2); + QCOMPARE(itmData.value(Qt::UserRole + 3).toInt(), 1); + QVERIFY(proxy.itemData(QModelIndex()).isEmpty()); + delete model; +} + +void tst_QTransposeProxyModel::setItemData() +{ + QAbstractItemModel *model = createTreeModel(this); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QSignalSpy sourceDataChangeSpy(model, &QAbstractItemModel::dataChanged); + QVERIFY(sourceDataChangeSpy.isValid()); + QSignalSpy proxyDataChangeSpy(&proxy, &QAbstractItemModel::dataChanged); + QVERIFY(proxyDataChangeSpy.isValid()); + const QMap<int, QVariant> itmData = { + std::make_pair<int, QVariant>(Qt::DisplayRole, QStringLiteral("Test")), + std::make_pair<int, QVariant>(Qt::UserRole, 88), + std::make_pair<int, QVariant>(Qt::UserRole + 1, 99), + }; + QModelIndex idx = proxy.index(0, 1); + QVERIFY(proxy.setItemData(idx, itmData)); + QCOMPARE(idx.data(Qt::DisplayRole).toString(), QStringLiteral("Test")); + QCOMPARE(idx.data(Qt::UserRole).toInt(), 88); + QCOMPARE(idx.data(Qt::UserRole + 1).toInt(), 99); + QCOMPARE(sourceDataChangeSpy.size(), 1); + QCOMPARE(proxyDataChangeSpy.size(), 1); + auto signalData = proxyDataChangeSpy.takeFirst(); + QCOMPARE(signalData.at(0).value<QModelIndex>(), idx); + QCOMPARE(signalData.at(1).value<QModelIndex>(), idx); + const QVector<int> expectedRoles{Qt::DisplayRole, Qt::UserRole, Qt::EditRole, Qt::UserRole + 1}; + QVector<int> receivedRoles = signalData.at(2).value<QVector<int> >(); + QCOMPARE(receivedRoles.size(), expectedRoles.size()); + for (int role : expectedRoles) + QVERIFY(receivedRoles.contains(role)); + signalData = sourceDataChangeSpy.takeFirst(); + QCOMPARE(signalData.at(0).value<QModelIndex>(), proxy.mapToSource(idx)); + QCOMPARE(signalData.at(1).value<QModelIndex>(), proxy.mapToSource(idx)); + receivedRoles = signalData.at(2).value<QVector<int> >(); + QCOMPARE(receivedRoles.size(), expectedRoles.size()); + for (int role : expectedRoles) + QVERIFY(receivedRoles.contains(role)); + idx = proxy.index(1, 2, proxy.index(0, 1)); + QVERIFY(proxy.setItemData(idx, itmData)); + QCOMPARE(idx.data(Qt::DisplayRole).toString(), QStringLiteral("Test")); + QCOMPARE(idx.data(Qt::UserRole).toInt(), 88); + QCOMPARE(idx.data(Qt::UserRole + 1).toInt(), 99); + QCOMPARE(idx.data(Qt::UserRole + 2).toInt(), 2); + QCOMPARE(idx.data(Qt::UserRole + 3).toInt(), 1); + QCOMPARE(sourceDataChangeSpy.size(), 1); + QCOMPARE(proxyDataChangeSpy.size(), 1); + signalData = proxyDataChangeSpy.takeFirst(); + QCOMPARE(signalData.at(0).value<QModelIndex>(), idx); + QCOMPARE(signalData.at(1).value<QModelIndex>(), idx); + receivedRoles = signalData.at(2).value<QVector<int> >(); + QCOMPARE(receivedRoles.size(), expectedRoles.size()); + for (int role : expectedRoles) + QVERIFY(receivedRoles.contains(role)); + signalData = sourceDataChangeSpy.takeFirst(); + QCOMPARE(signalData.at(0).value<QModelIndex>(), proxy.mapToSource(idx)); + QCOMPARE(signalData.at(1).value<QModelIndex>(), proxy.mapToSource(idx)); + receivedRoles = signalData.at(2).value<QVector<int> >(); + QCOMPARE(receivedRoles.size(), expectedRoles.size()); + for (int role : expectedRoles) + QVERIFY(receivedRoles.contains(role)); + QVERIFY(!proxy.setItemData(QModelIndex(), itmData)); + delete model; +} + +void tst_QTransposeProxyModel::moveRowsBase() +{ + QStringListModel model{QStringList{"A", "B", "C", "D"}}; + QTransposeProxyModel proxy; + QSignalSpy columnsMoveSpy(&proxy, &QAbstractItemModel::columnsMoved); + QVERIFY(columnsMoveSpy.isValid()); + QSignalSpy columnsAboutToBeMoveSpy(&proxy, &QAbstractItemModel::columnsAboutToBeMoved); + QVERIFY(columnsAboutToBeMoveSpy.isValid()); + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(&model); + const QStringList expectedNewVal = {"B", "A", "C", "D"}; + QVERIFY(model.moveRows(QModelIndex(), 0, 1, QModelIndex(), 2)); + for (int i = 0; i < expectedNewVal.size(); ++i) + QCOMPARE(proxy.index(0, i).data(), expectedNewVal.at(i)); + QCOMPARE(columnsMoveSpy.count(), 1); + QCOMPARE(columnsAboutToBeMoveSpy.count(), 1); + for (const auto &spyArgs : {columnsMoveSpy.takeFirst(), + columnsAboutToBeMoveSpy.takeFirst()}) { + QVERIFY(!spyArgs.at(0).value<QModelIndex>().isValid()); + QCOMPARE(spyArgs.at(1).toInt(), 0); + QCOMPARE(spyArgs.at(2).toInt(), 0); + QVERIFY(!spyArgs.at(3).value<QModelIndex>().isValid()); + QCOMPARE(spyArgs.at(4).toInt(), 2); + } +} + +void tst_QTransposeProxyModel::moveColumnsProxy() +{ + QStringListModel model{QStringList{"A", "B", "C", "D"}}; + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + QSignalSpy columnsMoveSpy(&proxy, &QAbstractItemModel::columnsMoved); + QVERIFY(columnsMoveSpy.isValid()); + QSignalSpy columnsAboutToBeMoveSpy(&proxy, &QAbstractItemModel::columnsAboutToBeMoved); + QVERIFY(columnsAboutToBeMoveSpy.isValid()); + QSignalSpy rowsMoveSpy(&model, &QAbstractItemModel::rowsMoved); + QVERIFY(rowsMoveSpy.isValid()); + QSignalSpy rowsAboutToBeMoveSpy(&model, &QAbstractItemModel::rowsAboutToBeMoved); + QVERIFY(rowsAboutToBeMoveSpy.isValid()); + proxy.setSourceModel(&model); + const QStringList expectedNewVal = {"B", "A", "C", "D"}; + QVERIFY(proxy.moveColumns(QModelIndex(), 0, 1, QModelIndex(), 2)); + for (int i = 0; i < expectedNewVal.size(); ++i) + QCOMPARE(proxy.index(0, i).data(), expectedNewVal.at(i)); + for (int i = 0; i < expectedNewVal.size(); ++i) + QCOMPARE(model.index(i, 0).data(), expectedNewVal.at(i)); + QCOMPARE(columnsMoveSpy.count(), 1); + QCOMPARE(columnsAboutToBeMoveSpy.count(), 1); + QCOMPARE(rowsMoveSpy.count(), 1); + QCOMPARE(rowsAboutToBeMoveSpy.count(), 1); + for (const auto &spyArgs : {columnsMoveSpy.takeFirst(), + columnsAboutToBeMoveSpy.takeFirst(), + rowsMoveSpy.takeFirst(),rowsAboutToBeMoveSpy.takeFirst()}) { + QVERIFY(!spyArgs.at(0).value<QModelIndex>().isValid()); + QCOMPARE(spyArgs.at(1).toInt(), 0); + QCOMPARE(spyArgs.at(2).toInt(), 0); + QVERIFY(!spyArgs.at(3).value<QModelIndex>().isValid()); + } +} + +void tst_QTransposeProxyModel::setData_data() +{ + QTest::addColumn<QAbstractItemModel *>("model"); + QTest::addColumn<bool>("rootItem"); + QTest::addColumn<bool>("viaProxy"); + QTest::newRow("List_via_Base") << createListModel(this) << true << false; + QTest::newRow("Table_via_Base") << createTableModel(this) << true << false; + QTest::newRow("Tree_via_Base_Root_Item") << createTreeModel(this) << true << false; + QTest::newRow("Tree_via_Base_Child_Item") << createTreeModel(this) << false << false; + QTest::newRow("List_via_Proxy") << createListModel(this) << true << true; + QTest::newRow("Table_via_Proxy") << createTableModel(this) << true << true; + QTest::newRow("Tree_via_Proxy_Root_Item") << createTreeModel(this) << true << true; + QTest::newRow("Tree_via_Proxy_Child_Item") << createTreeModel(this) << false << true; +} + +void tst_QTransposeProxyModel::setData() +{ + QFETCH(QAbstractItemModel *, model); + QFETCH(bool, rootItem); + QFETCH(bool, viaProxy); + QTransposeProxyModel proxy; + new QAbstractItemModelTester(&proxy, &proxy); + proxy.setSourceModel(model); + QSignalSpy sourceDataChangeSpy(model, &QAbstractItemModel::dataChanged); + QVERIFY(sourceDataChangeSpy.isValid()); + QSignalSpy proxyDataChangeSpy(&proxy, &QAbstractItemModel::dataChanged); + QVERIFY(proxyDataChangeSpy.isValid()); + const QString testData = QStringLiteral("TestingSetData"); + if (viaProxy) { + const QModelIndex parIdx = rootItem ? QModelIndex() : proxy.index(0, 1); + QVERIFY(proxy.setData(proxy.index(0, 1, parIdx), testData)); + QCOMPARE(model->index(1, 0, proxy.mapToSource(parIdx)).data().toString(), testData); + } else { + const QModelIndex parIdx = rootItem ? QModelIndex() : model->index(1, 0); + QVERIFY(model->setData(model->index(1, 0, parIdx), testData)); + QCOMPARE(proxy.index(0, 1, proxy.mapFromSource(parIdx)).data().toString(), testData); + } + QCOMPARE(sourceDataChangeSpy.size(), 1); + QCOMPARE(proxyDataChangeSpy.size(), 1); + delete model; +} + +QTEST_GUILESS_MAIN(tst_QTransposeProxyModel) + +#include "tst_qtransposeproxymodel.moc" |