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>
25 
26 #include "modeltest.h"
27 
Q_DECLARE_METATYPE(QModelIndex)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(QModelIndex,int,int)),
38             this, SLOT(runAllTests()));
39     connect(model, SIGNAL(columnsAboutToBeRemoved(QModelIndex,int,int)),
40             this, SLOT(runAllTests()));
41     connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
42             this, SLOT(runAllTests()));
43     connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
44             this, SLOT(runAllTests()));
45     connect(model, SIGNAL(dataChanged(QModelIndex,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(QModelIndex,int,int)),
53             this, SLOT(runAllTests()));
54     connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
55             this, SLOT(runAllTests()));
56     connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
57             this, SLOT(runAllTests()));
58     connect(model, SIGNAL(rowsRemoved(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(QModelIndex,int,int)),
68             this, SLOT(rowsAboutToBeInserted(QModelIndex,int,int)));
69     connect(model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
70             this, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
71     connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
72             this, SLOT(rowsInserted(QModelIndex,int,int)));
73     connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
74             this, SLOT(rowsRemoved(QModelIndex,int,int)));
75 
76     runAllTests();
77 }
78 
runAllTests()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 */
nonDestructiveBasicTest()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     Q_UNUSED(flags);
108     model->hasChildren(QModelIndex());
109     model->hasIndex(0, 0);
110     model->headerData(0, Qt::Horizontal);
111     model->index(0, 0);
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  */
rowCount()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  */
columnCount()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  */
hasIndex()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     Q_UNUSED(columns);
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  */
index()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     Q_UNUSED(columns);
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  */
parent()230 void ModelTest::parent()
231 {
232     // Make sure the model won't 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  */
checkChildren(const QModelIndex & parent,int currentDepth)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     //dbgKrita << "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                 dbgKrita << r << c << currentDepth << model->data(index).toString()
349                          << model->data(parent).toString();
350                 dbgKrita << 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             Q_ASSERT(model->parent(index) == parent);
359 
360             // recursively go down the children
361             if (model->hasChildren(index) && currentDepth < 10) {
362                 //dbgKrita << r << c << "has children" << model->rowCount(index);
363                 checkChildren(index, ++currentDepth);
364             }/* else { if (currentDepth >= 10) dbgKrita << "checked 10 deep"; };*/
365 
366             // make sure that after testing the children that the index doesn't change.
367             QModelIndex newerIndex = model->index(r, c, parent);
368             Q_ASSERT(index == newerIndex);
369         }
370     }
371 }
372 
373 /*!
374     Tests model's implementation of QAbstractItemModel::data()
375  */
data()376 void ModelTest::data()
377 {
378     // Invalid index should return an invalid qvariant
379     Q_ASSERT(!model->data(QModelIndex()).isValid());
380 
381     if (model->rowCount() == 0)
382         return;
383 
384     // A valid index should have a valid QVariant data
385     Q_ASSERT(model->index(0, 0).isValid());
386 
387     // shouldn't be able to set data on an invalid index
388     Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false);
389 
390     // General Purpose roles that should return a QString
391     QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole);
392     if (variant.isValid()) {
393         Q_ASSERT(variant.canConvert<QString>());
394     }
395     variant = model->data(model->index(0, 0), Qt::StatusTipRole);
396     if (variant.isValid()) {
397         Q_ASSERT(variant.canConvert<QString>());
398     }
399     variant = model->data(model->index(0, 0), Qt::WhatsThisRole);
400     if (variant.isValid()) {
401         Q_ASSERT(variant.canConvert<QString>());
402     }
403 
404     // General Purpose roles that should return a QSize
405     variant = model->data(model->index(0, 0), Qt::SizeHintRole);
406     if (variant.isValid()) {
407         Q_ASSERT(variant.canConvert<QSize>());
408     }
409 
410     // General Purpose roles that should return a QFont
411     QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole);
412     if (fontVariant.isValid()) {
413         Q_ASSERT(fontVariant.canConvert<QFont>());
414     }
415 
416     // Check that the alignment is one we know about
417     QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole);
418     if (textAlignmentVariant.isValid()) {
419         int alignment = textAlignmentVariant.toInt();
420         Q_UNUSED(alignment);
421         Q_ASSERT(alignment == Qt::AlignLeft ||
422                  alignment == Qt::AlignRight ||
423                  alignment == Qt::AlignHCenter ||
424                  alignment == Qt::AlignJustify ||
425                  alignment == Qt::AlignTop ||
426                  alignment == Qt::AlignBottom ||
427                  alignment == Qt::AlignVCenter ||
428                  alignment == Qt::AlignCenter ||
429                  alignment == Qt::AlignAbsolute ||
430                  alignment == Qt::AlignLeading ||
431                  alignment == Qt::AlignTrailing);
432     }
433 
434     // General Purpose roles that should return a QColor
435     QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole);
436     if (colorVariant.isValid()) {
437         Q_ASSERT(colorVariant.canConvert<QColor>());
438     }
439 
440     colorVariant = model->data(model->index(0, 0), Qt::TextColorRole);
441     if (colorVariant.isValid()) {
442         Q_ASSERT(colorVariant.canConvert<QColor>());
443     }
444 
445     // Check that the "check state" is one we know about.
446     QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole);
447     if (checkStateVariant.isValid()) {
448         int state = checkStateVariant.toInt();
449         Q_UNUSED(state);
450         Q_ASSERT(state == Qt::Unchecked ||
451                  state == Qt::PartiallyChecked ||
452                  state == Qt::Checked);
453     }
454 }
455 
456 /*!
457     Store what is about to be inserted to make sure it actually happens
458 
459     \sa rowsInserted()
460  */
rowsAboutToBeInserted(const QModelIndex & parent,int start,int end)461 void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end)
462 {
463     Q_UNUSED(end);
464     Changing c;
465     c.parent = parent;
466     c.oldSize = model->rowCount(parent);
467     c.last = model->data(model->index(start - 1, 0, parent));
468     c.next = model->data(model->index(start, 0, parent));
469     insert.push(c);
470 }
471 
472 /*!
473     Confirm that what was said was going to happen actually did
474 
475     \sa rowsAboutToBeInserted()
476  */
rowsInserted(const QModelIndex & parent,int start,int end)477 void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end)
478 {
479     Q_UNUSED(parent);
480     Q_UNUSED(start);
481     Q_UNUSED(end);
482 
483     Changing c = insert.pop();
484     Q_ASSERT(c.parent == parent);
485     Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent));
486     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
487     /*
488     if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
489         dbgKrita << start << end;
490         for (int i=0; i < model->rowCount(); ++i)
491             dbgKrita << model->index(i, 0).data().toString();
492         dbgKrita << c.next << model->data(model->index(end + 1, 0, c.parent));
493     }
494     */
495     Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent)));
496 }
497 
layoutAboutToBeChanged()498 void ModelTest::layoutAboutToBeChanged()
499 {
500     for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i)
501         changing.append(QPersistentModelIndex(model->index(i, 0)));
502 }
503 
layoutChanged()504 void ModelTest::layoutChanged()
505 {
506     for (int i = 0; i < changing.count(); ++i) {
507         QPersistentModelIndex p = changing[i];
508         Q_ASSERT(p == model->index(p.row(), p.column(), p.parent()));
509     }
510     changing.clear();
511 }
512 
513 /*!
514     Store what is about to be inserted to make sure it actually happens
515 
516     \sa rowsRemoved()
517  */
rowsAboutToBeRemoved(const QModelIndex & parent,int start,int end)518 void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
519 {
520     Changing c;
521     c.parent = parent;
522     c.oldSize = model->rowCount(parent);
523     c.last = model->data(model->index(start - 1, 0, parent));
524     c.next = model->data(model->index(end + 1, 0, parent));
525     remove.push(c);
526 }
527 
528 /*!
529     Confirm that what was said was going to happen actually did
530 
531     \sa rowsAboutToBeRemoved()
532  */
rowsRemoved(const QModelIndex & parent,int start,int end)533 void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end)
534 {
535     Q_UNUSED(parent);
536     Q_UNUSED(start);
537     Q_UNUSED(end);
538 
539     Changing c = remove.pop();
540     Q_ASSERT(c.parent == parent);
541     Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent));
542     Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent)));
543     Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent)));
544 }
545 
546