/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** 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-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "qmodellistener.h" #include #include void QModelListener::rowsAboutToBeRemovedOrInserted(const QModelIndex & parent, int start, int end ) { for (int i = 0; start + i <= end; i++) { QModelIndex mIndex = m_pModel->index(start + i, 0, parent); QVariant var = m_pModel->data(mIndex, Qt::DisplayRole); QString str = var.toString(); QCOMPARE(str, m_pAboutToStringlist->at(i)); } } void QModelListener::rowsRemovedOrInserted(const QModelIndex & parent, int , int) { // Can the rows that *are* removed be iterated now ? // What about rowsAboutToBeInserted - what will the indices be? // will insertRow() overwrite existing, or insert (and conseq. grow the model?) // What will the item then contain? empty data? // RemoveColumn. Does that also fire the rowsRemoved-family signals? for (int i = 0; i < m_pExpectedStringlist->size(); i++) { QModelIndex mIndex = m_pModel->index(i, 0, parent); QVariant var = m_pModel->data(mIndex, Qt::DisplayRole); QString str = var.toString(); QCOMPARE(str, m_pExpectedStringlist->at(i)); } } class tst_QStringListModel : public QObject { Q_OBJECT private slots: void rowsAboutToBeRemoved_rowsRemoved(); void rowsAboutToBeRemoved_rowsRemoved_data(); void rowsAboutToBeInserted_rowsInserted(); void rowsAboutToBeInserted_rowsInserted_data(); void setData_emits_both_roles_data(); void setData_emits_both_roles(); void setData_emits_on_change_only(); void supportedDragDropActions(); void moveRows_data(); void moveRows(); void moveRowsInvalid_data(); void moveRowsInvalid(); void itemData(); void setItemData(); }; void tst_QStringListModel::moveRowsInvalid_data() { QTest::addColumn("baseModel"); QTest::addColumn("startParent"); QTest::addColumn("startRow"); QTest::addColumn("count"); QTest::addColumn("destinationParent"); QTest::addColumn("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("startRow"); QTest::addColumn("count"); QTest::addColumn("destination"); QTest::addColumn("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 &signalArgs : {rowMovedSpy.first(), rowAboutMovedSpy.first()}){ QVERIFY(!signalArgs.at(0).value().isValid()); QCOMPARE(signalArgs.at(1).toInt(), startRow); QCOMPARE(signalArgs.at(2).toInt(), startRow + count - 1); QVERIFY(!signalArgs.at(3).value().isValid()); QCOMPARE(signalArgs.at(4).toInt(), destination); } } void tst_QStringListModel::rowsAboutToBeRemoved_rowsRemoved_data() { QTest::addColumn("input"); QTest::addColumn("row"); QTest::addColumn("count"); QTest::addColumn("aboutto"); QTest::addColumn("res"); QStringList strings0; strings0 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto0; aboutto0 << "Two" << "Three"; QStringList res0; res0 << "One" << "Four" << "Five"; QTest::newRow( "data0" ) << strings0 << 1 << 2 << aboutto0 << res0; QStringList strings1; strings1 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto1; aboutto1 << "One" << "Two"; QStringList res1; res1 << "Three" << "Four" << "Five"; QTest::newRow( "data1" ) << strings1 << 0 << 2 << aboutto1 << res1; QStringList strings2; strings2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto2; aboutto2 << "Four" << "Five"; QStringList res2; res2 << "One" << "Two" << "Three"; QTest::newRow( "data2" ) << strings2 << 3 << 2 << aboutto2 << res2; QStringList strings3; strings3 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto3; aboutto3 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList res3; QTest::newRow( "data3" ) << strings3 << 0 << 5 << aboutto3 << res3; /* * Keep this, template to add more data QStringList strings2; strings2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto2; aboutto2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList res2; res2 << "One" << "Two" << "Three" << "Four" << "Five"; QTest::newRow( "data2" ) << strings2 << 0 << 5 << aboutto2 << res2; */ } void tst_QStringListModel::rowsAboutToBeRemoved_rowsRemoved() { QFETCH(QStringList, input); QFETCH(int, row); QFETCH(int, count); QFETCH(QStringList, aboutto); QFETCH(QStringList, res); QStringListModel model(input); QModelListener listener(&aboutto, &res, &model); connect(&model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), &listener, SLOT(rowsAboutToBeRemovedOrInserted(QModelIndex,int,int))); connect(&model, SIGNAL(rowsRemoved(QModelIndex,int,int)), &listener, SLOT(rowsRemovedOrInserted(QModelIndex,int,int))); model.removeRows(row, count); // At this point, control goes to our connected slots inn this order: // 1. rowsAboutToBeRemovedOrInserted // 2. rowsRemovedOrInserted // Control returns here } void tst_QStringListModel::rowsAboutToBeInserted_rowsInserted_data() { QTest::addColumn("input"); QTest::addColumn("row"); QTest::addColumn("count"); QTest::addColumn("aboutto"); QTest::addColumn("res"); QStringList strings0; strings0 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto0; aboutto0 << "Two" << "Three"; QStringList res0; res0 << "One" << "" << "" << "Two" << "Three" << "Four" << "Five"; QTest::newRow( "data0" ) << strings0 << 1 << 2 << aboutto0 << res0; QStringList strings1; strings1 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto1; aboutto1 << "One" << "Two"; QStringList res1; res1 << "" << "" << "One" << "Two" << "Three" << "Four" << "Five"; QTest::newRow( "data1" ) << strings1 << 0 << 2 << aboutto1 << res1; QStringList strings2; strings2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto2; aboutto2 << "Four" << "Five"; QStringList res2; res2 << "One" << "Two" << "Three" << "" << "" << "Four" << "Five"; QTest::newRow( "data2" ) << strings2 << 3 << 2 << aboutto2 << res2; QStringList strings3; strings3 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto3; aboutto3 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList res3; res3 << "" << "" << "" << "" << "" << "One" << "Two" << "Three" << "Four" << "Five"; QTest::newRow( "data3" ) << strings3 << 0 << 5 << aboutto3 << res3; /* * Keep this, template to add more data QStringList strings2; strings2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList aboutto2; aboutto2 << "One" << "Two" << "Three" << "Four" << "Five"; QStringList res2; res2 << "One" << "Two" << "Three" << "Four" << "Five"; QTest::newRow( "data2" ) << strings2 << 0 << 5 << aboutto2 << res2; */ } void tst_QStringListModel::rowsAboutToBeInserted_rowsInserted() { QFETCH(QStringList, input); QFETCH(int, row); QFETCH(int, count); QFETCH(QStringList, aboutto); QFETCH(QStringList, res); QStringListModel model(input); QModelListener listener(&aboutto, &res, &model); connect(&model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), &listener, SLOT(rowsAboutToBeRemovedOrInserted(QModelIndex,int,int))); connect(&model, SIGNAL(rowsInserted(QModelIndex,int,int)), &listener, SLOT(rowsRemovedOrInserted(QModelIndex,int,int))); model.insertRows(row, count); // At this point, control goes to our connected slots inn this order: // 1. rowsAboutToBeRemovedOrInserted // 2. rowsRemovedOrInserted // Control returns here } void tst_QStringListModel::setData_emits_both_roles_data() { QTest::addColumn("row"); QTest::addColumn("data"); QTest::addColumn("role"); #define ROW(row, string, role) \ QTest::newRow(#row " -> " string) << row << QString(string) << int(Qt::role) ROW(0, "1", EditRole); ROW(1, "2", DisplayRole); #undef ROW } template C sorted(C c) { std::sort(c.begin(), c.end()); return std::move(c); } void tst_QStringListModel::setData_emits_both_roles() { QFETCH(int, row); QFETCH(QString, data); QFETCH(int, role); QStringListModel model(QStringList() << "one" << "two"); QVector expected; expected.reserve(2); expected.append(Qt::DisplayRole); expected.append(Qt::EditRole); QSignalSpy spy(&model, &QAbstractItemModel::dataChanged); QVERIFY(spy.isValid()); model.setData(model.index(row, 0), data, role); QCOMPARE(spy.size(), 1); QCOMPARE(sorted(spy.at(0).at(2).value >()), expected); } void tst_QStringListModel::itemData() { QStringListModel testModel{ QStringList { QStringLiteral("One"), QStringLiteral("Two"), QStringLiteral("Three"), QStringLiteral("Four"), QStringLiteral("Five") }}; QMap 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 changeRoles{Qt::DisplayRole, Qt::EditRole}; const QString changedString("Changed"); QMap newItemData{std::make_pair(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(), changeIndex); QCOMPARE(dataChangedArguments.at(1).value(), changeIndex); QCOMPARE(dataChangedArguments.at(2).value >(), 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(), changeIndex); QCOMPARE(dataChangedArguments.at(1).value(), changeIndex); QCOMPARE(dataChangedArguments.at(2).value >(), changeRoles); } void tst_QStringListModel::setData_emits_on_change_only() { QStringListModel model(QStringList{QStringLiteral("one"), QStringLiteral("two")}); QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged); QVERIFY(dataChangedSpy.isValid()); const QModelIndex modelIdx = model.index(0, 0); const QString newStringData = QStringLiteral("test"); QVERIFY(model.setData(modelIdx, newStringData)); QCOMPARE(dataChangedSpy.count(), 1); const QList spyList = dataChangedSpy.takeFirst(); QCOMPARE(spyList.at(0).value(), modelIdx); QCOMPARE(spyList.at(1).value(), modelIdx); const QVector expectedRoles{Qt::DisplayRole, Qt::EditRole}; QCOMPARE(spyList.at(2).value >(), expectedRoles); QVERIFY(model.setData(modelIdx, newStringData)); QVERIFY(dataChangedSpy.isEmpty()); } void tst_QStringListModel::supportedDragDropActions() { QStringListModel model; QCOMPARE(model.supportedDragActions(), Qt::CopyAction | Qt::MoveAction); QCOMPARE(model.supportedDropActions(), Qt::CopyAction | Qt::MoveAction); } QTEST_MAIN(tst_QStringListModel) #include "tst_qstringlistmodel.moc"