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