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