Extending the auto test for QHeaderView
authorThorbjørn Lund Martsum <tmartsum@gmail.com>
Sun, 1 Jan 2012 10:24:04 +0000 (11:24 +0100)
committerQt by Nokia <qt-info@nokia.com>
Wed, 1 Feb 2012 15:26:11 +0000 (16:26 +0100)
This is a test that verifies correct behaviour of qheaderview.
It includes test of swapSections, moveSection, rowcount-change,
resizeSection, hideSection and position lookup.

Change-Id: Idc1fe8167f2d95dee5c5b1161cb633cbf9495d17
Reviewed-by: Stephen Kelly <stephen.kelly@kdab.com>
tests/auto/widgets/itemviews/qheaderview/tst_qheaderview.cpp

index 2a0a40f..75a72a7 100644 (file)
@@ -1,6 +1,7 @@
 /****************************************************************************
 **
 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Copyright (C) 2012 Thorbjørn Lund Martsum - tmartsum[at]gmail.com
 ** Contact: http://www.qt-project.org/
 **
 ** This file is part of the test suite of the Qt Toolkit.
@@ -74,6 +75,26 @@ public:
     friend class tst_QHeaderView;
 };
 
+class XResetModel : public QStandardItemModel
+{
+    virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex())
+    {
+        blockSignals(true);
+        bool r = QStandardItemModel::removeRows(row, count, parent);
+        blockSignals(false);
+        emit reset();
+        return r;
+    }
+    virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())
+    {
+        blockSignals(true);
+        bool r = QStandardItemModel::insertRows(row, count, parent);
+        blockSignals(false);
+        emit reset();
+        return r;
+    }
+};
+
 class tst_QHeaderView : public QObject
 {
     Q_OBJECT
@@ -186,10 +207,38 @@ private slots:
 
     void initialSortOrderRole();
 
+    void logicalIndexAtTest_data()   { setupTestData(); }
+    void visualIndexAtTest_data()    { setupTestData(); }
+    void hideShowTest_data()         { setupTestData(); }
+    void swapSectionsTest_data()     { setupTestData(); }
+    void moveSectionTest_data()      { setupTestData(); }
+    void defaultSizeTest_data()      { setupTestData(); }
+    void removeTest_data()           { setupTestData(true); }
+    void insertTest_data()           { setupTestData(true); }
+    void mixedTests_data()           { setupTestData(true); }
+    void resizeToContentTest_data()  { setupTestData(); }
+    void logicalIndexAtTest();
+    void visualIndexAtTest();
+    void hideShowTest();
+    void swapSectionsTest();
+    void moveSectionTest();
+    void defaultSizeTest();
+    void removeTest();
+    void insertTest();
+    void mixedTests();
+    void resizeToContentTest();
+
 protected:
+    void setupTestData(bool use_reset_model = false);
+    void additionalInit();
+    void calculateAndCheck(int cppline, const int precalced_comparedata[]);
+
     QWidget *topLevel;
     QHeaderView *view;
     QStandardItemModel *model;
+    QTableView *m_tableview;
+    bool m_using_reset_model;
+    QElapsedTimer timer;
 };
 
 class QtTestModel: public QAbstractTableModel
@@ -329,10 +378,12 @@ void tst_QHeaderView::initTestCase()
 #ifdef Q_OS_WINCE //disable magic for WindowsCE
     qApp->setAutoMaximizeThreshold(-1);
 #endif
+    m_tableview = new QTableView();
 }
 
 void tst_QHeaderView::cleanupTestCase()
 {
+    delete m_tableview;
 }
 
 void tst_QHeaderView::init()
@@ -372,7 +423,9 @@ void tst_QHeaderView::init()
 
 void tst_QHeaderView::cleanup()
 {
-    delete view;
+    m_tableview->setUpdatesEnabled(true);
+    if (view && view->parent() != m_tableview)
+        delete view;
     view = 0;
     delete model;
     model = 0;
@@ -2065,7 +2118,7 @@ void tst_QHeaderView::QTBUG8650_crashOnInsertSections()
 
 void tst_QHeaderView::QTBUG12268_hiddenMovedSectionSorting()
 {
-    QTableView view;
+    QTableView view; // ### this test fails on QTableView &view = *m_tableview; !? + shadowing view member
     QStandardItemModel *model = new QStandardItemModel(4,3, &view);
     for (int i = 0; i< model->rowCount(); ++i)
         for (int j = 0; j< model->columnCount(); ++j)
@@ -2105,7 +2158,7 @@ void tst_QHeaderView::QTBUG14242_hideSectionAutoSize()
 
 void tst_QHeaderView::initialSortOrderRole()
 {
-    QTableView view;
+    QTableView view; // ### Shadowing member view (of type QHeaderView)
     QStandardItemModel *model = new QStandardItemModel(4, 3, &view);
     for (int i = 0; i< model->rowCount(); ++i)
         for (int j = 0; j< model->columnCount(); ++j)
@@ -2138,5 +2191,450 @@ void tst_QHeaderView::initialSortOrderRole()
     QCOMPARE(view.horizontalHeader()->sortIndicatorOrder(), Qt::AscendingOrder);
 }
 
+const bool block_some_signals = true; // The test should also work with this set to false
+const int rowcount = 500;
+const int colcount = 10;
+
+QString istr(int n, bool comma = true)
+{
+    QString s;
+    s.setNum(n);
+    if (comma)
+        s += ", ";
+    return s;
+}
+
+void tst_QHeaderView::calculateAndCheck(int cppline, const int precalced_comparedata[])
+{
+    qint64 endtimer = timer.elapsed();
+    const bool silentmode = true;
+    if (!silentmode)
+        qDebug().nospace() << "(Time:" << endtimer << ")";
+
+    QString sline;
+    sline.setNum(cppline - 1);
+
+    const int p1 = 3133777;      // just a prime (maybe not that random ;) )
+    const int p2 = 135928393;    // just a random large prime - a bit less than signed 32-bit
+
+    int sum_visual = 0;
+    int sum_logical = 0;
+    int sum_size = 0;
+    int sum_hidden_size = 0;
+    int sum_lookup_visual = 0;
+    int sum_lookup_logical = 0;
+
+    int chk_visual = 1;
+    int chk_logical = 1;
+    int chk_sizes = 1;
+    int chk_hidden_size = 1;
+    int chk_lookup_visual = 1;
+    int chk_lookup_logical = 1;
+
+    int header_lenght = 0;
+    int lastindex = view->count() - 1;
+
+    // calculate information based on index
+    for (int i = 0; i <= lastindex; ++i) {
+        int visual = view->visualIndex(i);
+        int logical = view->logicalIndex(i);
+        int ssize = view->sectionSize(i);
+
+        sum_visual += visual;
+        sum_logical += logical;
+        sum_size += ssize;
+
+        if (visual >= 0) {
+            chk_visual %= p2;
+            chk_visual *= (visual + 1) * (i + 1) * p1;
+        }
+
+        if (logical >= 0) {
+            chk_logical %= p2;
+            chk_logical *= (logical + 1) * (i + 1 + (logical != i) ) * p1;
+        }
+
+        if (ssize >= 0) {
+            chk_sizes %= p2;
+            chk_sizes *= ( (ssize + 2) * (i + 1) * p1);
+        }
+
+        if (view->isSectionHidden(i)) {
+            view->showSection(i);
+            int hiddensize = view->sectionSize(i);
+            sum_hidden_size += hiddensize;
+            chk_hidden_size %= p2;
+            chk_hidden_size += ( (hiddensize + 1) * (i + 1) * p1);
+            // (hiddensize + 1) in the above to differ between hidden and size 0
+            // Though it can be changed (why isn't sections with size 0 hidden?)
+
+            view->hideSection(i);
+        }
+    }
+
+    // lookup indexes by pixel position
+    const int max_lookup_count = 500;
+    int lookup_to = view->height() + 1;
+    if (lookup_to > max_lookup_count)
+        lookup_to = max_lookup_count; // We limit this lookup - not to spend years when testing.
+                                      // Notice that lookupTest also has its own extra test
+    for (int u = 0; u < max_lookup_count; ++u) {
+        int visu = view->visualIndexAt(u);
+        int logi = view->logicalIndexAt(u);
+        sum_lookup_visual += logi;
+        sum_lookup_logical += visu;
+        chk_lookup_visual %= p2;
+        chk_lookup_visual *= ( (u + 1) * p1 * (visu + 2));
+        chk_lookup_logical %= p2;
+        chk_lookup_logical *=  ( (u + 1) * p1 * (logi + 2));
+    }
+    header_lenght = view->length();
+
+    // visual and logical indexes.
+    int sum_to_last_index = (lastindex * (lastindex + 1)) / 2; // == 0 + 1 + 2 + 3 + ... + lastindex
+
+    const bool write_calced_data = false;  // Do not write calculated output (unless the test fails)
+    if (write_calced_data) {
+        qDebug().nospace() << "(" << cppline - 1 << ")"  // << " const int precalced[] = "
+                           << " {" << chk_visual << ", " << chk_logical << ", " << chk_sizes << ", " << chk_hidden_size
+                           << ", " << chk_lookup_visual << ", " << chk_lookup_logical << ", " << header_lenght << "};";
+    }
+
+    const bool sanity_checks = true;
+    if (sanity_checks) {
+        QString msg = QString("sanity problem at ") + sline;
+        char *verifytext = QTest::toString(msg);
+
+        QVERIFY2(m_tableview->model()->rowCount() == view->count() , verifytext);
+        QVERIFY2(view->visualIndex(lastindex + 1) <= 0, verifytext);       // there is no such index in model
+        QVERIFY2(view->logicalIndex(lastindex + 1) <= 0, verifytext);      // there is no such index in model.
+        QVERIFY2(view->logicalIndex(lastindex + 1) <= 0, verifytext);      // there is no such index in model.
+        QVERIFY2(lastindex < 0 || view->visualIndex(0) >= 0, verifytext);   // no rows or legal index
+        QVERIFY2(lastindex < 0 || view->logicalIndex(0) >= 0, verifytext);  // no rows or legal index
+        QVERIFY2(lastindex < 0 || view->visualIndex(lastindex) >= 0, verifytext);  // no rows or legal index
+        QVERIFY2(lastindex < 0 || view->logicalIndex(lastindex) >= 0, verifytext); // no rows or legal index
+        QVERIFY2(view->visualIndexAt(-1) == -1, verifytext);
+        QVERIFY2(view->logicalIndexAt(-1) == -1, verifytext);
+        QVERIFY2(view->visualIndexAt(view->length()) == -1, verifytext);
+        QVERIFY2(view->logicalIndexAt(view->length()) == -1, verifytext);
+        QVERIFY2(sum_visual == sum_logical, verifytext);
+        QVERIFY2(sum_to_last_index == sum_logical, verifytext);
+    }
+
+    // Semantic test
+    const bool check_semantics = true; // Otherwise there is no 'real' test
+    if (!check_semantics)
+        return;
+
+    const int *x = precalced_comparedata;
+
+    QString msg = "semantic problem at " + QString(__FILE__) + " (" + sline + ")";
+    msg += "\nThe *expected* result was : {" + istr(x[0]) + istr(x[1]) + istr(x[2]) + istr(x[3])
+        + istr(x[4]) + istr(x[5]) + istr(x[6], false) + "}";
+    msg += "\nThe calculated result was : {";
+    msg += istr(chk_visual) + istr(chk_logical) + istr(chk_sizes) + istr(chk_hidden_size)
+        + istr(chk_lookup_visual) + istr(chk_lookup_logical) + istr(header_lenght, false) + "};";
+
+    char *verifytext = QTest::toString(msg);
+
+    QVERIFY2(chk_visual            == x[0], verifytext);
+    QVERIFY2(chk_logical           == x[1], verifytext);
+    QVERIFY2(chk_sizes             == x[2], verifytext);
+    QVERIFY2(chk_hidden_size       == x[3], verifytext);
+    QVERIFY2(chk_lookup_visual     == x[4], verifytext);  // Here the semantic can change. See final note (*).
+    QVERIFY2(chk_lookup_logical    == x[5], verifytext);  // Here the semantic can change. See final note (*).
+    QVERIFY2(header_lenght         == x[6], verifytext);
+}
+
+void tst_QHeaderView::setupTestData(bool also_use_reset_model)
+{
+    QTest::addColumn<bool>("updates_enabled");
+    QTest::addColumn<bool>("special_prepare");
+    QTest::addColumn<bool>("reset_model");
+
+    if (also_use_reset_model) {
+        QTest::newRow("no_updates+normal")  << false << false << true;
+        QTest::newRow("hasupdates+normal")  << true << false << true;
+        QTest::newRow("no_updates+special") << false << true << true;
+        QTest::newRow("no_updates+special") << true << true << true;
+    }
+
+    QTest::newRow("no_updates+normal")  << false << false << false;
+    QTest::newRow("hasupdates+normal")  << true << false << false;
+    QTest::newRow("no_updates+special") << false << true << false;
+    QTest::newRow("no_updates+special") << true << true << false;
+}
+
+void tst_QHeaderView::additionalInit()
+{
+    m_tableview->setVerticalHeader(view);
+
+    QFETCH(bool, updates_enabled);
+    QFETCH(bool, special_prepare);
+    QFETCH(bool, reset_model);
+
+    m_using_reset_model = reset_model;
+
+    if (m_using_reset_model) {
+        XResetModel *m = new XResetModel();
+        m_tableview->setModel(m);
+        delete model;
+        model = m;
+    } else {
+        m_tableview->setModel(model);
+    }
+
+    const int default_section_size = 25;
+    view->setDefaultSectionSize(default_section_size); // Important - otherwise there will be semantic changes
+
+    model->clear();
+
+    if (special_prepare) {
+
+        for (int u = 0; u <= rowcount; ++u) // ensures fragmented spans in e.g Qt 4.7
+            model->setRowCount(u);
+
+        model->setColumnCount(colcount);
+        view->swapSections(0, rowcount - 1);
+        view->swapSections(0, rowcount - 1); // No real change - setup visual and log-indexes
+        view->hideSection(rowcount - 1);
+        view->showSection(rowcount - 1);  // No real change - (maybe init hidden vector)
+    } else {
+        model->setColumnCount(colcount);
+        model->setRowCount(rowcount);
+    }
+
+    QString s;
+    for (int i = 0; i < model->rowCount(); ++i) {
+        model->setData(model->index(i, 0), QVariant(i));
+        s.setNum(i);
+        s += ".";
+        s += 'a' + (i % 25);
+        model->setData(model->index(i, 1), QVariant(s));
+    }
+    m_tableview->setUpdatesEnabled(updates_enabled);
+    view->blockSignals(block_some_signals);
+    timer.start();
+}
+
+void tst_QHeaderView::logicalIndexAtTest()
+{
+    additionalInit();
+
+    view->swapSections(4, 9); // Make sure that visual and logical Indexes are not just the same.
+
+    int check1 = 0;
+    int check2 = 0;
+    for (int u = 0; u < model->rowCount(); ++u) {
+        view->resizeSection(u, 10 + u % 30);
+        int v = view->visualIndexAt(u * 29);
+        view->visualIndexAt(u * 29);
+        check1 += v;
+        check2 += u * v;
+    }
+    view->resizeSection(0, 0); // Make sure that we have a 0 size section - before the result set
+    view->setSectionHidden(6, true); // Make sure we have a real hidden section before result set
+
+    //qDebug() << "logicalIndexAtTest" << check1 << check2;
+    const int precalced_check1 = 106327;
+    const int precalced_check2 = 29856418;
+    QVERIFY(precalced_check1 == check1); // Here the semantic can change. See final note (*)
+    QVERIFY(precalced_check2 == check2); // Here the semantic can change. See final note (*)
+
+    const int precalced_results[] = { 1145298384, -1710423344, -650981936, 372919464, 2139446944, 854793816, 12124 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+void tst_QHeaderView::visualIndexAtTest()
+{
+    additionalInit();
+
+    view->swapSections(4, 9); // Make sure that visual and logical Indexes are not just the same.
+    int check1 = 0;
+    int check2 = 0;
+
+    for (int u = 0; u < model->rowCount(); ++u) {
+        view->resizeSection(u, 3 + u % 17);
+        int v = view->visualIndexAt(u * 29);
+        check1 += v;
+        check2 += u * v;
+    }
+
+    view->resizeSection(1, 0); // Make sure that we have a 0 size section - before the result set
+    view->setSectionHidden(5, true); // Make sure we have a real hidden section before result set
+
+    //qDebug() << "visualIndexAtTest" << check1 << check2;
+    const int precalced_check1 = 72665;
+    const int precalced_check2 = 14015890;
+    QVERIFY(precalced_check1 == check1); // Here the semantic can change. See final note (*)
+    QVERIFY(precalced_check2 == check2); // Here the semantic can change. See final note (*)
+
+    const int precalced_results[] = { 1145298384, -1710423344, -1457520212, 169223959, 726651072, -2105938696, 5453 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+void tst_QHeaderView::hideShowTest()
+{
+    additionalInit();
+
+    for (int u = model->rowCount(); u >= 0; --u)
+        if (u % 8 != 0)
+            view->hideSection(u);
+
+    for (int u = model->rowCount(); u >= 0; --u)
+        if (u % 3 == 0)
+            view->showSection(u);
+
+    view->setSectionHidden(model->rowCount(), true); // invalid hide (should be ignored)
+    view->setSectionHidden(-1, true); // invalid hide (should be ignored)
+
+    const int precalced_results[] = { -1523279360, -1523279360, -1321506816, 2105322423, 1879611280, 1879611280, 5225 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+void tst_QHeaderView::swapSectionsTest()
+{
+    additionalInit();
+
+    for (int u = 0; u < rowcount / 2; ++u)
+        view->swapSections(u, rowcount - u - 1);
+
+    for (int u = 0; u < rowcount; u += 2)
+        view->swapSections(u, u + 1);
+
+    view->swapSections(0, model->rowCount()); // invalid swapsection (should be ignored)
+
+    const int precalced_results[] = { -1536450048, -1774641430, -1347156568, 1, 1719705216, -240077576, 12500 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+void tst_QHeaderView::moveSectionTest()
+{
+    additionalInit();
+
+    for (int u = 1; u < 5; ++u)
+        view->moveSection(u, model->rowCount() - u);
+
+    view->moveSection(2, model->rowCount() / 2);
+    view->moveSection(0, 10);
+    view->moveSection(0, model->rowCount() - 10);
+
+    view->moveSection(0, model->rowCount()); // invalid move (should be ignored)
+
+    const int precalced_results[] = { 645125952, 577086896, -1347156568, 1, 1719705216, 709383416, 12500 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+void tst_QHeaderView::defaultSizeTest()
+{
+    additionalInit();
+
+    view->hideSection(rowcount / 2);
+    int restore_to = view->defaultSectionSize();
+    view->setDefaultSectionSize(restore_to + 5);
+
+    const int precalced_results[] = { -1523279360, -1523279360, -1739688320, -1023807777, 997629696, 997629696, 14970 };
+    calculateAndCheck(__LINE__, precalced_results);
+
+    view->setDefaultSectionSize(restore_to);
+}
+
+void tst_QHeaderView::removeTest()
+{
+    additionalInit();
+
+    view->swapSections(0, 5);
+    model->removeRows(0, 1);   // remove one row
+    model->removeRows(4, 10);
+    model->setRowCount(model->rowCount() / 2 - 1);
+
+    if (m_using_reset_model) {
+        const int precalced_results[] = { 1741224292, -135269187, -569519837, 1, 1719705216, -1184395000, 6075 };
+        calculateAndCheck(__LINE__, precalced_results);
+    } else {
+        const int precalced_results[] = { 289162397, 289162397, -569519837, 1, 1719705216, 1719705216, 6075 };
+        calculateAndCheck(__LINE__, precalced_results);
+    }
+}
+
+void tst_QHeaderView::insertTest()
+{
+    additionalInit();
+
+    view->swapSections(0, model->rowCount() - 1);
+    model->insertRows(0, 1);   // insert one row
+    model->insertRows(4, 10);
+    model->setRowCount(model->rowCount() * 2 - 1);
+
+    if (m_using_reset_model) {
+        const int precalced_results[] = { 2040508069, -1280266538, -150350734, 1, 1719705216, 1331312784, 25525 };
+        calculateAndCheck(__LINE__, precalced_results);
+    } else {
+        const int precalced_results[] = { -1909447021, 339092083, -150350734, 1, 1719705216, -969712728, 25525 };
+        calculateAndCheck(__LINE__, precalced_results);
+    }
+}
+
+void tst_QHeaderView::mixedTests()
+{
+    additionalInit();
+
+    model->setRowCount(model->rowCount() + 10);
+
+    for (int u = 0; u < model->rowCount(); u += 2)
+        view->swapSections(u, u + 1);
+
+    view->moveSection(0, 5);
+
+    for (int u = model->rowCount(); u >= 0; --u) {
+        if (u % 5 != 0)
+            view->hideSection(u);
+        if (u % 3 != 0)
+            view->showSection(u);
+    }
+
+    model->insertRows(3, 7);
+    model->removeRows(8, 3);
+    model->setRowCount(model->rowCount() - 10);
+
+    if (m_using_reset_model) {
+        const int precalced_results[] = { 898296472, 337096378, -543340640, 1, 703951756, 250831536, 9250 };
+        calculateAndCheck(__LINE__, precalced_results);
+    } else {
+        const int precalced_results[] = { 1911338224, 1693514365, -613398968, -1912534953, 1582159424, -1851079000, 9300 };
+        calculateAndCheck(__LINE__, precalced_results);
+    }
+}
+
+void tst_QHeaderView::resizeToContentTest()
+{
+    additionalInit();
+
+    QModelIndex idx = model->index(2, 2);
+    model->setData(idx, QVariant("A normal string"));
+
+    idx = model->index(1, 3);
+    model->setData(idx, QVariant("A normal longer string to test resize"));
+
+    QHeaderView *hh = m_tableview->horizontalHeader();
+    hh->resizeSections(QHeaderView::ResizeToContents);
+    QVERIFY(hh->sectionSize(3) > hh->sectionSize(2));
+
+    for (int u = 0; u < 10; ++u)
+        view->resizeSection(u, 1);
+
+    view->resizeSections(QHeaderView::ResizeToContents);
+    QVERIFY(view->sectionSize(1) > 1);
+    QVERIFY(view->sectionSize(2) > 1);
+
+    view->setDefaultSectionSize(25); // To make sure our precalced data are correct. We do not know font height etc.
+
+    const int precalced_results[] =  { -1523279360, -1523279360, -1347156568, 1, 1719705216, 1719705216, 12500 };
+    calculateAndCheck(__LINE__, precalced_results);
+}
+
+// (*) Currently qheaderview position lookup acts a bit strange. It can return sections with size 0.
+// This could be changed in the future.
+
 QTEST_MAIN(tst_QHeaderView)
 #include "tst_qheaderview.moc"