1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Ford Motor Company |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtRemoteObjects module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "modeltest.h" |
30 | #include "../shared/model_utilities.h" |
31 | |
32 | #include <QtTest/QtTest> |
33 | #include <QMetaType> |
34 | #include <QRemoteObjectReplica> |
35 | #include <QRemoteObjectNode> |
36 | #include <QAbstractItemModelReplica> |
37 | #include <QStandardItemModel> |
38 | #include <QSortFilterProxyModel> |
39 | #include <QEventLoop> |
40 | #include <QRandomGenerator> |
41 | |
42 | namespace { |
43 | |
44 | QList<QStandardItem*> createInsertionChildren(int num, const QString& name, const QColor &background) |
45 | { |
46 | QList<QStandardItem*> children; |
47 | for (int i = 0; i < num; ++i) { |
48 | QScopedPointer<QStandardItem> item(new QStandardItem(QStringLiteral("%1 %2" ).arg(a: name).arg(a: i+1))); |
49 | item->setBackground(background); |
50 | children.append(t: item.take()); |
51 | } |
52 | return children; |
53 | } |
54 | |
55 | class RowsWatcher : public WaitHelper |
56 | { |
57 | public: |
58 | RowsWatcher(const QAbstractItemModel *model, int expectedRowsCount) |
59 | : WaitHelper(), m_model(model), m_expectedRowsCount(expectedRowsCount) |
60 | { |
61 | connect(sender: m_model, signal: &QAbstractItemModel::rowsInserted, context: this, |
62 | slot: [this](const QModelIndex &parent, int first, int last) { |
63 | const auto columnCount = m_model->columnCount(parent); |
64 | for (int row = first; row <= last; ++row) { |
65 | for (int column = 0; column < columnCount; ++column) |
66 | m_changedData.append(t: m_model->index(row, column, parent)); |
67 | } |
68 | onNumRowsChanged(parent, first, last); |
69 | }); |
70 | |
71 | connect(sender: m_model, signal: &QAbstractItemModel::rowsRemoved, receiver: this, slot: &RowsWatcher::onNumRowsChanged); |
72 | } |
73 | |
74 | void onNumRowsChanged(const QModelIndex &parent, int first, int last) |
75 | { |
76 | const auto compare = [=](const RowData &row) { |
77 | return (row.m_start == first && row.m_end == last |
78 | && compareIndices(lhs: row.m_index, rhs: parent)); |
79 | }; |
80 | QVERIFY(std::find_if(m_pendingRows.begin(), m_pendingRows.end(), compare) |
81 | != m_pendingRows.end()); |
82 | |
83 | m_currentRowsCount += last - first + 1; |
84 | if (m_currentRowsCount == m_expectedRowsCount) |
85 | finish(); |
86 | } |
87 | |
88 | void scheduleRowsToWatch(const QModelIndex &index, int start, int end) |
89 | { |
90 | m_pendingRows.push_back(t: RowData(index, start, end)); |
91 | } |
92 | |
93 | QVector<QModelIndex> changedData() const { return m_changedData; } |
94 | |
95 | private: |
96 | struct RowData |
97 | { |
98 | RowData(const QModelIndex &idx = QModelIndex(), int s = -1, int e = -1) |
99 | : m_index(idx), m_start(s), m_end(e) {} |
100 | QModelIndex m_index; |
101 | int m_start; |
102 | int m_end; |
103 | }; |
104 | |
105 | const QAbstractItemModel *m_model; |
106 | QVector<RowData> m_pendingRows; |
107 | QVector<QModelIndex> m_changedData; |
108 | int m_currentRowsCount = 0; |
109 | const int m_expectedRowsCount; |
110 | }; |
111 | |
112 | QTextStream cout(stdout, QIODevice::WriteOnly); |
113 | |
114 | //Keep in case we need detailed debugging in the future... |
115 | #if 0 |
116 | inline QDebug operator <<(QDebug dbg, const InsertedRow &row) |
117 | { |
118 | return dbg.nospace() << "Index(" << row.m_index << ", " << row.m_start << ", " << row.m_end << ")" ; |
119 | } |
120 | |
121 | inline void dumpModel(const QAbstractItemModel *model, const QModelIndex &parent = QModelIndex()) |
122 | { |
123 | int level = 0; |
124 | for (QModelIndex idx = parent; idx.isValid(); idx = model->parent(idx), ++level); |
125 | const QHash<int,QByteArray> roles = model->roleNames(); |
126 | for (int i = 0; i < model->rowCount(parent); ++i) { |
127 | for (int j = 0; j < model->columnCount(parent); ++j) { |
128 | const QModelIndex index = model->index(i, j); |
129 | Q_ASSERT(index.isValid()); |
130 | QString s; |
131 | s.fill(QChar(' '), level*2); |
132 | cout << qPrintable(s); |
133 | cout << QString(QLatin1String("%1:%2" )).arg(i).arg(j); |
134 | for (auto it = roles.cbegin(), end = roles.cend(); it != end; ++it) { |
135 | const QVariant v = model->data(index, it.key()); |
136 | if (!v.isValid()) continue; |
137 | QString t; |
138 | QDebug(&t) << v; |
139 | cout << " " << QString::fromUtf8(it.value()) << "=" << t.trimmed(); |
140 | } |
141 | |
142 | { |
143 | QString t; |
144 | QDebug(&t) << model->flags(index); |
145 | cout << " flags=" << t; |
146 | } |
147 | |
148 | cout << "\n" ; |
149 | cout.flush(); |
150 | dumpModel(model, index); // recursive |
151 | } |
152 | } |
153 | } |
154 | #endif |
155 | |
156 | void compareData(const QAbstractItemModel *sourceModel, const QAbstractItemModelReplica *replica) |
157 | { |
158 | QVERIFY(sourceModel); |
159 | QVERIFY(replica); |
160 | |
161 | QCOMPARE(replica->rowCount(), sourceModel->rowCount()); |
162 | QCOMPARE(replica->columnCount(), sourceModel->columnCount()); |
163 | QCOMPARE(replica->roleNames(), sourceModel->roleNames()); |
164 | |
165 | for (int i = 0; i < sourceModel->rowCount(); ++i) { |
166 | for (int j = 0; j < sourceModel->columnCount(); ++j) { |
167 | const auto roles = replica->availableRoles(); |
168 | for (int role : roles) { |
169 | QCOMPARE(replica->index(i, j).data(role), sourceModel->index(i, j).data(role)); |
170 | } |
171 | } |
172 | } |
173 | } |
174 | |
175 | void compareIndex(const QModelIndex &sourceIndex, const QModelIndex &replicaIndex, |
176 | const QVector<int> &roles) |
177 | { |
178 | QVERIFY(sourceIndex.isValid()); |
179 | QVERIFY(replicaIndex.isValid()); |
180 | for (int role : roles) { |
181 | QCOMPARE(replicaIndex.data(role), sourceIndex.data(role)); |
182 | } |
183 | const QAbstractItemModel *sourceModel = sourceIndex.model(); |
184 | const QAbstractItemModel *replicaModel = replicaIndex.model(); |
185 | const int sourceRowCount = sourceModel->rowCount(parent: sourceIndex); |
186 | const int replicaRowCount = replicaModel->rowCount(parent: replicaIndex); |
187 | QCOMPARE(replicaRowCount, sourceRowCount); |
188 | const int sourceColumnCount = sourceModel->columnCount(parent: sourceIndex); |
189 | const int replicaColumnCount = replicaModel->columnCount(parent: replicaIndex); |
190 | // only test the column count if the row count is larger than zero, because we |
191 | // assume the column count is constant over a tree model and it doesn't make a |
192 | // difference in the view. |
193 | if (sourceRowCount) |
194 | QCOMPARE(replicaColumnCount, sourceColumnCount); |
195 | for (int i = 0; i < sourceRowCount; ++i) { |
196 | for (int j = 0; j < sourceColumnCount; ++j) { |
197 | auto sourceChild = sourceModel->index(row: i, column: j, parent: sourceIndex); |
198 | auto replicaChild = replicaModel->index(row: i, column: j, parent: replicaIndex); |
199 | compareIndex(sourceIndex: sourceChild, replicaIndex: replicaChild, roles); |
200 | |
201 | } |
202 | } |
203 | } |
204 | |
205 | void compareTreeData(const QAbstractItemModel *sourceModel, const QAbstractItemModelReplica *replica) |
206 | { |
207 | QVERIFY(sourceModel); |
208 | QVERIFY(replica); |
209 | |
210 | QCOMPARE(replica->rowCount(), sourceModel->rowCount()); |
211 | QCOMPARE(replica->columnCount(), sourceModel->columnCount()); |
212 | |
213 | for (int i = 0; i < sourceModel->rowCount(); ++i) { |
214 | for (int j = 0; j < sourceModel->columnCount(); ++j) { |
215 | const QModelIndex replicaIndex = replica->index(row: i, column: j); |
216 | const QModelIndex sourceIndex = sourceModel->index(row: i, column: j); |
217 | compareIndex(sourceIndex, replicaIndex, roles: replica->availableRoles()); |
218 | } |
219 | } |
220 | } |
221 | |
222 | void compareTreeData(const QAbstractItemModel *sourceModel, const QAbstractItemModel *replica, const QVector<int> &roles) |
223 | { |
224 | QVERIFY(sourceModel); |
225 | QVERIFY(replica); |
226 | |
227 | QCOMPARE(replica->rowCount(), sourceModel->rowCount()); |
228 | QCOMPARE(replica->columnCount(), sourceModel->columnCount()); |
229 | |
230 | for (int i = 0; i < sourceModel->rowCount(); ++i) { |
231 | for (int j = 0; j < sourceModel->columnCount(); ++j) { |
232 | const QModelIndex replicaIndex = replica->index(row: i, column: j); |
233 | const QModelIndex sourceIndex = sourceModel->index(row: i, column: j); |
234 | compareIndex(sourceIndex, replicaIndex, roles); |
235 | } |
236 | } |
237 | } |
238 | |
239 | void compareFlags(const QAbstractItemModel *sourceModel, const QAbstractItemModelReplica *replica) |
240 | { |
241 | QVERIFY(sourceModel); |
242 | QVERIFY(replica); |
243 | |
244 | QCOMPARE(replica->rowCount(), sourceModel->rowCount()); |
245 | QCOMPARE(replica->columnCount(), sourceModel->columnCount()); |
246 | |
247 | for (int i = 0; i < sourceModel->rowCount(); ++i) { |
248 | for (int j = 0; j < sourceModel->columnCount(); ++j) { |
249 | if (replica->index(row: i, column: j).flags() != sourceModel->index(row: i, column: j).flags()) |
250 | qWarning() << sourceModel->index(row: i, column: j).flags() << replica->index(row: i, column: j).flags() << i << j; |
251 | QCOMPARE(replica->index(i, j).flags(), sourceModel->index(i, j).flags()); |
252 | } |
253 | } |
254 | } |
255 | |
256 | // class to test cutom role names |
257 | class RolenamesListModel : public QAbstractListModel |
258 | { |
259 | public: |
260 | explicit RolenamesListModel(QObject *parent = 0) : QAbstractListModel(parent) { } |
261 | int rowCount(const QModelIndex &) const override{return m_list.length(); } |
262 | QVariant data(const QModelIndex &index, int role) const override |
263 | { |
264 | if (role == Qt::UserRole) |
265 | return m_list.at(i: index.row()).second; |
266 | else if (role == Qt::UserRole+1) |
267 | return m_list.at(i: index.row()).first; |
268 | else |
269 | return QVariant(); |
270 | } |
271 | QHash<int, QByteArray> roleNames() const override |
272 | { |
273 | QHash<int, QByteArray> roles; |
274 | roles[Qt::UserRole] = "name" ; |
275 | roles[Qt::UserRole+1] = "pid" ; |
276 | return roles; |
277 | } |
278 | void addPair(const QVariant pid,const QVariant name) { |
279 | m_list.append(t: qMakePair(x: pid, y: name)); |
280 | } |
281 | void clearList() { |
282 | m_list.clear(); |
283 | } |
284 | private: |
285 | QVector<QPair<QVariant, QVariant> > m_list; |
286 | }; |
287 | |
288 | QList<QStandardItem*> addChild(int numChilds, int nestingLevel) |
289 | { |
290 | QList<QStandardItem*> result; |
291 | if (nestingLevel == 0) |
292 | return result; |
293 | for (int i = 0; i < numChilds; ++i) { |
294 | QStandardItem *child = new QStandardItem(QStringLiteral("Child num %1, nesting Level %2" ).arg(a: i+1).arg(a: nestingLevel)); |
295 | if (i == 0) { |
296 | QList<QStandardItem*> res = addChild(numChilds, nestingLevel: nestingLevel -1); |
297 | if (res.size() > 0) |
298 | child->appendRow(aitems: res); |
299 | } |
300 | result.push_back(t: child); |
301 | } |
302 | return result; |
303 | } |
304 | |
305 | int getRandomNumber(int min, int max) |
306 | { |
307 | int res = QRandomGenerator::global()->generate() & INT_MAX; |
308 | const int diff = (max - min); |
309 | res = res % diff; |
310 | res += min; |
311 | return res; |
312 | } |
313 | |
314 | class FetchData : public WaitHelper |
315 | { |
316 | public: |
317 | FetchData(const QAbstractItemModelReplica *replica) : WaitHelper(), m_replica(replica) |
318 | { |
319 | if (!m_replica->isInitialized()) { |
320 | QEventLoop l; |
321 | connect(sender: m_replica, signal: &QAbstractItemModelReplica::initialized, receiver: &l, slot: &QEventLoop::quit); |
322 | l.exec(); |
323 | } |
324 | |
325 | connect(sender: m_replica, signal: &QAbstractItemModelReplica::dataChanged, receiver: this, slot: &FetchData::dataChanged); |
326 | connect(sender: m_replica, signal: &QAbstractItemModelReplica::rowsInserted, receiver: this, slot: &FetchData::rowsInserted); |
327 | } |
328 | |
329 | bool fetchAndWait(int timeout = 15000) |
330 | { |
331 | addAll(); |
332 | fetch(); |
333 | return wait(timeout); |
334 | } |
335 | |
336 | private: |
337 | const QAbstractItemModelReplica *m_replica; |
338 | QHash<QPersistentModelIndex, QVector<int>> m_pending; |
339 | QSet<QPersistentModelIndex> m_waitForInsertion; |
340 | |
341 | void addData(const QModelIndex &index, const QVector<int> &roles) |
342 | { |
343 | for (int role : roles) { |
344 | const bool cached = m_replica->hasData(index, role); |
345 | if (cached) |
346 | continue; |
347 | if (!m_pending.contains(key: index)) |
348 | m_pending[index] = QVector<int>() << role; |
349 | else { |
350 | if (!m_pending[index].contains(t: role)) |
351 | m_pending[index].append(t: role); |
352 | } |
353 | } |
354 | } |
355 | |
356 | void addIndex(const QModelIndex &parent, const QVector<int> &roles) |
357 | { |
358 | if (parent.isValid()) |
359 | addData(index: parent, roles); |
360 | for (int i = 0; i < m_replica->rowCount(parent); ++i) { |
361 | for (int j = 0; j < m_replica->columnCount(parent); ++j) { |
362 | const QModelIndex index = m_replica->index(row: i, column: j, parent); |
363 | Q_ASSERT(index.isValid()); |
364 | addIndex(parent: index, roles); |
365 | } |
366 | } |
367 | } |
368 | |
369 | void addAll() |
370 | { |
371 | addIndex(parent: QModelIndex(), roles: m_replica->availableRoles()); |
372 | } |
373 | |
374 | void fetch() |
375 | { |
376 | if (m_pending.isEmpty() && m_waitForInsertion.isEmpty()) { |
377 | finish(); |
378 | return; |
379 | } |
380 | QHash<QPersistentModelIndex, QVector<int> > pending(m_pending); |
381 | pending.detach(); |
382 | QHash<QPersistentModelIndex, QVector<int> >::ConstIterator it(pending.constBegin()), end(pending.constEnd()); |
383 | for (; it != end; ++it) { |
384 | for (int role : it.value()) { |
385 | QVariant v = m_replica->data(index: it.key(), role); |
386 | Q_UNUSED(v); |
387 | } |
388 | } |
389 | } |
390 | |
391 | void rowsInserted(const QModelIndex &parent, int first, int last) |
392 | { |
393 | static QVector<int> rolesV; |
394 | if (rolesV.isEmpty()) |
395 | rolesV << Qt::DisplayRole << Qt::BackgroundRole; |
396 | m_waitForInsertion.remove(value: parent); |
397 | const int columnCount = m_replica->columnCount(parent); |
398 | if (!(m_replica->hasChildren(parent) && columnCount > 0 && m_replica->rowCount(parent) > 0)) |
399 | qWarning() << m_replica->hasChildren(parent) << columnCount << m_replica->rowCount(parent) << parent.data(); |
400 | QVERIFY(m_replica->hasChildren(parent) && columnCount > 0 && m_replica->rowCount(parent) > 0); |
401 | for (int i = first; i <= last; ++i) { |
402 | for (int j = 0; j < columnCount; ++j) { |
403 | const QModelIndex index = m_replica->index(row: i, column: j, parent); |
404 | const int childRowCount = m_replica->rowCount(parent: index); |
405 | |
406 | QVERIFY(index.isValid()); |
407 | if (m_replica->hasChildren(parent: index) && childRowCount == 0) { |
408 | if (index.column() == 0) |
409 | m_waitForInsertion.insert(value: index); |
410 | } |
411 | addIndex(parent: index, roles: rolesV); |
412 | } |
413 | } |
414 | if (m_replica->hasChildren(parent)) |
415 | fetch(); |
416 | } |
417 | |
418 | void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) |
419 | { |
420 | Q_ASSERT(topLeft.isValid()); |
421 | Q_ASSERT(bottomRight.isValid()); |
422 | Q_ASSERT(topLeft.parent() == bottomRight.parent()); |
423 | |
424 | const QModelIndex parent = topLeft.parent(); |
425 | for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { |
426 | for (int j = topLeft.column(); j <= bottomRight.column(); ++j) { |
427 | const QModelIndex index = m_replica->index(row: i, column: j, parent); |
428 | Q_ASSERT(index.isValid()); |
429 | QHash<QPersistentModelIndex, QVector<int> >::Iterator it = m_pending.find(key: index); |
430 | if (it == m_pending.end()) |
431 | continue; |
432 | |
433 | #if 0 |
434 | QVector<int> itroles = it.value(); |
435 | itroles.detach(); |
436 | if (roles.isEmpty()) { |
437 | itroles.clear(); |
438 | } else { |
439 | for (int r : roles) { |
440 | itroles.removeAll(r); |
441 | } |
442 | } |
443 | if (itroles.isEmpty()) { |
444 | m_pending.erase(it); |
445 | } else { |
446 | m_pending[index] = itroles; |
447 | } |
448 | #else |
449 | Q_UNUSED(roles); |
450 | m_pending.erase(it); |
451 | if (m_replica->hasChildren(parent: index)) { |
452 | // ask for the row count to get an update |
453 | const int rowCount = m_replica->rowCount(parent: index); |
454 | for (int i = 0; i < rowCount; ++i) { |
455 | const QModelIndex cIndex = m_replica->index(row: i, column: 0, parent: index); |
456 | Q_ASSERT(cIndex.isValid()); |
457 | addIndex(parent: cIndex, roles: m_replica->availableRoles()); |
458 | } |
459 | if (rowCount) |
460 | fetch(); |
461 | else if (index.column() == 0) |
462 | m_waitForInsertion.insert(value: index); |
463 | } |
464 | #endif |
465 | } |
466 | } |
467 | |
468 | if (m_pending.isEmpty() && m_waitForInsertion.isEmpty()) |
469 | finish(); |
470 | } |
471 | }; |
472 | |
473 | } // namespace |
474 | |
475 | #define _SETUP_TEST_ \ |
476 | QRemoteObjectHost basicServer; \ |
477 | QRemoteObjectNode client; \ |
478 | QRemoteObjectRegistryHost registryServer; \ |
479 | setup_models(basicServer, client, registryServer); |
480 | |
481 | class TestModelView: public QObject |
482 | { |
483 | Q_OBJECT |
484 | |
485 | QStandardItemModel m_sourceModel; |
486 | RolenamesListModel m_listModel; |
487 | |
488 | public: |
489 | void setup_models(QRemoteObjectHost &basicServer, |
490 | QRemoteObjectNode &client, |
491 | QRemoteObjectRegistryHost ®istryServer); |
492 | |
493 | private slots: |
494 | // NB: The tests have side effects on the models used, and need to be run |
495 | // in order (they may depend on previous side effects). |
496 | void initTestCase(); |
497 | |
498 | void testEmptyModel(); |
499 | void testInitialData(); |
500 | void testInitialDataTree(); |
501 | void testHeaderData(); |
502 | void testHeaderDataChange(); |
503 | void testFlags(); |
504 | void testDataChanged(); |
505 | void testDataChangedTree(); |
506 | void testDataInsertion(); |
507 | void testDataInsertionTree(); |
508 | void testSetData(); |
509 | void testSetDataTree(); |
510 | void testDataRemoval(); |
511 | void testDataRemovalTree(); |
512 | void testServerInsertDataTree(); |
513 | |
514 | void testRoleNames(); |
515 | |
516 | void testModelTest(); |
517 | void testSortFilterModel(); |
518 | |
519 | void testSelectionFromReplica(); |
520 | void testSelectionFromSource(); |
521 | void testChildSelection(); |
522 | |
523 | void testCacheData_data(); |
524 | void testCacheData(); |
525 | |
526 | void cleanup(); |
527 | }; |
528 | |
529 | void TestModelView::initTestCase() |
530 | { |
531 | QLoggingCategory::setFilterRules("qt.remoteobjects.warning=false" ); |
532 | |
533 | static const int modelSize = 20; |
534 | |
535 | QHash<int,QByteArray> roleNames; |
536 | roleNames[Qt::DisplayRole] = "text" ; |
537 | roleNames[Qt::BackgroundRole] = "background" ; |
538 | m_sourceModel.setItemRoleNames(roleNames); |
539 | |
540 | QStringList list; |
541 | list.reserve(size: modelSize); |
542 | |
543 | QStringList ; |
544 | hHeaderList << QStringLiteral("First Column with spacing" ) << QStringLiteral("Second Column with spacing" ); |
545 | m_sourceModel.setHorizontalHeaderLabels(hHeaderList); |
546 | |
547 | for (int i = 0; i < modelSize; ++i) { |
548 | QStandardItem *firstItem = new QStandardItem(QStringLiteral("FancyTextNumber %1" ).arg(a: i)); |
549 | QStandardItem *secondItem = new QStandardItem(QStringLiteral("FancyRow2TextNumber %1" ).arg(a: i)); |
550 | if (i % 2 == 0) |
551 | firstItem->setBackground(Qt::red); |
552 | firstItem->appendRow(aitems: addChild(numChilds: 2,nestingLevel: getRandomNumber(min: 1, max: 4))); |
553 | QList<QStandardItem*> row; |
554 | row << firstItem << secondItem; |
555 | m_sourceModel.appendRow(items: row); |
556 | list << QStringLiteral("FancyTextNumber %1" ).arg(a: i); |
557 | } |
558 | |
559 | const int numElements = 1000; |
560 | for (int i = 0; i < numElements; ++i) { |
561 | QString name = QString("Data %1" ).arg(a: i); |
562 | QString pid = QString("%1" ).arg(a: i); |
563 | m_listModel.addPair(pid: name, name: pid); |
564 | } |
565 | } |
566 | |
567 | void TestModelView::setup_models(QRemoteObjectHost &basicServer, QRemoteObjectNode &client, QRemoteObjectRegistryHost ®istryServer) |
568 | { |
569 | static int port = 65211; |
570 | static const QString url = QStringLiteral("tcp://127.0.0.1:%1" ); |
571 | |
572 | // QStandardItem::flags are stored as data with Qt::UserRole - 1 |
573 | static const QVector<int> sourceModelRoles( {Qt::DisplayRole, Qt::BackgroundRole, (Qt::UserRole - 1)} ); |
574 | |
575 | static const QVector<int> listModelRoles( {Qt::UserRole, Qt::UserRole+1} ); |
576 | |
577 | //Setup registry |
578 | //Registry needs to be created first until we get the retry mechanism implemented |
579 | basicServer.setHostUrl(hostAddress: QUrl(url.arg(a: port))); |
580 | registryServer.setRegistryUrl(QUrl(url.arg(a: port+1))); |
581 | basicServer.setRegistryUrl(QUrl(url.arg(a: port+1))); |
582 | basicServer.enableRemoting(model: &m_sourceModel, name: "test" , roles: sourceModelRoles); |
583 | basicServer.enableRemoting(model: &m_listModel, name: "testRoleNames" , roles: listModelRoles); |
584 | client.setRegistryUrl(QUrl(url.arg(a: port+1))); |
585 | port += 2; |
586 | } |
587 | |
588 | #ifdef SLOW_MODELTEST |
589 | #define MODELTEST_WAIT_TIME 25000 |
590 | #else |
591 | #define MODELTEST_WAIT_TIME |
592 | #endif |
593 | |
594 | void TestModelView::testEmptyModel() |
595 | { |
596 | _SETUP_TEST_ |
597 | QVector<int> roles = QVector<int>() << Qt::DisplayRole << Qt::BackgroundRole; |
598 | QStandardItemModel emptyModel; |
599 | basicServer.enableRemoting(model: &emptyModel, name: "emptyModel" , roles); |
600 | |
601 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "emptyModel" )); |
602 | model->setRootCacheSize(1000); |
603 | |
604 | FetchData f(model.data()); |
605 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
606 | |
607 | compareData(sourceModel: &emptyModel, replica: model.data()); |
608 | } |
609 | |
610 | void TestModelView::testInitialData() |
611 | { |
612 | _SETUP_TEST_ |
613 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
614 | |
615 | FetchData f(model.data()); |
616 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
617 | |
618 | compareData(sourceModel: &m_sourceModel, replica: model.data()); |
619 | } |
620 | |
621 | void TestModelView::testInitialDataTree() |
622 | { |
623 | _SETUP_TEST_ |
624 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
625 | |
626 | FetchData f(model.data()); |
627 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
628 | |
629 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data()); |
630 | } |
631 | |
632 | void TestModelView::() |
633 | { |
634 | _SETUP_TEST_ |
635 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
636 | |
637 | FetchData f(model.data()); |
638 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
639 | |
640 | // ask for all Data members first, so we don't have to wait for update signals |
641 | QSignalSpy (model.data(), &QAbstractItemModelReplica::headerDataChanged); |
642 | for (int i = 0; i < m_sourceModel.rowCount(); ++i) |
643 | model->headerData(section: i, orientation: Qt::Vertical, role: Qt::DisplayRole); |
644 | for (int i = 0; i < m_sourceModel.columnCount(); ++i) |
645 | model->headerData(section: i, orientation: Qt::Horizontal, role: Qt::DisplayRole); |
646 | spyHeader.wait(); |
647 | |
648 | for (int i = 0; i < m_sourceModel.rowCount(); ++i) |
649 | QCOMPARE(model->headerData(i, Qt::Vertical, Qt::DisplayRole), m_sourceModel.headerData(i, Qt::Vertical, Qt::DisplayRole)); |
650 | for (int i = 0; i < m_sourceModel.columnCount(); ++i) |
651 | QCOMPARE(model->headerData(i, Qt::Horizontal, Qt::DisplayRole), m_sourceModel.headerData(i, Qt::Horizontal, Qt::DisplayRole)); |
652 | } |
653 | |
654 | void TestModelView::() |
655 | { |
656 | _SETUP_TEST_ |
657 | QString = QStringLiteral("New header name" ); |
658 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
659 | |
660 | FetchData f(model.data()); |
661 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
662 | QVERIFY(model->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString() != newHeader); |
663 | |
664 | QSignalSpy (model.data(), &QAbstractItemModelReplica::headerDataChanged); |
665 | m_sourceModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: newHeader, role: Qt::DisplayRole); |
666 | spyHeader.wait(); |
667 | QTRY_COMPARE(model->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString(), newHeader); |
668 | |
669 | spyHeader.clear(); |
670 | m_sourceModel.setHeaderData(section: 1, orientation: Qt::Horizontal, value: newHeader, role: Qt::DisplayRole); |
671 | spyHeader.wait(); |
672 | QTRY_COMPARE(model->headerData(1, Qt::Horizontal, Qt::DisplayRole).toString(), newHeader); |
673 | |
674 | QString = QStringLiteral("Modified header name" ); |
675 | m_sourceModel.setHeaderData(section: 0, orientation: Qt::Horizontal, value: anotherHeader, role: Qt::DisplayRole); |
676 | spyHeader.wait(); |
677 | |
678 | QTRY_COMPARE(model->headerData(0, Qt::Horizontal, Qt::DisplayRole).toString(), anotherHeader); |
679 | QCOMPARE(model->headerData(1, Qt::Horizontal, Qt::DisplayRole).toString(), newHeader); |
680 | } |
681 | |
682 | void TestModelView::testDataChangedTree() |
683 | { |
684 | _SETUP_TEST_ |
685 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
686 | |
687 | FetchData f(model.data()); |
688 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
689 | |
690 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data()); |
691 | QSignalSpy dataChangedSpy(model.data(), &QAbstractItemModelReplica::dataChanged); |
692 | QSet<int> expected; |
693 | for (int i = 10; i < 20; ++i) { |
694 | const QModelIndex parent = m_sourceModel.index(row: i,column: 0); |
695 | const int rowCount = m_sourceModel.rowCount(parent); |
696 | const int colCount = m_sourceModel.columnCount(parent); |
697 | for (int row = 0; row < rowCount; ++row) { |
698 | for (int col = 0; col < colCount; ++col) { |
699 | if (col % 2 == 0) |
700 | m_sourceModel.setData(index: m_sourceModel.index(row, column: col, parent), value: QColor(Qt::gray), role: Qt::BackgroundRole); |
701 | else |
702 | m_sourceModel.setData(index: m_sourceModel.index(row, column: col, parent), value: QColor(Qt::cyan), role: Qt::BackgroundRole); |
703 | } |
704 | } |
705 | m_sourceModel.setData(index: m_sourceModel.index(row: i, column: 1), value: QColor(Qt::magenta), role: Qt::BackgroundRole); |
706 | expected << i; |
707 | } |
708 | |
709 | bool signalsReceived = false; |
710 | int runs = 0; |
711 | const int maxRuns = 10; |
712 | while (runs < maxRuns) { |
713 | if (dataChangedSpy.wait() &&!dataChangedSpy.isEmpty()) { |
714 | signalsReceived = true; |
715 | for (auto args : dataChangedSpy) { |
716 | int row = args.at(i: 1).value<QModelIndex>().row(); |
717 | if (row && expected.contains(value: row)) |
718 | expected.remove(value: row); |
719 | } |
720 | if (expected.size() == 0) |
721 | break; |
722 | } |
723 | ++runs; |
724 | } |
725 | QVERIFY(signalsReceived); |
726 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data()); |
727 | } |
728 | |
729 | void TestModelView::testFlags() |
730 | { |
731 | _SETUP_TEST_ |
732 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
733 | |
734 | FetchData f(model.data()); |
735 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
736 | |
737 | QSignalSpy dataChangedSpy(model.data(), &QAbstractItemModelReplica::dataChanged); |
738 | for (int i = 10; i < 20; ++i) { |
739 | QStandardItem* firstItem = m_sourceModel.item(row: i, column: 0); |
740 | QStandardItem* secondItem = m_sourceModel.item(row: i, column: 1); |
741 | firstItem->setFlags(firstItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsAutoTristate); |
742 | secondItem->setFlags(firstItem->flags() | Qt::ItemIsEnabled); |
743 | } |
744 | bool signalsReceived = false; |
745 | while (dataChangedSpy.wait()) { |
746 | signalsReceived = true; |
747 | if (dataChangedSpy.takeLast().at(i: 1).value<QModelIndex>().row() == 19) |
748 | break; |
749 | } |
750 | QVERIFY(signalsReceived); |
751 | compareFlags(sourceModel: &m_sourceModel, replica: model.data()); |
752 | } |
753 | |
754 | void TestModelView::testDataChanged() |
755 | { |
756 | _SETUP_TEST_ |
757 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
758 | |
759 | FetchData f(model.data()); |
760 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
761 | |
762 | QSignalSpy dataChangedSpy(model.data(), SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>))); |
763 | for (int i = 10; i < 20; ++i) |
764 | m_sourceModel.setData(index: m_sourceModel.index(row: i, column: 1), value: QColor(Qt::blue), role: Qt::BackgroundRole); |
765 | |
766 | bool signalsReceived = false; |
767 | while (dataChangedSpy.wait()) { |
768 | signalsReceived = true; |
769 | if (dataChangedSpy.takeLast().at(i: 1).value<QModelIndex>().row() == 19) |
770 | break; |
771 | } |
772 | QVERIFY(signalsReceived); |
773 | compareData(sourceModel: &m_sourceModel, replica: model.data()); |
774 | } |
775 | |
776 | void TestModelView::testDataInsertion() |
777 | { |
778 | _SETUP_TEST_ |
779 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
780 | |
781 | FetchData f(model.data()); |
782 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
783 | |
784 | const int insertedRowsCount = 9; |
785 | RowsWatcher watcher(model.data(), insertedRowsCount); |
786 | QVector<QModelIndex> pending; |
787 | |
788 | m_sourceModel.insertRows(row: 2, count: insertedRowsCount); |
789 | |
790 | watcher.scheduleRowsToWatch(index: QModelIndex(), start: 2, end: 2 + insertedRowsCount - 1); |
791 | QVERIFY(watcher.wait()); |
792 | QCOMPARE(m_sourceModel.rowCount(), model->rowCount()); |
793 | |
794 | pending.append(l: watcher.changedData()); |
795 | |
796 | // change one row to check for inconsistencies |
797 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 1), value: QColor(Qt::green), role: Qt::BackgroundRole); |
798 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 1), value: QLatin1String("foo" ), role: Qt::DisplayRole); |
799 | pending.append(t: model->index(row: 0, column: 1)); |
800 | WaitForDataChanged w(model.data(), pending); |
801 | |
802 | QVERIFY(w.wait()); |
803 | compareData(sourceModel: &m_sourceModel, replica: model.data()); |
804 | } |
805 | |
806 | void TestModelView::testDataInsertionTree() |
807 | { |
808 | _SETUP_TEST_ |
809 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
810 | |
811 | FetchData f(model.data()); |
812 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
813 | |
814 | const int insertedRowsCount = 9; |
815 | const int insertedChildRowsCount = 4; |
816 | RowsWatcher watcher(model.data(), insertedRowsCount + insertedChildRowsCount); |
817 | |
818 | QVector<QModelIndex> pending; |
819 | |
820 | for (int i = 0; i < insertedRowsCount; ++i) { |
821 | watcher.scheduleRowsToWatch(index: QModelIndex(), start: 2 + i, end: 2 + i); |
822 | m_sourceModel.insertRow(row: 2 + i, items: createInsertionChildren(num: 2, QStringLiteral("insertedintree" ), background: Qt::darkRed)); |
823 | const QModelIndex childIndex = m_sourceModel.index(row: 2 + i, column: 0); |
824 | const QModelIndex childIndex2 = m_sourceModel.index(row: 2 + i, column: 1); |
825 | pending.append(t: childIndex); |
826 | pending.append(t: childIndex2); |
827 | } |
828 | const QModelIndex parent = m_sourceModel.index(row: 10, column: 0); |
829 | QStandardItem* parentItem = m_sourceModel.item(row: 10, column: 0); |
830 | for (int i = 0; i < insertedChildRowsCount; ++i) { |
831 | watcher.scheduleRowsToWatch(index: parent, start: i, end: i); |
832 | parentItem->insertRow(row: i, items: createInsertionChildren(num: 2, QStringLiteral("insertedintreedeep" ), background: Qt::darkCyan)); |
833 | const QModelIndex childIndex = m_sourceModel.index(row: 0, column: 0, parent); |
834 | const QModelIndex childIndex2 = m_sourceModel.index(row: 0, column: 1, parent); |
835 | Q_ASSERT(childIndex.isValid()); |
836 | Q_ASSERT(childIndex2.isValid()); |
837 | pending.append(t: childIndex); |
838 | pending.append(t: childIndex2); |
839 | } |
840 | |
841 | QVERIFY(watcher.wait()); |
842 | QCOMPARE(m_sourceModel.rowCount(), model->rowCount()); |
843 | |
844 | pending.append(l: watcher.changedData()); |
845 | |
846 | // change one row to check for inconsistencies |
847 | |
848 | pending << m_sourceModel.index(row: 0, column: 0, parent); |
849 | WaitForDataChanged w(model.data(), pending); |
850 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 0, parent), value: QColor(Qt::green), role: Qt::BackgroundRole); |
851 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 0, parent), value: QLatin1String("foo" ), role: Qt::DisplayRole); |
852 | |
853 | QVERIFY(w.wait()); |
854 | |
855 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data()); |
856 | } |
857 | |
858 | void TestModelView::testDataRemoval() |
859 | { |
860 | _SETUP_TEST_ |
861 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
862 | qputenv(varName: "QTRO_NODES_CACHE_SIZE" , value: "1000" ); |
863 | model->setRootCacheSize(1000); |
864 | FetchData f(model.data()); |
865 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
866 | |
867 | const QPersistentModelIndex parent = m_sourceModel.index(row: 10, column: 0); |
868 | { |
869 | const int removedRowsCount = 3; |
870 | RowsWatcher watcher(model.data(), removedRowsCount); |
871 | m_sourceModel.removeRows(row: 0, count: removedRowsCount, parent); |
872 | watcher.scheduleRowsToWatch(index: parent, start: 0, end: removedRowsCount - 1); |
873 | QVERIFY(watcher.wait()); |
874 | QCOMPARE(m_sourceModel.rowCount(parent), model->rowCount(model->index(10, 0))); |
875 | } |
876 | { |
877 | const int removedRowsCount = 8; |
878 | RowsWatcher watcher(model.data(), removedRowsCount); |
879 | m_sourceModel.removeRows(row: 2, count: removedRowsCount); |
880 | watcher.scheduleRowsToWatch(index: QModelIndex(), start: 2, end: 2 + removedRowsCount - 1); |
881 | QVERIFY(watcher.wait()); |
882 | QCOMPARE(m_sourceModel.rowCount(), model->rowCount()); |
883 | } |
884 | |
885 | // change one row to check for inconsistencies |
886 | |
887 | QVector<QModelIndex> pending; |
888 | pending << m_sourceModel.index(row: 0, column: 0, parent); |
889 | WaitForDataChanged w(model.data(), pending); |
890 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 0, parent), value: QColor(Qt::green), role: Qt::BackgroundRole); |
891 | m_sourceModel.setData(index: m_sourceModel.index(row: 0, column: 0, parent), value: QLatin1String("foo" ), role: Qt::DisplayRole); |
892 | |
893 | QVERIFY(w.wait()); |
894 | |
895 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data()); |
896 | } |
897 | |
898 | void TestModelView::testRoleNames() |
899 | { |
900 | _SETUP_TEST_ |
901 | QScopedPointer<QAbstractItemModelReplica> repModel( client.acquireModel(QStringLiteral("testRoleNames" ))); |
902 | // Set a bigger cache enough to keep all the data otherwise the last test will fail |
903 | repModel->setRootCacheSize(1500); |
904 | FetchData f(repModel.data()); |
905 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
906 | |
907 | // test custom role names |
908 | QCOMPARE(repModel.data()->roleNames(), m_listModel.roleNames()); |
909 | |
910 | // test data associated with custom roles |
911 | compareData(sourceModel: &m_listModel,replica: repModel.data()); |
912 | } |
913 | |
914 | void TestModelView::testDataRemovalTree() |
915 | { |
916 | _SETUP_TEST_ |
917 | m_sourceModel.removeRows(row: 2, count: 4); |
918 | //Maybe some checks here? |
919 | } |
920 | |
921 | void TestModelView::testServerInsertDataTree() |
922 | { |
923 | _SETUP_TEST_ |
924 | QVector<int> roles = QVector<int>() << Qt::DisplayRole << Qt::BackgroundRole; |
925 | QStandardItemModel testTreeModel; |
926 | basicServer.enableRemoting(model: &testTreeModel, name: "testTreeModel" , roles); |
927 | |
928 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "testTreeModel" )); |
929 | |
930 | QTRY_COMPARE(testTreeModel.rowCount(), model->rowCount()); |
931 | |
932 | QVERIFY(testTreeModel.insertRow(0)); |
933 | QVERIFY(testTreeModel.insertColumn(0)); |
934 | auto root = testTreeModel.index(row: 0, column: 0); |
935 | QVERIFY(testTreeModel.setData(root, QLatin1String("Root" ), Qt::DisplayRole)); |
936 | QVERIFY(testTreeModel.setData(root, QColor(Qt::green), Qt::BackgroundRole)); |
937 | QVERIFY(testTreeModel.insertRow(0, root)); |
938 | QVERIFY(testTreeModel.insertColumn(0, root)); |
939 | auto child1 = testTreeModel.index(row: 0, column: 0, parent: root); |
940 | QVERIFY(testTreeModel.setData(child1, QLatin1String("Child1" ), Qt::DisplayRole)); |
941 | QVERIFY(testTreeModel.setData(child1, QColor(Qt::red), Qt::BackgroundRole)); |
942 | |
943 | QTRY_COMPARE(testTreeModel.rowCount(), model->rowCount()); |
944 | |
945 | FetchData f(model.data()); |
946 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
947 | |
948 | compareData(sourceModel: &testTreeModel, replica: model.data()); |
949 | } |
950 | |
951 | void TestModelView::testModelTest() |
952 | { |
953 | _SETUP_TEST_ |
954 | QScopedPointer<QAbstractItemModelReplica> repModel( client.acquireModel(QStringLiteral("test" ))); |
955 | ModelTest test(repModel.data()); |
956 | |
957 | FetchData f(repModel.data()); |
958 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
959 | Q_UNUSED(test); |
960 | } |
961 | |
962 | void TestModelView::testSortFilterModel() |
963 | { |
964 | _SETUP_TEST_ |
965 | QScopedPointer<QAbstractItemModelReplica> repModel( client.acquireModel(QStringLiteral("test" ))); |
966 | |
967 | FetchData f(repModel.data()); |
968 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
969 | |
970 | QSortFilterProxyModel clientSort; |
971 | clientSort.setSourceModel(repModel.data()); |
972 | clientSort.setSortRole(Qt::DisplayRole); |
973 | QSortFilterProxyModel sourceSort; |
974 | sourceSort.setSourceModel(&m_sourceModel); |
975 | sourceSort.setSortRole(Qt::DisplayRole); |
976 | |
977 | compareTreeData(sourceModel: &sourceSort, replica: &clientSort, roles: repModel->availableRoles()); |
978 | } |
979 | |
980 | void TestModelView::testSetData() |
981 | { |
982 | _SETUP_TEST_ |
983 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
984 | |
985 | FetchData f(model.data()); |
986 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
987 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data(), roles: model->availableRoles()); |
988 | |
989 | //fetched and verified initial state, now setData on the client |
990 | QVector<QModelIndex> pending; |
991 | QVector<QModelIndex> pendingReplica; |
992 | for (int row = 0, numRows = model->rowCount(); row < numRows; ++row) { |
993 | for (int column = 0, numColumns = model->columnCount(); column != numColumns; ++column) { |
994 | const QModelIndex index = model->index(row, column); |
995 | const QString newData = QStringLiteral("This entry was changed with setData" ); |
996 | QVERIFY(model->setData(index, newData, Qt::DisplayRole)); |
997 | pending.append(t: m_sourceModel.index(row, column)); |
998 | pendingReplica.append(t: model->index(row, column)); |
999 | } |
1000 | } |
1001 | WaitForDataChanged waiter(&m_sourceModel, pending); |
1002 | QVERIFY(waiter.wait()); |
1003 | WaitForDataChanged waiterReplica(model.data(), pendingReplica); |
1004 | QVERIFY(waiterReplica.wait()); |
1005 | compareData(sourceModel: &m_sourceModel, replica: model.data()); |
1006 | } |
1007 | |
1008 | void TestModelView::testSetDataTree() |
1009 | { |
1010 | _SETUP_TEST_ |
1011 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "test" )); |
1012 | |
1013 | FetchData f(model.data()); |
1014 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
1015 | compareTreeData(sourceModel: &m_sourceModel, replica: model.data(), roles: model->availableRoles()); |
1016 | |
1017 | //fetched and verified initial state, now setData on the client |
1018 | QVector<QModelIndex> pending; |
1019 | QVector<QModelIndex> pendingReplica; |
1020 | |
1021 | QVector<QModelIndex> stack; |
1022 | stack.push_back(t: QModelIndex()); |
1023 | QVector<QModelIndex> sourceStack; |
1024 | sourceStack.push_back(t: QModelIndex()); |
1025 | |
1026 | |
1027 | const QString newData = QStringLiteral("This entry was changed with setData in a tree %1 %2 %3" ); |
1028 | while (!stack.isEmpty()) { |
1029 | const QModelIndex parent = stack.takeLast(); |
1030 | const QModelIndex parentSource = sourceStack.takeLast(); |
1031 | for (int row = 0; row < model->rowCount(parent); ++row) { |
1032 | for (int column = 0; column < model->columnCount(parent); ++column) { |
1033 | const QModelIndex index = model->index(row, column, parent); |
1034 | const QModelIndex indexSource = m_sourceModel.index(row, column, parent: parentSource); |
1035 | QVERIFY(model->setData(index, newData.arg(parent.isValid()).arg(row).arg(column), Qt::DisplayRole)); |
1036 | pending.append(t: indexSource); |
1037 | pendingReplica.append(t: index); |
1038 | if (column == 0) { |
1039 | stack.push_back(t: index); |
1040 | sourceStack.push_back(t: indexSource); |
1041 | } |
1042 | } |
1043 | } |
1044 | } |
1045 | WaitForDataChanged waiter(&m_sourceModel, pending); |
1046 | QVERIFY(waiter.wait()); |
1047 | WaitForDataChanged waiterReplica(model.data(), pendingReplica); |
1048 | QVERIFY(waiterReplica.wait()); |
1049 | compareData(sourceModel: &m_sourceModel, replica: model.data()); |
1050 | } |
1051 | |
1052 | void TestModelView::testSelectionFromReplica() |
1053 | { |
1054 | _SETUP_TEST_ |
1055 | QVector<int> roles = QVector<int>() << Qt::DisplayRole << Qt::BackgroundRole; |
1056 | QStandardItemModel simpleModel; |
1057 | for (int i = 0; i < 4; ++i) |
1058 | simpleModel.appendRow(aitem: new QStandardItem(QString("item %0" ).arg(a: i))); |
1059 | QItemSelectionModel selectionModel(&simpleModel); |
1060 | basicServer.enableRemoting(model: &simpleModel, name: "simpleModelFromReplica" , roles, selectionModel: &selectionModel); |
1061 | |
1062 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "simpleModelFromReplica" )); |
1063 | QItemSelectionModel *replicaSelectionModel = model->selectionModel(); |
1064 | |
1065 | FetchData f(model.data()); |
1066 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
1067 | |
1068 | replicaSelectionModel->setCurrentIndex(index: model->index(row: 1,column: 0), command: QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); |
1069 | QTRY_COMPARE(selectionModel.currentIndex().row(), 1); |
1070 | } |
1071 | |
1072 | void TestModelView::testSelectionFromSource() |
1073 | { |
1074 | _SETUP_TEST_ |
1075 | QVector<int> roles = QVector<int>() << Qt::DisplayRole << Qt::BackgroundRole; |
1076 | QStandardItemModel simpleModel; |
1077 | for (int i = 0; i < 4; ++i) |
1078 | simpleModel.appendRow(aitem: new QStandardItem(QString("item %0" ).arg(a: i))); |
1079 | QItemSelectionModel selectionModel(&simpleModel); |
1080 | basicServer.enableRemoting(model: &simpleModel, name: "simpleModelFromSource" , roles, selectionModel: &selectionModel); |
1081 | |
1082 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "simpleModelFromSource" )); |
1083 | QItemSelectionModel *replicaSelectionModel = model->selectionModel(); |
1084 | |
1085 | FetchData f(model.data()); |
1086 | QVERIFY(f.fetchAndWait(MODELTEST_WAIT_TIME)); |
1087 | |
1088 | selectionModel.setCurrentIndex(index: simpleModel.index(row: 1,column: 0), command: QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); |
1089 | QTRY_COMPARE(replicaSelectionModel->currentIndex().row(), 1); |
1090 | } |
1091 | |
1092 | void TestModelView::testCacheData_data() |
1093 | { |
1094 | QTest::addColumn<QVector<int>>(name: "roles" ); |
1095 | |
1096 | QTest::newRow(dataTag: "empty" ) << QVector<int> {}; |
1097 | QTest::newRow(dataTag: "all" ) << QVector<int> { Qt::UserRole, Qt::UserRole + 1 }; |
1098 | } |
1099 | |
1100 | void TestModelView::testCacheData() |
1101 | { |
1102 | QFETCH(QVector<int>, roles); |
1103 | |
1104 | _SETUP_TEST_ |
1105 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "testRoleNames" , action: QtRemoteObjects::PrefetchData, rolesHint: roles)); |
1106 | model->setRootCacheSize(1000); |
1107 | |
1108 | QEventLoop l; |
1109 | connect(sender: model.data(), signal: &QAbstractItemModelReplica::initialized, receiver: &l, slot: &QEventLoop::quit); |
1110 | l.exec(); |
1111 | |
1112 | compareData(sourceModel: &m_listModel, replica: model.data()); |
1113 | } |
1114 | |
1115 | void TestModelView::testChildSelection() |
1116 | { |
1117 | _SETUP_TEST_ |
1118 | QVector<int> roles = {Qt::DisplayRole, Qt::BackgroundRole}; |
1119 | QStandardItemModel simpleModel; |
1120 | QStandardItem *parentItem = simpleModel.invisibleRootItem(); |
1121 | for (int i = 0; i < 4; ++i) { |
1122 | QStandardItem *item = new QStandardItem(QString("item %0" ).arg(a: i)); |
1123 | parentItem->appendRow(aitem: item); |
1124 | parentItem = item; |
1125 | } |
1126 | QItemSelectionModel selectionModel(&simpleModel); |
1127 | basicServer.enableRemoting(model: &simpleModel, name: "treeModelFromSource" , roles, selectionModel: &selectionModel); |
1128 | |
1129 | QScopedPointer<QAbstractItemModelReplica> model(client.acquireModel(name: "treeModelFromSource" , action: QtRemoteObjects::PrefetchData, rolesHint: roles)); |
1130 | QItemSelectionModel *replicaSelectionModel = model->selectionModel(); |
1131 | |
1132 | QTRY_COMPARE(simpleModel.rowCount(), model->rowCount()); |
1133 | QTRY_COMPARE(model->data(model->index(0, 0)), QVariant(QString("item 0" ))); |
1134 | |
1135 | // select an item not yet "seen" by the replica |
1136 | selectionModel.setCurrentIndex(index: simpleModel.index(row: 0, column: 0, parent: simpleModel.index(row: 0,column: 0)), command: QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current); |
1137 | QTRY_COMPARE(replicaSelectionModel->currentIndex().row(), 0); |
1138 | QVERIFY(replicaSelectionModel->currentIndex().parent().isValid()); |
1139 | } |
1140 | |
1141 | void TestModelView::cleanup() |
1142 | { |
1143 | // wait for delivery of RemoveObject events to the source |
1144 | QTest::qWait(ms: 20); |
1145 | } |
1146 | |
1147 | QTEST_MAIN(TestModelView) |
1148 | |
1149 | #include "tst_modelview.moc" |
1150 | |