1 | // Copyright (C) 2016 The Qt Company Ltd. |
---|---|
2 | // Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include "qabstractitemmodeltester.h" |
6 | |
7 | #include <private/qobject_p.h> |
8 | #include <private/qabstractitemmodel_p.h> |
9 | #include <QtCore/qmetatype.h> |
10 | #include <QtCore/QPointer> |
11 | #include <QtCore/QAbstractItemModel> |
12 | #include <QtCore/QStack> |
13 | #include <QTest> |
14 | #include <QLoggingCategory> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_LOGGING_CATEGORY(lcModelTest, "qt.modeltest") |
19 | |
20 | #define MODELTESTER_VERIFY(statement) \ |
21 | do { \ |
22 | if (!verify(static_cast<bool>(statement), #statement, "", __FILE__, __LINE__)) \ |
23 | return; \ |
24 | } while (false) |
25 | |
26 | #define MODELTESTER_COMPARE(actual, expected) \ |
27 | do { \ |
28 | if (!compare((actual), (expected), #actual, #expected, __FILE__, __LINE__)) \ |
29 | return; \ |
30 | } while (false) |
31 | |
32 | class QAbstractItemModelTesterPrivate : public QObjectPrivate |
33 | { |
34 | Q_DECLARE_PUBLIC(QAbstractItemModelTester) |
35 | public: |
36 | QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode); |
37 | |
38 | void nonDestructiveBasicTest(); |
39 | void rowAndColumnCount(); |
40 | void hasIndex(); |
41 | void index(); |
42 | void parent(); |
43 | void data(); |
44 | |
45 | void runAllTests(); |
46 | |
47 | void layoutAboutToBeChanged(); |
48 | void layoutChanged(); |
49 | |
50 | void modelAboutToBeReset(); |
51 | void modelReset(); |
52 | |
53 | void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last); |
54 | void columnsInserted(const QModelIndex &parent, int first, int last); |
55 | void columnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, |
56 | const QModelIndex &destinationParent, int destinationColumn); |
57 | void columnsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, |
58 | int column); |
59 | void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); |
60 | void columnsRemoved(const QModelIndex &parent, int first, int last); |
61 | |
62 | void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); |
63 | void rowsInserted(const QModelIndex &parent, int start, int end); |
64 | void rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, |
65 | const QModelIndex &destinationParent, int destinationRow); |
66 | void rowsMoved(const QModelIndex &parent, int start, int end, const QModelIndex &destination, |
67 | int row); |
68 | void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); |
69 | void rowsRemoved(const QModelIndex &parent, int start, int end); |
70 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); |
71 | void headerDataChanged(Qt::Orientation orientation, int start, int end); |
72 | |
73 | private: |
74 | void checkChildren(const QModelIndex &parent, int currentDepth = 0); |
75 | |
76 | bool verify(bool statement, const char *statementStr, const char *description, const char *file, int line); |
77 | void testDataGuiRoles(QAbstractItemModelTester *tester); |
78 | |
79 | template<typename T1, typename T2> |
80 | bool compare(const T1 &t1, const T2 &t2, |
81 | const char *actual, const char *expected, |
82 | const char *file, int line); |
83 | |
84 | QPointer<QAbstractItemModel> model; |
85 | QAbstractItemModelTester::FailureReportingMode failureReportingMode; |
86 | |
87 | struct Changing { |
88 | QModelIndex parent; |
89 | int oldSize; |
90 | QVariant last; |
91 | QVariant next; |
92 | }; |
93 | QStack<Changing> insert; |
94 | QStack<Changing> remove; |
95 | |
96 | bool useFetchMore = true; |
97 | bool fetchingMore; |
98 | |
99 | enum class ChangeInFlight { |
100 | None, |
101 | ColumnsInserted, |
102 | ColumnsMoved, |
103 | ColumnsRemoved, |
104 | LayoutChanged, |
105 | ModelReset, |
106 | RowsInserted, |
107 | RowsMoved, |
108 | RowsRemoved |
109 | }; |
110 | ChangeInFlight changeInFlight = ChangeInFlight::None; |
111 | |
112 | QList<QPersistentModelIndex> changing; |
113 | }; |
114 | |
115 | /*! |
116 | \class QAbstractItemModelTester |
117 | \since 5.11 |
118 | \inmodule QtTest |
119 | |
120 | \brief The QAbstractItemModelTester class helps testing QAbstractItemModel subclasses. |
121 | |
122 | The QAbstractItemModelTester class is a utility class to test item models. |
123 | |
124 | When implementing an item model (that is, a concrete QAbstractItemModel |
125 | subclass) one must abide to a very strict set of rules that ensure |
126 | consistency for users of the model (views, proxy models, and so on). |
127 | |
128 | For instance, for a given index, a model's reimplementation of |
129 | \l{QAbstractItemModel::hasChildren()}{hasChildren()} must be consistent |
130 | with the values returned by \l{QAbstractItemModel::rowCount()}{rowCount()} |
131 | and \l{QAbstractItemModel::columnCount()}{columnCount()}. |
132 | |
133 | QAbstractItemModelTester helps catching the most common errors in custom |
134 | item model classes. By performing a series of tests, it |
135 | will try to check that the model status is consistent at all times. The |
136 | tests will be repeated automatically every time the model is modified. |
137 | |
138 | QAbstractItemModelTester employs non-destructive tests, which typically |
139 | consist in reading data and metadata out of a given item model. |
140 | QAbstractItemModelTester will also attempt illegal modifications of |
141 | the model. In models which are properly implemented, such attempts |
142 | should be rejected, and no data should be changed as a consequence. |
143 | |
144 | \section1 Usage |
145 | |
146 | Using QAbstractItemModelTester is straightforward. In a \l{Qt Test Overview}{test case} |
147 | it is sufficient to create an instance, passing the model that |
148 | needs to be tested to the constructor: |
149 | |
150 | \code |
151 | MyModel *modelToBeTested = ...; |
152 | auto tester = new QAbstractItemModelTester(modelToBeTested); |
153 | \endcode |
154 | |
155 | QAbstractItemModelTester will report testing failures through the |
156 | Qt Test logging mechanisms. |
157 | |
158 | It is also possible to use QAbstractItemModelTester outside of a test case. |
159 | For instance, it may be useful to test an item model used by an application |
160 | without the need of building an explicit unit test for such a model (which |
161 | might be challenging). In order to use QAbstractItemModelTester outside of |
162 | a test case, pass one of the \c QAbstractItemModelTester::FailureReportingMode |
163 | enumerators to its constructor, therefore specifying how failures should |
164 | be logged. |
165 | |
166 | QAbstractItemModelTester may also report additional debugging information |
167 | as logging messages under the \c qt.modeltest logging category. Such |
168 | debug logging is disabled by default; refer to the |
169 | QLoggingCategory documentation to learn how to enable it. |
170 | |
171 | \note While QAbstractItemModelTester is a valid help for development and |
172 | testing of custom item models, it does not (and cannot) catch all possible |
173 | problems in QAbstractItemModel subclasses. Notably, it will never perform |
174 | meaningful destructive testing of a model, which must be therefore tested |
175 | separately. |
176 | |
177 | \sa {Model/View Programming}, QAbstractItemModel |
178 | */ |
179 | |
180 | /*! |
181 | \enum QAbstractItemModelTester::FailureReportingMode |
182 | |
183 | This enumeration specifies how QAbstractItemModelTester should report |
184 | a failure when it tests a QAbstractItemModel subclass. |
185 | |
186 | \value QtTest The failures will be reported as QtTest test failures. |
187 | |
188 | \value Warning The failures will be reported as |
189 | warning messages in the \c{qt.modeltest} logging category. |
190 | |
191 | \value Fatal A failure will cause immediate and |
192 | abnormal program termination. The reason for the failure will be reported |
193 | using \c{qFatal()}. |
194 | */ |
195 | |
196 | /*! |
197 | Creates a model tester instance, with the given \a parent, that will test |
198 | the model \a model. |
199 | */ |
200 | QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, QObject *parent) |
201 | : QAbstractItemModelTester(model, FailureReportingMode::QtTest, parent) |
202 | { |
203 | } |
204 | |
205 | /*! |
206 | Creates a model tester instance, with the given \a parent, that will test |
207 | the model \a model, using the specified \a mode to report test failures. |
208 | |
209 | \sa QAbstractItemModelTester::FailureReportingMode |
210 | */ |
211 | QAbstractItemModelTester::QAbstractItemModelTester(QAbstractItemModel *model, FailureReportingMode mode, QObject *parent) |
212 | : QObject(*new QAbstractItemModelTesterPrivate(model, mode), parent) |
213 | { |
214 | if (!model) |
215 | qFatal(msg: "%s: model must not be null", Q_FUNC_INFO); |
216 | |
217 | Q_D(QAbstractItemModelTester); |
218 | |
219 | auto runAllTests = [d] { d->runAllTests(); }; |
220 | |
221 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeInserted, |
222 | context: this, slot&: runAllTests); |
223 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeRemoved, |
224 | context: this, slot&: runAllTests); |
225 | connect(sender: model, signal: &QAbstractItemModel::columnsInserted, |
226 | context: this, slot&: runAllTests); |
227 | connect(sender: model, signal: &QAbstractItemModel::columnsRemoved, |
228 | context: this, slot&: runAllTests); |
229 | connect(sender: model, signal: &QAbstractItemModel::dataChanged, |
230 | context: this, slot&: runAllTests); |
231 | connect(sender: model, signal: &QAbstractItemModel::headerDataChanged, |
232 | context: this, slot&: runAllTests); |
233 | connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged, |
234 | context: this, slot&: runAllTests); |
235 | connect(sender: model, signal: &QAbstractItemModel::layoutChanged, |
236 | context: this, slot&: runAllTests); |
237 | connect(sender: model, signal: &QAbstractItemModel::modelReset, |
238 | context: this, slot&: runAllTests); |
239 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted, |
240 | context: this, slot&: runAllTests); |
241 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
242 | context: this, slot&: runAllTests); |
243 | connect(sender: model, signal: &QAbstractItemModel::rowsInserted, |
244 | context: this, slot&: runAllTests); |
245 | connect(sender: model, signal: &QAbstractItemModel::rowsRemoved, |
246 | context: this, slot&: runAllTests); |
247 | |
248 | // Special checks for changes |
249 | connect(sender: model, signal: &QAbstractItemModel::layoutAboutToBeChanged, |
250 | context: this, slot: [d]{ d->layoutAboutToBeChanged(); }); |
251 | connect(sender: model, signal: &QAbstractItemModel::layoutChanged, |
252 | context: this, slot: [d]{ d->layoutChanged(); }); |
253 | |
254 | // column operations |
255 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeInserted, |
256 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeInserted(parent, first: start, last: end); }); |
257 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeMoved, |
258 | context: this, slot: [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn) { |
259 | d->columnsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationColumn); }); |
260 | connect(sender: model, signal: &QAbstractItemModel::columnsAboutToBeRemoved, |
261 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsAboutToBeRemoved(parent, first: start, last: end); }); |
262 | connect(sender: model, signal: &QAbstractItemModel::columnsInserted, |
263 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsInserted(parent, first: start, last: end); }); |
264 | connect(sender: model, signal: &QAbstractItemModel::columnsMoved, |
265 | context: this, slot: [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int col) { |
266 | d->columnsMoved(parent, start, end, destination, column: col); }); |
267 | connect(sender: model, signal: &QAbstractItemModel::columnsRemoved, |
268 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->columnsRemoved(parent, first: start, last: end); }); |
269 | |
270 | // row operations |
271 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeInserted, |
272 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeInserted(parent, start, end); }); |
273 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeMoved, |
274 | context: this, slot: [d](const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { |
275 | d->rowsAboutToBeMoved(sourceParent, sourceStart, sourceEnd, destinationParent, destinationRow); }); |
276 | connect(sender: model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
277 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsAboutToBeRemoved(parent, start, end); }); |
278 | connect(sender: model, signal: &QAbstractItemModel::rowsInserted, |
279 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsInserted(parent, start, end); }); |
280 | connect(sender: model, signal: &QAbstractItemModel::rowsMoved, |
281 | context: this, slot: [d](const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row) { |
282 | d->rowsMoved(parent, start, end, destination, row); }); |
283 | connect(sender: model, signal: &QAbstractItemModel::rowsRemoved, |
284 | context: this, slot: [d](const QModelIndex &parent, int start, int end) { d->rowsRemoved(parent, start, end); }); |
285 | |
286 | // reset |
287 | connect(sender: model, signal: &QAbstractItemModel::modelAboutToBeReset, |
288 | context: this, slot: [d]() { d->modelAboutToBeReset(); }); |
289 | connect(sender: model, signal: &QAbstractItemModel::modelReset, |
290 | context: this, slot: [d]() { d->modelReset(); }); |
291 | |
292 | // data |
293 | connect(sender: model, signal: &QAbstractItemModel::dataChanged, |
294 | context: this, slot: [d](const QModelIndex &topLeft, const QModelIndex &bottomRight) { d->dataChanged(topLeft, bottomRight); }); |
295 | connect(sender: model, signal: &QAbstractItemModel::headerDataChanged, |
296 | context: this, slot: [d](Qt::Orientation orientation, int start, int end) { d->headerDataChanged(orientation, start, end); }); |
297 | |
298 | runAllTests(); |
299 | } |
300 | |
301 | /*! |
302 | Returns the model that this instance is testing. |
303 | */ |
304 | QAbstractItemModel *QAbstractItemModelTester::model() const |
305 | { |
306 | Q_D(const QAbstractItemModelTester); |
307 | return d->model.data(); |
308 | } |
309 | |
310 | /*! |
311 | Returns the mode that this instancing is using to report test failures. |
312 | |
313 | \sa QAbstractItemModelTester::FailureReportingMode |
314 | */ |
315 | QAbstractItemModelTester::FailureReportingMode QAbstractItemModelTester::failureReportingMode() const |
316 | { |
317 | Q_D(const QAbstractItemModelTester); |
318 | return d->failureReportingMode; |
319 | } |
320 | |
321 | /*! |
322 | If \a value is true, enables dynamic population of the |
323 | tested model, which is the default. |
324 | If \a value is false, it disables it. |
325 | |
326 | \since 6.4 |
327 | \sa QAbstractItemModel::fetchMore() |
328 | */ |
329 | void QAbstractItemModelTester::setUseFetchMore(bool value) |
330 | { |
331 | Q_D(QAbstractItemModelTester); |
332 | d->useFetchMore = value; |
333 | } |
334 | |
335 | bool QAbstractItemModelTester::verify(bool statement, const char *statementStr, const char *description, const char *file, int line) |
336 | { |
337 | Q_D(QAbstractItemModelTester); |
338 | return d->verify(statement, statementStr, description, file, line); |
339 | } |
340 | |
341 | QAbstractItemModelTesterPrivate::QAbstractItemModelTesterPrivate(QAbstractItemModel *model, QAbstractItemModelTester::FailureReportingMode failureReportingMode) |
342 | : model(model), |
343 | failureReportingMode(failureReportingMode), |
344 | fetchingMore(false) |
345 | { |
346 | } |
347 | |
348 | void QAbstractItemModelTesterPrivate::runAllTests() |
349 | { |
350 | if (fetchingMore) |
351 | return; |
352 | nonDestructiveBasicTest(); |
353 | rowAndColumnCount(); |
354 | hasIndex(); |
355 | index(); |
356 | parent(); |
357 | data(); |
358 | } |
359 | |
360 | /* |
361 | nonDestructiveBasicTest tries to call a number of the basic functions (not all) |
362 | to make sure the model doesn't outright segfault, testing the functions that makes sense. |
363 | */ |
364 | void QAbstractItemModelTesterPrivate::nonDestructiveBasicTest() |
365 | { |
366 | MODELTESTER_VERIFY(!model->buddy(QModelIndex()).isValid()); |
367 | model->canFetchMore(parent: QModelIndex()); |
368 | MODELTESTER_VERIFY(model->columnCount(QModelIndex()) >= 0); |
369 | if (useFetchMore) { |
370 | fetchingMore = true; |
371 | model->fetchMore(parent: QModelIndex()); |
372 | fetchingMore = false; |
373 | } |
374 | Qt::ItemFlags flags = model->flags(index: QModelIndex()); |
375 | MODELTESTER_VERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); |
376 | model->hasChildren(parent: QModelIndex()); |
377 | const bool hasRow = model->hasIndex(row: 0, column: 0); |
378 | QVariant cache; |
379 | if (hasRow) |
380 | model->match(start: model->index(row: 0, column: 0), role: -1, value: cache); |
381 | model->mimeTypes(); |
382 | MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); |
383 | MODELTESTER_VERIFY(model->rowCount() >= 0); |
384 | model->span(index: QModelIndex()); |
385 | model->supportedDropActions(); |
386 | model->roleNames(); |
387 | } |
388 | |
389 | /* |
390 | Tests model's implementation of QAbstractItemModel::rowCount(), |
391 | columnCount() and hasChildren(). |
392 | |
393 | Models that are dynamically populated are not as fully tested here. |
394 | */ |
395 | void QAbstractItemModelTesterPrivate::rowAndColumnCount() |
396 | { |
397 | if (!model->hasChildren()) |
398 | return; |
399 | |
400 | QModelIndex topIndex = model->index(row: 0, column: 0, parent: QModelIndex()); |
401 | |
402 | // check top row |
403 | int rows = model->rowCount(parent: topIndex); |
404 | MODELTESTER_VERIFY(rows >= 0); |
405 | |
406 | int columns = model->columnCount(parent: topIndex); |
407 | MODELTESTER_VERIFY(columns >= 0); |
408 | |
409 | if (rows == 0 || columns == 0) |
410 | return; |
411 | |
412 | MODELTESTER_VERIFY(model->hasChildren(topIndex)); |
413 | |
414 | QModelIndex secondLevelIndex = model->index(row: 0, column: 0, parent: topIndex); |
415 | MODELTESTER_VERIFY(secondLevelIndex.isValid()); |
416 | |
417 | rows = model->rowCount(parent: secondLevelIndex); |
418 | MODELTESTER_VERIFY(rows >= 0); |
419 | |
420 | columns = model->columnCount(parent: secondLevelIndex); |
421 | MODELTESTER_VERIFY(columns >= 0); |
422 | |
423 | if (rows == 0 || columns == 0) |
424 | return; |
425 | |
426 | MODELTESTER_VERIFY(model->hasChildren(secondLevelIndex)); |
427 | |
428 | // rowCount() / columnCount() are tested more extensively in checkChildren() |
429 | } |
430 | |
431 | /* |
432 | Tests model's implementation of QAbstractItemModel::hasIndex() |
433 | */ |
434 | void QAbstractItemModelTesterPrivate::hasIndex() |
435 | { |
436 | // Make sure that invalid values returns an invalid index |
437 | MODELTESTER_VERIFY(!model->hasIndex(-2, -2)); |
438 | MODELTESTER_VERIFY(!model->hasIndex(-2, 0)); |
439 | MODELTESTER_VERIFY(!model->hasIndex(0, -2)); |
440 | |
441 | const int rows = model->rowCount(); |
442 | const int columns = model->columnCount(); |
443 | |
444 | // check out of bounds |
445 | MODELTESTER_VERIFY(!model->hasIndex(rows, columns)); |
446 | MODELTESTER_VERIFY(!model->hasIndex(rows + 1, columns + 1)); |
447 | |
448 | if (rows > 0 && columns > 0) |
449 | MODELTESTER_VERIFY(model->hasIndex(0, 0)); |
450 | |
451 | // hasIndex() is tested more extensively in checkChildren(), |
452 | // but this catches the big mistakes |
453 | } |
454 | |
455 | /* |
456 | Tests model's implementation of QAbstractItemModel::index() |
457 | */ |
458 | void QAbstractItemModelTesterPrivate::index() |
459 | { |
460 | const int rows = model->rowCount(); |
461 | const int columns = model->columnCount(); |
462 | |
463 | for (int row = 0; row < rows; ++row) { |
464 | for (int column = 0; column < columns; ++column) { |
465 | // Make sure that the same index is *always* returned |
466 | QModelIndex a = model->index(row, column); |
467 | QModelIndex b = model->index(row, column); |
468 | MODELTESTER_VERIFY(a.isValid()); |
469 | MODELTESTER_VERIFY(b.isValid()); |
470 | MODELTESTER_COMPARE(a, b); |
471 | } |
472 | } |
473 | |
474 | // index() is tested more extensively in checkChildren(), |
475 | // but this catches the big mistakes |
476 | } |
477 | |
478 | /* |
479 | Tests model's implementation of QAbstractItemModel::parent() |
480 | */ |
481 | void QAbstractItemModelTesterPrivate::parent() |
482 | { |
483 | // Make sure the model won't crash and will return an invalid QModelIndex |
484 | // when asked for the parent of an invalid index. |
485 | MODELTESTER_VERIFY(!model->parent(QModelIndex()).isValid()); |
486 | |
487 | if (model->rowCount() == 0 || model->columnCount() == 0) |
488 | return; |
489 | |
490 | // Column 0 | Column 1 | |
491 | // QModelIndex() | | |
492 | // \- topIndex | topIndex1 | |
493 | // \- childIndex | childIndex1 | |
494 | |
495 | // Common error test #1, make sure that a top level index has a parent |
496 | // that is a invalid QModelIndex. |
497 | QModelIndex topIndex = model->index(row: 0, column: 0, parent: QModelIndex()); |
498 | MODELTESTER_VERIFY(topIndex.isValid()); |
499 | MODELTESTER_VERIFY(!model->parent(topIndex).isValid()); |
500 | |
501 | // Common error test #2, make sure that a second level index has a parent |
502 | // that is the first level index. |
503 | if (model->rowCount(parent: topIndex) > 0 && model->columnCount(parent: topIndex) > 0) { |
504 | QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex); |
505 | MODELTESTER_VERIFY(childIndex.isValid()); |
506 | MODELTESTER_COMPARE(model->parent(childIndex), topIndex); |
507 | } |
508 | |
509 | // Common error test #3, the second column should NOT have the same children |
510 | // as the first column in a row. |
511 | // Usually the second column shouldn't have children. |
512 | if (model->hasIndex(row: 0, column: 1)) { |
513 | QModelIndex topIndex1 = model->index(row: 0, column: 1, parent: QModelIndex()); |
514 | MODELTESTER_VERIFY(topIndex1.isValid()); |
515 | if (model->rowCount(parent: topIndex) > 0 && model->rowCount(parent: topIndex1) > 0) { |
516 | QModelIndex childIndex = model->index(row: 0, column: 0, parent: topIndex); |
517 | MODELTESTER_VERIFY(childIndex.isValid()); |
518 | QModelIndex childIndex1 = model->index(row: 0, column: 0, parent: topIndex1); |
519 | MODELTESTER_VERIFY(childIndex1.isValid()); |
520 | MODELTESTER_VERIFY(childIndex != childIndex1); |
521 | } |
522 | } |
523 | |
524 | // Full test, walk n levels deep through the model making sure that all |
525 | // parent's children correctly specify their parent. |
526 | checkChildren(parent: QModelIndex()); |
527 | } |
528 | |
529 | /* |
530 | Called from the parent() test. |
531 | |
532 | A model that returns an index of parent X should also return X when asking |
533 | for the parent of the index. |
534 | |
535 | This recursive function does pretty extensive testing on the whole model in an |
536 | effort to catch edge cases. |
537 | |
538 | This function assumes that rowCount(), columnCount() and index() already work. |
539 | If they have a bug it will point it out, but the above tests should have already |
540 | found the basic bugs because it is easier to figure out the problem in |
541 | those tests then this one. |
542 | */ |
543 | void QAbstractItemModelTesterPrivate::checkChildren(const QModelIndex &parent, int currentDepth) |
544 | { |
545 | // First just try walking back up the tree. |
546 | QModelIndex p = parent; |
547 | while (p.isValid()) |
548 | p = p.parent(); |
549 | |
550 | // For models that are dynamically populated |
551 | if (model->canFetchMore(parent) && useFetchMore) { |
552 | fetchingMore = true; |
553 | model->fetchMore(parent); |
554 | fetchingMore = false; |
555 | } |
556 | |
557 | const int rows = model->rowCount(parent); |
558 | const int columns = model->columnCount(parent); |
559 | |
560 | if (rows > 0) |
561 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
562 | |
563 | // Some further testing against rows(), columns(), and hasChildren() |
564 | MODELTESTER_VERIFY(rows >= 0); |
565 | MODELTESTER_VERIFY(columns >= 0); |
566 | if (rows > 0 && columns > 0) |
567 | MODELTESTER_VERIFY(model->hasChildren(parent)); |
568 | |
569 | const QModelIndex topLeftChild = model->index(row: 0, column: 0, parent); |
570 | |
571 | MODELTESTER_VERIFY(!model->hasIndex(rows, 0, parent)); |
572 | MODELTESTER_VERIFY(!model->hasIndex(rows + 1, 0, parent)); |
573 | |
574 | for (int r = 0; r < rows; ++r) { |
575 | MODELTESTER_VERIFY(!model->hasIndex(r, columns, parent)); |
576 | MODELTESTER_VERIFY(!model->hasIndex(r, columns + 1, parent)); |
577 | for (int c = 0; c < columns; ++c) { |
578 | MODELTESTER_VERIFY(model->hasIndex(r, c, parent)); |
579 | QModelIndex index = model->index(row: r, column: c, parent); |
580 | // rowCount() and columnCount() said that it existed... |
581 | if (!index.isValid()) |
582 | qCWarning(lcModelTest) << "Got invalid index at row="<< r << "col="<< c << "parent="<< parent; |
583 | MODELTESTER_VERIFY(index.isValid()); |
584 | |
585 | // index() should always return the same index when called twice in a row |
586 | QModelIndex modifiedIndex = model->index(row: r, column: c, parent); |
587 | MODELTESTER_COMPARE(index, modifiedIndex); |
588 | |
589 | { |
590 | const QModelIndex sibling = model->sibling(row: r, column: c, idx: topLeftChild); |
591 | MODELTESTER_COMPARE(index, sibling); |
592 | } |
593 | { |
594 | const QModelIndex sibling = topLeftChild.sibling(arow: r, acolumn: c); |
595 | MODELTESTER_COMPARE(index, sibling); |
596 | } |
597 | |
598 | // Some basic checking on the index that is returned |
599 | MODELTESTER_COMPARE(index.model(), model); |
600 | MODELTESTER_COMPARE(index.row(), r); |
601 | MODELTESTER_COMPARE(index.column(), c); |
602 | |
603 | // If the next test fails here is some somewhat useful debug you play with. |
604 | if (model->parent(child: index) != parent) { |
605 | qCWarning(lcModelTest) << "Inconsistent parent() implementation detected:"; |
606 | qCWarning(lcModelTest) << " index="<< index << "exp. parent="<< parent << "act. parent="<< model->parent(child: index); |
607 | qCWarning(lcModelTest) << " row="<< r << "col="<< c << "depth="<< currentDepth; |
608 | qCWarning(lcModelTest) << " data for child"<< model->data(index).toString(); |
609 | qCWarning(lcModelTest) << " data for parent"<< model->data(index: parent).toString(); |
610 | } |
611 | |
612 | // Check that we can get back our real parent. |
613 | MODELTESTER_COMPARE(model->parent(index), parent); |
614 | |
615 | QPersistentModelIndex persistentIndex = index; |
616 | |
617 | // recursively go down the children |
618 | if (model->hasChildren(parent: index) && currentDepth < 10) |
619 | checkChildren(parent: index, currentDepth: currentDepth + 1); |
620 | |
621 | // make sure that after testing the children that the index doesn't change. |
622 | QModelIndex newerIndex = model->index(row: r, column: c, parent); |
623 | MODELTESTER_COMPARE(persistentIndex, newerIndex); |
624 | } |
625 | } |
626 | } |
627 | |
628 | void QAbstractItemModelTesterPrivate::testDataGuiRoles(QAbstractItemModelTester *tester) |
629 | { |
630 | const auto model = tester->model(); |
631 | Q_ASSERT(model); |
632 | |
633 | if (!model->hasChildren()) |
634 | return; |
635 | |
636 | static const QMetaType pixmapType = QMetaType(QMetaType::QPixmap); |
637 | if (!pixmapType.isValid()) |
638 | return; |
639 | |
640 | static const QMetaType imageType = QMetaType(QMetaType::QImage); |
641 | static const QMetaType iconType = QMetaType(QMetaType::QIcon); |
642 | static const QMetaType colorType = QMetaType(QMetaType::QColor); |
643 | static const QMetaType brushType = QMetaType(QMetaType::QBrush); |
644 | static const QMetaType fontType = QMetaType(QMetaType::QFont); |
645 | |
646 | QVariant variant = model->data(index: model->index(row: 0, column: 0), role: Qt::DecorationRole); |
647 | if (variant.isValid()) { |
648 | MODELTESTER_VERIFY(variant.canConvert(pixmapType) |
649 | || variant.canConvert(imageType) |
650 | || variant.canConvert(iconType) |
651 | || variant.canConvert(colorType) |
652 | || variant.canConvert(brushType)); |
653 | } |
654 | |
655 | // General Purpose roles that should return a QFont |
656 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::FontRole); |
657 | if (variant.isValid()) |
658 | MODELTESTER_VERIFY(variant.canConvert(fontType)); |
659 | |
660 | // General Purpose roles that should return a QColor or a QBrush |
661 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::BackgroundRole); |
662 | if (variant.isValid()) |
663 | MODELTESTER_VERIFY(variant.canConvert(colorType) || variant.canConvert(brushType)); |
664 | |
665 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::ForegroundRole); |
666 | if (variant.isValid()) |
667 | MODELTESTER_VERIFY(variant.canConvert(colorType) || variant.canConvert(brushType)); |
668 | } |
669 | |
670 | /* |
671 | Tests model's implementation of QAbstractItemModel::data() |
672 | */ |
673 | void QAbstractItemModelTesterPrivate::data() |
674 | { |
675 | if (model->rowCount() == 0 || model->columnCount() == 0) |
676 | return; |
677 | |
678 | MODELTESTER_VERIFY(model->index(0, 0).isValid()); |
679 | |
680 | // General Purpose roles that should return a QString |
681 | QVariant variant; |
682 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::DisplayRole); |
683 | if (variant.isValid()) |
684 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
685 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::ToolTipRole); |
686 | if (variant.isValid()) |
687 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
688 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::StatusTipRole); |
689 | if (variant.isValid()) |
690 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
691 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::WhatsThisRole); |
692 | if (variant.isValid()) |
693 | MODELTESTER_VERIFY(variant.canConvert<QString>()); |
694 | |
695 | // General Purpose roles that should return a QSize |
696 | variant = model->data(index: model->index(row: 0, column: 0), role: Qt::SizeHintRole); |
697 | if (variant.isValid()) |
698 | MODELTESTER_VERIFY(variant.canConvert<QSize>()); |
699 | |
700 | // Check that the alignment is one we know about |
701 | QVariant textAlignmentVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::TextAlignmentRole); |
702 | if (textAlignmentVariant.isValid()) { |
703 | Qt::Alignment alignment = QtPrivate::legacyFlagValueFromModelData<Qt::Alignment>(data: textAlignmentVariant); |
704 | MODELTESTER_COMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); |
705 | } |
706 | |
707 | // Check that the "check state" is one we know about. |
708 | QVariant checkStateVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::CheckStateRole); |
709 | if (checkStateVariant.isValid()) { |
710 | Qt::CheckState state = QtPrivate::legacyEnumValueFromModelData<Qt::CheckState>(data: checkStateVariant); |
711 | MODELTESTER_VERIFY(state == Qt::Unchecked |
712 | || state == Qt::PartiallyChecked |
713 | || state == Qt::Checked); |
714 | } |
715 | |
716 | QVariant sizeHintVariant = model->data(index: model->index(row: 0, column: 0), role: Qt::SizeHintRole); |
717 | if (sizeHintVariant.isValid()) |
718 | MODELTESTER_VERIFY(sizeHintVariant.canConvert<QSize>()); |
719 | |
720 | Q_Q(QAbstractItemModelTester); |
721 | testDataGuiRoles(tester: q); |
722 | } |
723 | |
724 | void QAbstractItemModelTesterPrivate::columnsAboutToBeInserted(const QModelIndex &parent, int start, |
725 | int end) |
726 | { |
727 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
728 | changeInFlight = ChangeInFlight::ColumnsInserted; |
729 | |
730 | qCDebug(lcModelTest) << "columnsAboutToBeInserted" |
731 | << "start="<< start << "end="<< end << "parent="<< parent |
732 | << "parent data="<< model->data(index: parent).toString() |
733 | << "current count of parent="<< model->rowCount(parent) |
734 | << "last before insertion="<< model->index(row: start - 1, column: 0, parent) |
735 | << model->data(index: model->index(row: start - 1, column: 0, parent)); |
736 | } |
737 | |
738 | void QAbstractItemModelTesterPrivate::columnsInserted(const QModelIndex &parent, int first, |
739 | int last) |
740 | { |
741 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsInserted); |
742 | changeInFlight = ChangeInFlight::None; |
743 | |
744 | qCDebug(lcModelTest) << "columnsInserted" |
745 | << "start="<< first << "end="<< last << "parent="<< parent |
746 | << "parent data="<< model->data(index: parent).toString() |
747 | << "current count of parent="<< model->rowCount(parent); |
748 | } |
749 | |
750 | void QAbstractItemModelTesterPrivate::columnsAboutToBeMoved(const QModelIndex &sourceParent, |
751 | int sourceStart, int sourceEnd, |
752 | const QModelIndex &destinationParent, |
753 | int destinationColumn) |
754 | { |
755 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
756 | changeInFlight = ChangeInFlight::ColumnsMoved; |
757 | |
758 | qCDebug(lcModelTest) << "columnsAboutToBeMoved" |
759 | << "sourceStart="<< sourceStart << "sourceEnd="<< sourceEnd |
760 | << "sourceParent="<< sourceParent |
761 | << "sourceParent data="<< model->data(index: sourceParent).toString() |
762 | << "destinationParent="<< destinationParent |
763 | << "destinationColumn="<< destinationColumn; |
764 | } |
765 | |
766 | void QAbstractItemModelTesterPrivate::columnsMoved(const QModelIndex &sourceParent, int sourceStart, |
767 | int sourceEnd, |
768 | const QModelIndex &destinationParent, |
769 | int destinationColumn) |
770 | { |
771 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsMoved); |
772 | changeInFlight = ChangeInFlight::None; |
773 | |
774 | qCDebug(lcModelTest) << "columnsMoved" |
775 | << "sourceStart="<< sourceStart << "sourceEnd="<< sourceEnd |
776 | << "sourceParent="<< sourceParent |
777 | << "sourceParent data="<< model->data(index: sourceParent).toString() |
778 | << "destinationParent="<< destinationParent |
779 | << "destinationColumn="<< destinationColumn; |
780 | } |
781 | |
782 | void QAbstractItemModelTesterPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int first, |
783 | int last) |
784 | { |
785 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
786 | changeInFlight = ChangeInFlight::ColumnsRemoved; |
787 | |
788 | qCDebug(lcModelTest) << "columnsAboutToBeRemoved" |
789 | << "start="<< first << "end="<< last << "parent="<< parent |
790 | << "parent data="<< model->data(index: parent).toString() |
791 | << "current count of parent="<< model->rowCount(parent) |
792 | << "last before removal="<< model->index(row: first - 1, column: 0, parent) |
793 | << model->data(index: model->index(row: first - 1, column: 0, parent)); |
794 | } |
795 | |
796 | void QAbstractItemModelTesterPrivate::columnsRemoved(const QModelIndex &parent, int first, int last) |
797 | { |
798 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ColumnsRemoved); |
799 | changeInFlight = ChangeInFlight::None; |
800 | |
801 | qCDebug(lcModelTest) << "columnsRemoved" |
802 | << "start="<< first << "end="<< last << "parent="<< parent |
803 | << "parent data="<< model->data(index: parent).toString() |
804 | << "current count of parent="<< model->rowCount(parent); |
805 | } |
806 | |
807 | /* |
808 | Store what is about to be inserted to make sure it actually happens |
809 | |
810 | \sa rowsInserted() |
811 | */ |
812 | void QAbstractItemModelTesterPrivate::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) |
813 | { |
814 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
815 | changeInFlight = ChangeInFlight::RowsInserted; |
816 | |
817 | qCDebug(lcModelTest) << "rowsAboutToBeInserted" |
818 | << "start="<< start << "end="<< end << "parent="<< parent |
819 | << "parent data="<< model->data(index: parent).toString() |
820 | << "current count of parent="<< model->rowCount(parent) |
821 | << "last before insertion="<< model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent)); |
822 | |
823 | Changing c; |
824 | c.parent = parent; |
825 | c.oldSize = model->rowCount(parent); |
826 | c.last = (start - 1 >= 0) ? model->index(row: start - 1, column: 0, parent).data() : QVariant(); |
827 | c.next = (start < c.oldSize) ? model->index(row: start, column: 0, parent).data() : QVariant(); |
828 | insert.push(t: c); |
829 | } |
830 | |
831 | /* |
832 | Confirm that what was said was going to happen actually did |
833 | |
834 | \sa rowsAboutToBeInserted() |
835 | */ |
836 | void QAbstractItemModelTesterPrivate::rowsInserted(const QModelIndex &parent, int start, int end) |
837 | { |
838 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsInserted); |
839 | changeInFlight = ChangeInFlight::None; |
840 | |
841 | qCDebug(lcModelTest) << "rowsInserted" |
842 | << "start="<< start << "end="<< end << "parent="<< parent |
843 | << "parent data="<< model->data(index: parent).toString() |
844 | << "current count of parent="<< model->rowCount(parent); |
845 | |
846 | for (int i = start; i <= end; ++i) { |
847 | qCDebug(lcModelTest) << " itemWasInserted:"<< i |
848 | << model->index(row: i, column: 0, parent).data(); |
849 | } |
850 | |
851 | |
852 | Changing c = insert.pop(); |
853 | MODELTESTER_COMPARE(parent, c.parent); |
854 | |
855 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize + (end - start + 1)); |
856 | if (start - 1 >= 0) |
857 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
858 | |
859 | if (end + 1 < model->rowCount(parent: c.parent)) { |
860 | if (c.next != model->data(index: model->index(row: end + 1, column: 0, parent: c.parent))) { |
861 | qDebug() << start << end; |
862 | for (int i = 0; i < model->rowCount(); ++i) |
863 | qDebug() << model->index(row: i, column: 0).data().toString(); |
864 | qDebug() << c.next << model->data(index: model->index(row: end + 1, column: 0, parent: c.parent)); |
865 | } |
866 | |
867 | MODELTESTER_COMPARE(model->data(model->index(end + 1, 0, c.parent)), c.next); |
868 | } |
869 | } |
870 | |
871 | void QAbstractItemModelTesterPrivate::rowsAboutToBeMoved(const QModelIndex &sourceParent, |
872 | int sourceStart, int sourceEnd, |
873 | const QModelIndex &destinationParent, |
874 | int destinationRow) |
875 | { |
876 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
877 | changeInFlight = ChangeInFlight::RowsMoved; |
878 | |
879 | qCDebug(lcModelTest) << "rowsAboutToBeMoved" |
880 | << "sourceStart="<< sourceStart << "sourceEnd="<< sourceEnd |
881 | << "sourceParent="<< sourceParent |
882 | << "sourceParent data="<< model->data(index: sourceParent).toString() |
883 | << "destinationParent="<< destinationParent |
884 | << "destinationRow="<< destinationRow; |
885 | } |
886 | |
887 | void QAbstractItemModelTesterPrivate::rowsMoved(const QModelIndex &sourceParent, int sourceStart, |
888 | int sourceEnd, const QModelIndex &destinationParent, |
889 | int destinationRow) |
890 | { |
891 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsMoved); |
892 | changeInFlight = ChangeInFlight::None; |
893 | |
894 | qCDebug(lcModelTest) << "rowsMoved" |
895 | << "sourceStart="<< sourceStart << "sourceEnd="<< sourceEnd |
896 | << "sourceParent="<< sourceParent |
897 | << "sourceParent data="<< model->data(index: sourceParent).toString() |
898 | << "destinationParent="<< destinationParent |
899 | << "destinationRow="<< destinationRow; |
900 | } |
901 | |
902 | void QAbstractItemModelTesterPrivate::layoutAboutToBeChanged() |
903 | { |
904 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
905 | changeInFlight = ChangeInFlight::LayoutChanged; |
906 | |
907 | for (int i = 0; i < qBound(min: 0, val: model->rowCount(), max: 100); ++i) |
908 | changing.append(t: QPersistentModelIndex(model->index(row: i, column: 0))); |
909 | } |
910 | |
911 | void QAbstractItemModelTesterPrivate::layoutChanged() |
912 | { |
913 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::LayoutChanged); |
914 | changeInFlight = ChangeInFlight::None; |
915 | |
916 | for (int i = 0; i < changing.size(); ++i) { |
917 | QPersistentModelIndex p = changing[i]; |
918 | MODELTESTER_COMPARE(model->index(p.row(), p.column(), p.parent()), QModelIndex(p)); |
919 | } |
920 | changing.clear(); |
921 | } |
922 | |
923 | void QAbstractItemModelTesterPrivate::modelAboutToBeReset() |
924 | { |
925 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
926 | changeInFlight = ChangeInFlight::ModelReset; |
927 | } |
928 | |
929 | void QAbstractItemModelTesterPrivate::modelReset() |
930 | { |
931 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::ModelReset); |
932 | changeInFlight = ChangeInFlight::None; |
933 | } |
934 | |
935 | /* |
936 | Store what is about to be inserted to make sure it actually happens |
937 | |
938 | \sa rowsRemoved() |
939 | */ |
940 | void QAbstractItemModelTesterPrivate::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
941 | { |
942 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::None); |
943 | changeInFlight = ChangeInFlight::RowsRemoved; |
944 | |
945 | qCDebug(lcModelTest) << "rowsAboutToBeRemoved" |
946 | << "start="<< start << "end="<< end << "parent="<< parent |
947 | << "parent data="<< model->data(index: parent).toString() |
948 | << "current count of parent="<< model->rowCount(parent) |
949 | << "last before removal="<< model->index(row: start - 1, column: 0, parent) << model->data(index: model->index(row: start - 1, column: 0, parent)); |
950 | |
951 | Changing c; |
952 | c.parent = parent; |
953 | c.oldSize = model->rowCount(parent); |
954 | if (start > 0 && model->columnCount(parent) > 0) { |
955 | const QModelIndex startIndex = model->index(row: start - 1, column: 0, parent); |
956 | MODELTESTER_VERIFY(startIndex.isValid()); |
957 | c.last = model->data(index: startIndex); |
958 | } |
959 | if (end < c.oldSize - 1 && model->columnCount(parent) > 0) { |
960 | const QModelIndex endIndex = model->index(row: end + 1, column: 0, parent); |
961 | MODELTESTER_VERIFY(endIndex.isValid()); |
962 | c.next = model->data(index: endIndex); |
963 | } |
964 | |
965 | remove.push(t: c); |
966 | } |
967 | |
968 | /* |
969 | Confirm that what was said was going to happen actually did |
970 | |
971 | \sa rowsAboutToBeRemoved() |
972 | */ |
973 | void QAbstractItemModelTesterPrivate::rowsRemoved(const QModelIndex &parent, int start, int end) |
974 | { |
975 | MODELTESTER_COMPARE(changeInFlight, ChangeInFlight::RowsRemoved); |
976 | changeInFlight = ChangeInFlight::None; |
977 | |
978 | qCDebug(lcModelTest) << "rowsRemoved" |
979 | << "start="<< start << "end="<< end << "parent="<< parent |
980 | << "parent data="<< model->data(index: parent).toString() |
981 | << "current count of parent="<< model->rowCount(parent); |
982 | |
983 | Changing c = remove.pop(); |
984 | MODELTESTER_COMPARE(parent, c.parent); |
985 | MODELTESTER_COMPARE(model->rowCount(parent), c.oldSize - (end - start + 1)); |
986 | if (start > 0) |
987 | MODELTESTER_COMPARE(model->data(model->index(start - 1, 0, c.parent)), c.last); |
988 | if (end < c.oldSize - 1) |
989 | MODELTESTER_COMPARE(model->data(model->index(start, 0, c.parent)), c.next); |
990 | } |
991 | |
992 | void QAbstractItemModelTesterPrivate::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
993 | { |
994 | MODELTESTER_VERIFY(topLeft.isValid()); |
995 | MODELTESTER_VERIFY(bottomRight.isValid()); |
996 | QModelIndex commonParent = bottomRight.parent(); |
997 | MODELTESTER_COMPARE(topLeft.parent(), commonParent); |
998 | MODELTESTER_VERIFY(topLeft.row() <= bottomRight.row()); |
999 | MODELTESTER_VERIFY(topLeft.column() <= bottomRight.column()); |
1000 | int rowCount = model->rowCount(parent: commonParent); |
1001 | int columnCount = model->columnCount(parent: commonParent); |
1002 | MODELTESTER_VERIFY(bottomRight.row() < rowCount); |
1003 | MODELTESTER_VERIFY(bottomRight.column() < columnCount); |
1004 | } |
1005 | |
1006 | void QAbstractItemModelTesterPrivate::headerDataChanged(Qt::Orientation orientation, int start, int end) |
1007 | { |
1008 | MODELTESTER_VERIFY(start >= 0); |
1009 | MODELTESTER_VERIFY(end >= 0); |
1010 | MODELTESTER_VERIFY(start <= end); |
1011 | int itemCount = orientation == Qt::Vertical ? model->rowCount() : model->columnCount(); |
1012 | MODELTESTER_VERIFY(start < itemCount); |
1013 | MODELTESTER_VERIFY(end < itemCount); |
1014 | } |
1015 | |
1016 | bool QAbstractItemModelTesterPrivate::verify(bool statement, |
1017 | const char *statementStr, const char *description, |
1018 | const char *file, int line) |
1019 | { |
1020 | static const char formatString[] = "FAIL! %s (%s) returned FALSE (%s:%d)"; |
1021 | |
1022 | switch (failureReportingMode) { |
1023 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
1024 | return QTest::qVerify(statement, statementStr, description, file, line); |
1025 | break; |
1026 | |
1027 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
1028 | if (!statement) |
1029 | qCWarning(lcModelTest, formatString, statementStr, description, file, line); |
1030 | break; |
1031 | |
1032 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
1033 | if (!statement) |
1034 | qFatal(msg: formatString, statementStr, description, file, line); |
1035 | break; |
1036 | } |
1037 | |
1038 | return statement; |
1039 | } |
1040 | |
1041 | |
1042 | template<typename T1, typename T2> |
1043 | bool QAbstractItemModelTesterPrivate::compare(const T1 &t1, const T2 &t2, |
1044 | const char *actual, const char *expected, |
1045 | const char *file, int line) |
1046 | { |
1047 | const bool result = static_cast<bool>(t1 == t2); |
1048 | |
1049 | static const char formatString[] = "FAIL! Compared values are not the same:\n Actual (%s) %s\n Expected (%s) %s\n (%s:%d)"; |
1050 | |
1051 | switch (failureReportingMode) { |
1052 | case QAbstractItemModelTester::FailureReportingMode::QtTest: |
1053 | return QTest::qCompare(t1, t2, actual, expected, file, line); |
1054 | break; |
1055 | |
1056 | case QAbstractItemModelTester::FailureReportingMode::Warning: |
1057 | if (!result) { |
1058 | auto t1string = QTest::toString(t1); |
1059 | auto t2string = QTest::toString(t2); |
1060 | qCWarning(lcModelTest, formatString, actual, t1string ? t1string : "(nullptr)", |
1061 | expected, t2string ? t2string : "(nullptr)", |
1062 | file, line); |
1063 | delete [] t1string; |
1064 | delete [] t2string; |
1065 | } |
1066 | break; |
1067 | |
1068 | case QAbstractItemModelTester::FailureReportingMode::Fatal: |
1069 | if (!result) { |
1070 | auto t1string = QTest::toString(t1); |
1071 | auto t2string = QTest::toString(t2); |
1072 | qFatal(formatString, actual, t1string ? t1string : "(nullptr)", |
1073 | expected, t2string ? t2string : "(nullptr)", |
1074 | file, line); |
1075 | delete [] t1string; |
1076 | delete [] t2string; |
1077 | } |
1078 | break; |
1079 | } |
1080 | |
1081 | return result; |
1082 | } |
1083 | |
1084 | |
1085 | QT_END_NAMESPACE |
1086 | |
1087 | #include "moc_qabstractitemmodeltester.cpp" |
1088 |
Definitions
- lcModelTest
- QAbstractItemModelTesterPrivate
- Changing
- ChangeInFlight
- QAbstractItemModelTester
- QAbstractItemModelTester
- model
- failureReportingMode
- setUseFetchMore
- verify
- QAbstractItemModelTesterPrivate
- runAllTests
- nonDestructiveBasicTest
- rowAndColumnCount
- hasIndex
- index
- parent
- checkChildren
- testDataGuiRoles
- data
- columnsAboutToBeInserted
- columnsInserted
- columnsAboutToBeMoved
- columnsMoved
- columnsAboutToBeRemoved
- columnsRemoved
- rowsAboutToBeInserted
- rowsInserted
- rowsAboutToBeMoved
- rowsMoved
- layoutAboutToBeChanged
- layoutChanged
- modelAboutToBeReset
- modelReset
- rowsAboutToBeRemoved
- rowsRemoved
- dataChanged
- headerDataChanged
- verify
Learn to use CMake with our Intro Training
Find out more