You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

363 lines
15 KiB

/*
Copyright (c) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
Authors: David Faure <david.faure@kdab.com>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include <QSignalSpy>
#include <QSortFilterProxyModel>
#include <QTest>
#include <QDebug>
#include <QStandardItemModel>
#include <QTreeView>
#include <kextracolumnsproxymodel.h>
#include "test_model_helpers.h"
using namespace TestModelHelpers;
Q_DECLARE_METATYPE(QModelIndex)
class tst_KExtraColumnsProxyModel : public QObject
{
Q_OBJECT
private:
class NoExtraColumns : public KExtraColumnsProxyModel
{
QVariant extraColumnData(const QModelIndex &, int, int, int) const Q_DECL_OVERRIDE
{
Q_ASSERT(0);
return QVariant();
}
};
class TwoExtraColumnsProxyModel : public KExtraColumnsProxyModel
{
public:
TwoExtraColumnsProxyModel() : KExtraColumnsProxyModel(), m_extraColumnData('Z')
{
appendColumn(QStringLiteral("H5"));
appendColumn(QStringLiteral("WRONG")); // removed two lines below, just to test removeColumn
appendColumn(QStringLiteral("H6"));
removeColumn(1);
}
QVariant extraColumnData(const QModelIndex &, int row, int extraColumn, int role) const Q_DECL_OVERRIDE
{
Q_ASSERT(role == Qt::DisplayRole);
switch (extraColumn) {
case 0:
return QString(m_extraColumnData);
case 1:
return QString::number(row);
default:
Q_ASSERT(0);
return QVariant();
}
}
bool setExtraColumnData(const QModelIndex &parent, int row, int extraColumn, const QVariant &data, int role) Q_DECL_OVERRIDE {
if (extraColumn == 0 && role == Qt::EditRole)
{
m_extraColumnData = data.toString().at(0);
extraColumnDataChanged(QModelIndex(), 0, extraColumn, QVector<int>() << Qt::EditRole);
return true;
}
return KExtraColumnsProxyModel::setExtraColumnData(parent, row, extraColumn, data, role);
}
void changeExtraColumnData()
{
m_extraColumnData = '<';
extraColumnDataChanged(QModelIndex(), 0, 0, QVector<int>() << Qt::EditRole);
}
private:
QChar m_extraColumnData;
};
private Q_SLOTS:
void initTestCase()
{
qRegisterMetaType<QModelIndex>();
}
void init()
{
// Prepare the source model to use later on
mod.clear();
mod.appendRow(makeStandardItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("D")));
mod.item(0, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("m") << QStringLiteral("n") << QStringLiteral("o") << QStringLiteral("p")));
mod.item(0, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("q") << QStringLiteral("r") << QStringLiteral("s") << QStringLiteral("t")));
mod.appendRow(makeStandardItems(QStringList() << QStringLiteral("E") << QStringLiteral("F") << QStringLiteral("G") << QStringLiteral("H")));
mod.item(1, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("x") << QStringLiteral("y") << QStringLiteral("z") << QStringLiteral(".")));
mod.setHorizontalHeaderLabels(QStringList() << QStringLiteral("H1") << QStringLiteral("H2") << QStringLiteral("H3") << QStringLiteral("H4"));
QCOMPARE(extractRowTexts(&mod, 0), QStringLiteral("ABCD"));
QCOMPARE(extractRowTexts(&mod, 0, mod.index(0, 0)), QStringLiteral("mnop"));
QCOMPARE(extractRowTexts(&mod, 1, mod.index(0, 0)), QStringLiteral("qrst"));
QCOMPARE(extractRowTexts(&mod, 1), QStringLiteral("EFGH"));
QCOMPARE(extractRowTexts(&mod, 0, mod.index(1, 0)), QStringLiteral("xyz."));
QCOMPARE(extractHorizontalHeaderTexts(&mod), QStringLiteral("H1H2H3H4"));
// test code to see the model
// showModel(&mod);
}
void shouldDoNothingIfNoExtraColumns()
{
// Given a extra-columns proxy
NoExtraColumns pm;
// When setting it to a source model
pm.setSourceModel(&mod);
// Then the proxy should show the same as the model
QCOMPARE(pm.rowCount(), mod.rowCount());
QCOMPARE(pm.columnCount(), mod.columnCount());
QCOMPARE(pm.rowCount(pm.index(0, 0)), 2);
QCOMPARE(pm.index(0, 0).parent(), QModelIndex());
// (verify that the mapFromSource(mapToSource(x)) == x roundtrip works)
for (int row = 0; row < pm.rowCount(); ++row) {
for (int col = 0; col < pm.columnCount(); ++col) {
QCOMPARE(pm.mapFromSource(pm.mapToSource(pm.index(row, col))), pm.index(row, col));
}
}
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnop"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrst"));
QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGH"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz."));
QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H4"));
}
void shouldShowExtraColumns()
{
// Given a extra-columns proxy with two extra columns
TwoExtraColumnsProxyModel pm;
// When setting it to a source model
pm.setSourceModel(&mod);
// Then the proxy should show the extra column
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCDZ0"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnopZ0"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrstZ1"));
QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGHZ1"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz.Z0"));
QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H4H5H6"));
// Verify tree structure of proxy
const QModelIndex secondParent = pm.index(1, 0);
QVERIFY(!secondParent.parent().isValid());
QCOMPARE(indexToText(pm.index(0, 0, secondParent).parent()), indexToText(secondParent));
QCOMPARE(indexToText(pm.index(0, 3, secondParent).parent()), indexToText(secondParent));
QVERIFY(indexToText(pm.index(0, 4)).startsWith(QStringLiteral("0,4,")));
QCOMPARE(indexToText(pm.index(0, 4, secondParent).parent()), indexToText(secondParent));
QVERIFY(indexToText(pm.index(0, 5)).startsWith(QStringLiteral("0,5,")));
QCOMPARE(indexToText(pm.index(0, 5, secondParent).parent()), indexToText(secondParent));
QCOMPARE(pm.index(0, 0).sibling(0, 4).column(), 4);
QCOMPARE(pm.index(0, 4).sibling(0, 1).column(), 1);
QVERIFY(!pm.canFetchMore(QModelIndex()));
}
void shouldHandleDataChanged()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When a cell in a source model changes
mod.item(0, 2)->setData("c", 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, 2));
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABcDZ0"));
}
void shouldHandleDataChangedInExtraColumn()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When the proxy wants to signal a change in an extra column
pm.changeExtraColumnData();
// Then the change should be available and notified
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD<0"));
QCOMPARE(dataChangedSpy.count(), 1);
QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 4));
}
void shouldHandleSetDataInNormalColumn()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When editing a cell in the proxy
QVERIFY(pm.setData(pm.index(0, 2), "c", Qt::EditRole));
// Then the change should be available and notified
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABcDZ0"));
QCOMPARE(dataChangedSpy.count(), 1);
QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 2));
}
void shouldHandleSetDataInExtraColumn()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QSignalSpy dataChangedSpy(&pm, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
// When editing a cell in the proxy
QVERIFY(pm.setData(pm.index(0, 4), "-", Qt::EditRole));
// Then the change should be available and notified
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD-0"));
QCOMPARE(dataChangedSpy.count(), 1);
QCOMPARE(dataChangedSpy.at(0).at(0).toModelIndex(), pm.index(0, 4));
}
void shouldHandleRowInsertion()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QSignalSpy rowATBISpy(&pm, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
QSignalSpy rowInsertedSpy(&pm, SIGNAL(rowsInserted(QModelIndex,int,int)));
// When a source model inserts a new (child) row
mod.item(1, 0)->appendRow(makeStandardItems(QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3") << QStringLiteral("4")));
// 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(), 2);
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCDZ0"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnopZ0"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrstZ1"));
QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGHZ1"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz.Z0"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(1, 0)), QStringLiteral("1234Z1"));
QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H4H5H6"));
}
void shouldHandleColumnInsertion()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
setup(pm);
QCOMPARE(pm.columnCount(), 6);
QCOMPARE(mod.columnCount(), 4);
QSignalSpy colATBISpy(&pm, SIGNAL(columnsAboutToBeInserted(QModelIndex,int,int)));
QSignalSpy colInsertedSpy(&pm, SIGNAL(columnsInserted(QModelIndex,int,int)));
// When a source model inserts a new column
mod.setColumnCount(5); // like QStandardItem::setChild does
QCOMPARE(mod.columnCount(), 5);
// QStandardItemModel is quite dumb, it records the number of columns in each item
for (int row = 0; row < mod.rowCount(); ++row) {
mod.item(row, 0)->setColumnCount(5);
}
// Then the proxy should notify its users and show changes
QCOMPARE(rowSpyToText(colATBISpy), QStringLiteral("4,4;4,4;4,4")); // QStandardItemModel emits it for each parent
QCOMPARE(rowSpyToText(colInsertedSpy), QStringLiteral("4,4;4,4;4,4"));
QCOMPARE(pm.columnCount(), 7);
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCD Z0"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(0, 0)), QStringLiteral("mnop Z0"));
QCOMPARE(extractRowTexts(&pm, 1, pm.index(0, 0)), QStringLiteral("qrst Z1"));
QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("EFGH Z1"));
QCOMPARE(extractRowTexts(&pm, 0, pm.index(1, 0)), QStringLiteral("xyz. Z0"));
QCOMPARE(extractHorizontalHeaderTexts(&pm), QStringLiteral("H1H2H3H45H5H6")); // '5' was inserted in there
}
// row removal, layoutChanged, modelReset -> same thing, works via QIdentityProxyModel
// missing: test for mapSelectionToSource
void shouldHandleLayoutChanged()
{
// Given a extra-columns proxy, with two extra columns
TwoExtraColumnsProxyModel pm;
// And a QSFPM underneath
QSortFilterProxyModel proxy;
proxy.setSourceModel(&mod);
QCOMPARE(proxy.columnCount(), 4);
pm.setSourceModel(&proxy);
QCOMPARE(pm.columnCount(), 6);
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("ABCDZ0"));
// And a selection
QItemSelectionModel selection(&pm);
selection.select(pm.index(0, 0), QItemSelectionModel::Select | QItemSelectionModel::Rows);
const QModelIndexList lst = selection.selectedIndexes();
QCOMPARE(lst.count(), 6);
for (int col = 0; col < lst.count(); ++col) {
QCOMPARE(lst.at(col).row(), 0);
QCOMPARE(lst.at(col).column(), col);
}
// When sorting
pm.sort(0, Qt::DescendingOrder);
// Then the proxy should be sorted
QCOMPARE(extractRowTexts(&pm, 0), QStringLiteral("EFGHZ0"));
QCOMPARE(extractRowTexts(&pm, 1), QStringLiteral("ABCDZ1"));
// And the selection should be updated accordingly
const QModelIndexList lstAfter = selection.selectedIndexes();
QCOMPARE(lstAfter.count(), 6);
for (int col = 0; col < lstAfter.count(); ++col) {
QCOMPARE(lstAfter.at(col).row(), 1);
QCOMPARE(lstAfter.at(col).column(), col);
}
}
private:
void setup(KExtraColumnsProxyModel &pm)
{
pm.setSourceModel(&mod);
}
static QString indexToText(const QModelIndex &index)
{
if (!index.isValid()) {
return QStringLiteral("invalid");
}
return QString::number(index.row()) + "," + QString::number(index.column()) + ","
+ QString::number(reinterpret_cast<qulonglong>(index.internalPointer()), 16)
+ " in " + QString::number(reinterpret_cast<qulonglong>(index.model()), 16);
}
QStandardItemModel mod;
};
QTEST_MAIN(tst_KExtraColumnsProxyModel)
#include "kextracolumnsproxymodeltest.moc"