Data model for settings/options updates
[qtrapids] / src / utest / options / modeltest.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2007 Trolltech ASA. All rights reserved.
4 **
5 ** This file is part of the Qt Concurrent project on Trolltech Labs.
6 **
7 ** This file may be used under the terms of the GNU General Public
8 ** License version 2.0 as published by the Free Software Foundation
9 ** and appearing in the file LICENSE.GPL included in the packaging of
10 ** this file.  Please review the following information to ensure GNU
11 ** General Public Licensing requirements will be met:
12 ** http://www.trolltech.com/products/qt/opensource.html
13 **
14 ** If you are unsure which license is appropriate for your use, please
15 ** review the following information:
16 ** http://www.trolltech.com/products/qt/licensing.html or contact the
17 ** sales department at sales@trolltech.com.
18 **
19 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
20 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 **
22 ****************************************************************************/
23
24 #include <QtGui/QtGui>
25
26 #include "modeltest.h"
27
28 Q_DECLARE_METATYPE(QModelIndex)
29
30 /*!
31     Connect to all of the models signals.  Whenever anything happens recheck everything.
32 */
33 ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false)
34 {
35         Q_ASSERT(model);
36
37         connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
38                 this, SLOT(runAllTests()));
39         connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
40                 this, SLOT(runAllTests()));
41         connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
42                 this, SLOT(runAllTests()));
43         connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
44                 this, SLOT(runAllTests()));
45         connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
46                 this, SLOT(runAllTests()));
47         connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)),
48                 this, SLOT(runAllTests()));
49         connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests()));
50         connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests()));
51         connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests()));
52         connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
53                 this, SLOT(runAllTests()));
54         connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
55                 this, SLOT(runAllTests()));
56         connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
57                 this, SLOT(runAllTests()));
58         connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
59                 this, SLOT(runAllTests()));
60
61         // Special checks for inserting/removing
62         connect(model, SIGNAL(layoutAboutToBeChanged()),
63                 this, SLOT(layoutAboutToBeChanged()));
64         connect(model, SIGNAL(layoutChanged()),
65                 this, SLOT(layoutChanged()));
66
67         connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
68                 this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int)));
69         connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
70                 this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int)));
71         connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
72                 this, SLOT(rowsInserted(const QModelIndex &, int, int)));
73         connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
74                 this, SLOT(rowsRemoved(const QModelIndex &, int, int)));
75
76         runAllTests();
77 }
78
79 void ModelTest::runAllTests()
80 {
81         if (fetchingMore)
82                 return;
83         nonDestructiveBasicTest();
84         rowCount();
85         columnCount();
86         hasIndex();
87         index();
88         parent();
89         data();
90 }
91
92 /*!
93     nonDestructiveBasicTest tries to call a number of the basic functions (not all)
94     to make sure the model doesn't outright segfault, testing the functions that makes sense.
95 */
96 void ModelTest::nonDestructiveBasicTest()
97 {
98         Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex());
99         model->canFetchMore(QModelIndex());
100         Q_ASSERT(model->columnCount(QModelIndex()) >= 0);
101         Q_ASSERT(model->data(QModelIndex()) == QVariant());
102         fetchingMore = true;
103         model->fetchMore(QModelIndex());
104         fetchingMore = false;
105         Qt::ItemFlags flags = model->flags(QModelIndex());
106         Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0);
107         model->hasChildren(QModelIndex());
108         model->hasIndex(0, 0);
109         model->headerData(0, Qt::Horizontal);
110         model->index(0, 0);
111         Q_ASSERT(model->index(-1, -1) == QModelIndex());
112         model->itemData(QModelIndex());
113         QVariant cache;
114         model->match(QModelIndex(), -1, cache);
115         model->mimeTypes();
116         Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
117         Q_ASSERT(model->rowCount() >= 0);
118         QVariant variant;
119         model->setData(QModelIndex(), variant, -1);
120         model->setHeaderData(-1, Qt::Horizontal, QVariant());
121         model->setHeaderData(0, Qt::Horizontal, QVariant());
122         model->setHeaderData(999999, Qt::Horizontal, QVariant());
123         QMap<int, QVariant> roles;
124         model->sibling(0, 0, QModelIndex());
125         model->span(QModelIndex());
126         model->supportedDropActions();
127 }
128
129 /*!
130     Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
131
132     Models that are dynamically populated are not as fully tested here.
133  */
134 void ModelTest::rowCount()
135 {
136         // check top row
137         QModelIndex topIndex = model->index(0, 0, QModelIndex());
138         int rows = model->rowCount(topIndex);
139         Q_ASSERT(rows >= 0);
140         if (rows > 0)
141                 Q_ASSERT(model->hasChildren(topIndex) == true);
142
143         QModelIndex secondLevelIndex = model->index(0, 0, topIndex);
144         if (secondLevelIndex.isValid()) { // not the top level
145                 // check a row count where parent is valid
146                 rows = model->rowCount(secondLevelIndex);
147                 Q_ASSERT(rows >= 0);
148                 if (rows > 0)
149                         Q_ASSERT(model->hasChildren(secondLevelIndex) == true);
150         }
151
152         // The models rowCount() is tested more extensively in checkChildren(),
153         // but this catches the big mistakes
154 }
155
156 /*!
157     Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
158  */
159 void ModelTest::columnCount()
160 {
161         // check top row
162         QModelIndex topIndex = model->index(0, 0, QModelIndex());
163         Q_ASSERT(model->columnCount(topIndex) >= 0);
164
165         // check a column count where parent is valid
166         QModelIndex childIndex = model->index(0, 0, topIndex);
167         if (childIndex.isValid())
168                 Q_ASSERT(model->columnCount(childIndex) >= 0);
169
170         // columnCount() is tested more extensively in checkChildren(),
171         // but this catches the big mistakes
172 }
173
174 /*!
175     Tests model's implementation of QAbstractItemModel::hasIndex()
176  */
177 void ModelTest::hasIndex()
178 {
179         // Make sure that invalid values returns an invalid index
180         Q_ASSERT(model->hasIndex(-2, -2) == false);
181         Q_ASSERT(model->hasIndex(-2, 0) == false);
182         Q_ASSERT(model->hasIndex(0, -2) == false);
183
184         int rows = model->rowCount();
185         int columns = model->columnCount();
186
187         // check out of bounds
188         Q_ASSERT(model->hasIndex(rows, columns) == false);
189         Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false);
190
191         if (rows > 0)
192                 Q_ASSERT(model->hasIndex(0, 0) == true);
193
194         // hasIndex() is tested more extensively in checkChildren(),
195         // but this catches the big mistakes
196 }
197
198 /*!
199     Tests model's implementation of QAbstractItemModel::index()
200  */
201 void ModelTest::index()
202 {
203         // Make sure that invalid values returns an invalid index
204         Q_ASSERT(model->index(-2, -2) == QModelIndex());
205         Q_ASSERT(model->index(-2, 0) == QModelIndex());
206         Q_ASSERT(model->index(0, -2) == QModelIndex());
207
208         int rows = model->rowCount();
209         int columns = model->columnCount();
210
211         if (rows == 0)
212                 return;
213
214         // Catch off by one errors
215         Q_ASSERT(model->index(rows, columns) == QModelIndex());
216         Q_ASSERT(model->index(0, 0).isValid() == true);
217
218         // Make sure that the same index is *always* returned
219         QModelIndex a = model->index(0, 0);
220         QModelIndex b = model->index(0, 0);
221         Q_ASSERT(a == b);
222
223         // index() is tested more extensively in checkChildren(),
224         // but this catches the big mistakes
225 }
226
227 /*!
228     Tests model's implementation of QAbstractItemModel::parent()
229  */
230 void ModelTest::parent()
231 {
232         // Make sure the model wont crash and will return an invalid QModelIndex
233         // when asked for the parent of an invalid index.
234         Q_ASSERT(model->parent(QModelIndex()) == QModelIndex());
235
236         if (model->rowCount() == 0)
237                 return;
238
239         // Column 0                | Column 1    |
240         // QModelIndex()           |             |
241         //    \- topIndex          | topIndex1   |
242         //         \- childIndex   | childIndex1 |
243
244         // Common error test #1, make sure that a top level index has a parent
245         // that is a invalid QModelIndex.
246         QModelIndex topIndex = model->index(0, 0, QModelIndex());
247         Q_ASSERT(model->parent(topIndex) == QModelIndex());
248
249         // Common error test #2, make sure that a second level index has a parent
250         // that is the first level index.
251         if (model->rowCount(topIndex) > 0) {
252                 QModelIndex childIndex = model->index(0, 0, topIndex);
253                 Q_ASSERT(model->parent(childIndex) == topIndex);
254         }
255
256         // Common error test #3, the second column should NOT have the same children
257         // as the first column in a row.
258         // Usually the second column shouldn't have children.
259         QModelIndex topIndex1 = model->index(0, 1, QModelIndex());
260         if (model->rowCount(topIndex1) > 0) {
261                 QModelIndex childIndex = model->index(0, 0, topIndex);
262                 QModelIndex childIndex1 = model->index(0, 0, topIndex1);
263                 Q_ASSERT(childIndex != childIndex1);
264         }
265
266         // Full test, walk n levels deep through the model making sure that all
267         // parent's children correctly specify their parent.
268         checkChildren(QModelIndex());
269 }
270
271 /*!
272     Called from the parent() test.
273
274     A model that returns an index of parent X should also return X when asking
275     for the parent of the index.
276
277     This recursive function does pretty extensive testing on the whole model in an
278     effort to catch edge cases.
279
280     This function assumes that rowCount(), columnCount() and index() already work.
281     If they have a bug it will point it out, but the above tests should have already
282     found the basic bugs because it is easier to figure out the problem in
283     those tests then this one.
284  */
285 void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth)
286 {
287         // First just try walking back up the tree.
288         QModelIndex p = parent;
289         while (p.isValid())
290                 p = p.parent();
291
292         // For models that are dynamically populated
293         if (model->canFetchMore(parent)) {
294                 fetchingMore = true;
295                 model->fetchMore(parent);
296                 fetchingMore = false;
297         }
298
299         int rows = model->rowCount(parent);
300         int columns = model->columnCount(parent);
301
302         if (rows > 0)
303                 Q_ASSERT(model->hasChildren(parent));
304
305         // Some further testing against rows(), columns(), and hasChildren()
306         Q_ASSERT(rows >= 0);
307         Q_ASSERT(columns >= 0);
308         if (rows > 0)
309                 Q_ASSERT(model->hasChildren(parent) == true);
310
311         //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows
312         //         << "columns:" << columns << "parent column:" << parent.column();
313
314         Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false);
315         for (int r = 0; r < rows; ++r) {
316                 if (model->canFetchMore(parent)) {
317                         fetchingMore = true;
318                         model->fetchMore(parent);
319                         fetchingMore = false;
320                 }
321                 Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false);
322                 for (int c = 0; c < columns; ++c) {
323                         Q_ASSERT(model->hasIndex(r, c, parent) == true);
324                         QModelIndex index = model->index(r, c, parent);
325                         // rowCount() and columnCount() said that it existed...
326                         Q_ASSERT(index.isValid() == true);
327
328                         // index() should always return the same index when called twice in a row
329                         QModelIndex modifiedIndex = model->index(r, c, parent);
330                         Q_ASSERT(index == modifiedIndex);
331
332                         // Make sure we get the same index if we request it twice in a row
333                         QModelIndex a = model->index(r, c, parent);
334                         QModelIndex b = model->index(r, c, parent);
335                         Q_ASSERT(a == b);
336
337                         // Some basic checking on the index that is returned
338                         Q_ASSERT(index.model() == model);
339                         Q_ASSERT(index.row() == r);
340                         Q_ASSERT(index.column() == c);
341                         // While you can technically return a QVariant usually this is a sign
342                         // of an bug in data()  Disable if this really is ok in your model.
343                         //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true);
344
345                         // If the next test fails here is some somewhat useful debug you play with.
346                         /*
347                         if (model->parent(index) != parent) {
348                             qDebug() << r << c << currentDepth << model->data(index).toString()
349                                      << model->data(parent).toString();
350                             qDebug() << index << parent << model->parent(index);
351                             // And a view that you can even use to show the model.
352                             //QTreeView view;
353                             //view.setModel(model);
354                             //view.show();
355                         }*/
356
357                         // Check that we can get back our real parent.
358                         QModelIndex p = model->parent(index);
359                         //qDebug() << "child:" << index;
360                         //qDebug() << p;
361                         //qDebug() << parent;
362                         Q_ASSERT(model->parent(index) == parent);
363
364                         // recursively go down the children
365                         if (model->hasChildren(index) && currentDepth < 10 ) {
366                                 //qDebug() << r << c << "has children" << model->rowCount(index);
367                                 checkChildren(index, ++currentDepth);
368                         }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/
369
370                         // make sure that after testing the children that the index doesn't change.
371                         QModelIndex newerIndex = model->index(r, c, parent);
372                         Q_ASSERT(index == newerIndex);
373                 }
374         }
375 }
376
377 /*!
378     Tests model's implementation of QAbstractItemModel::data()
379  */
380 void ModelTest::data()
381 {
382         // Invalid index should return an invalid qvariant
383         Q_ASSERT(!model->data(QModelIndex()).isValid());
384
385         if (model->rowCount() == 0)
386                 return;
387
388         // A valid index should have a valid QVariant data
389         Q_ASSERT(model->index(0, 0).isValid());
390
391         // shouldn't be able to set data on an invalid index
392         Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
393
394         // General Purpose roles that should return a QString
395         QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
396         if (variant.isValid()) {
397                 Q_ASSERT(qVariantCanConvert<QString>(variant));
398         }
399         variant = model->data(model->index(0, 0), Qt::StatusTipRole);
400         if (variant.isValid()) {
401                 Q_ASSERT(qVariantCanConvert<QString>(variant));
402         }
403         variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
404         if (variant.isValid()) {
405                 Q_ASSERT(qVariantCanConvert<QString>(variant));
406         }
407
408         // General Purpose roles that should return a QSize
409         variant = model->data(model->index(0, 0), Qt::SizeHintRole);
410         if (variant.isValid()) {
411                 Q_ASSERT(qVariantCanConvert<QSize>(variant));
412         }
413
414         // General Purpose roles that should return a QFont
415         QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
416         if (fontVariant.isValid()) {
417                 Q_ASSERT(qVariantCanConvert<QFont>(fontVariant));
418         }
419
420         // Check that the alignment is one we know about
421         QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
422         if (textAlignmentVariant.isValid()) {
423                 int alignment = textAlignmentVariant.toInt();
424                 Q_ASSERT(alignment == Qt::AlignLeft ||
425                          alignment == Qt::AlignRight ||
426                          alignment == Qt::AlignHCenter ||
427                          alignment == Qt::AlignJustify ||
428                          alignment == Qt::AlignTop ||
429                          alignment == Qt::AlignBottom ||
430                          alignment == Qt::AlignVCenter ||
431                          alignment == Qt::AlignCenter ||
432                          alignment == Qt::AlignAbsolute ||
433                          alignment == Qt::AlignLeading ||
434                          alignment == Qt::AlignTrailing);
435         }
436
437         // General Purpose roles that should return a QColor
438         QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
439         if (colorVariant.isValid()) {
440                 Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
441         }
442
443         colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
444         if (colorVariant.isValid()) {
445                 Q_ASSERT(qVariantCanConvert<QColor>(colorVariant));
446         }
447
448         // Check that the "check state" is one we know about.
449         QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
450         if (checkStateVariant.isValid()) {
451                 int state = checkStateVariant.toInt();
452                 Q_ASSERT(state == Qt::Unchecked ||
453                          state == Qt::PartiallyChecked ||
454                          state == Qt::Checked);
455         }
456 }
457
458 /*!
459     Store what is about to be inserted to make sure it actually happens
460
461     \sa rowsInserted()
462  */
463 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
464 {
465         Q_UNUSED(end);
466         Changing c;
467         c.parent = parent;
468         c.oldSize = model->rowCount(parent);
469         c.last = model->data(model->index(start - 1, 0, parent));
470         c.next = model->data(model->index(start, 0, parent));
471         insert.push(c);
472 }
473
474 /*!
475     Confirm that what was said was going to happen actually did
476
477     \sa rowsAboutToBeInserted()
478  */
479 void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
480 {
481         Changing c = insert.pop();
482         Q_ASSERT(c.parent == parent);
483         Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
484         Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
485         /*
486         if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
487             qDebug() << start << end;
488             for (int i=0; i < model->rowCount(); ++i)
489                 qDebug() << model->index(i, 0).data().toString();
490             qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent));
491         }
492         */
493         Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
494 }
495
496 void ModelTest::layoutAboutToBeChanged()
497 {
498         for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
499                 changing.append(QPersistentModelIndex(model->index(i, 0)));
500 }
501
502 void ModelTest::layoutChanged()
503 {
504         for (int i = 0; i < changing.count(); ++i) {
505                 QPersistentModelIndex p = changing[i];
506                 Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
507         }
508         changing.clear();
509 }
510
511 /*!
512     Store what is about to be inserted to make sure it actually happens
513
514     \sa rowsRemoved()
515  */
516 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
517 {
518         Changing c;
519         c.parent = parent;
520         c.oldSize = model->rowCount(parent);
521         c.last = model->data(model->index(start - 1, 0, parent));
522         c.next = model->data(model->index(end + 1, 0, parent));
523         remove.push(c);
524 }
525
526 /*!
527     Confirm that what was said was going to happen actually did
528
529     \sa rowsAboutToBeRemoved()
530  */
531 void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
532 {
533         Changing c = remove.pop();
534         Q_ASSERT(c.parent == parent);
535         Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
536         Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
537         Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
538 }
539