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