1/****************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite 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 <QtTest/qtest.h>
30#include <QtTest/qsignalspy.h>
31#include <QtCore/qregularexpression.h>
32#include <QtCore/qabstractitemmodel.h>
33#include <QtQml/private/qqmlengine_p.h>
34#include <QtQml/qqmlcomponent.h>
35#include <QtQuick/qquickitem.h>
36#include <QtQuick/qquickview.h>
37#include <QtQuick/private/qquicktableview_p.h>
38
39#include "../../shared/util.h"
40
41class tst_QQmlTableModel : public QQmlDataTest
42{
43 Q_OBJECT
44
45public:
46 tst_QQmlTableModel() {}
47
48private slots:
49 void appendRemoveRow();
50 void appendRowToEmptyModel();
51 void clear();
52 void getRow();
53 void insertRow();
54 void moveRow();
55 void setRow();
56 void setDataThroughDelegate();
57 void setRowsImperatively();
58 void setRowsMultipleTimes();
59 void dataAndEditing();
60 void omitTableModelColumnIndex();
61 void complexRow();
62 void appendRowWithDouble();
63};
64
65void tst_QQmlTableModel::appendRemoveRow()
66{
67 QQuickView view(testFileUrl(fileName: "common.qml"));
68 QCOMPARE(view.status(), QQuickView::Ready);
69 view.show();
70 QVERIFY(QTest::qWaitForWindowActive(&view));
71
72 auto *model = view.rootObject()->property(name: "testModel") .value<QAbstractTableModel *>();
73 QVERIFY(model);
74 QCOMPARE(model->rowCount(), 2);
75 QCOMPARE(model->columnCount(), 2);
76
77 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
78 QVERIFY(columnCountSpy.isValid());
79
80 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
81 QVERIFY(rowCountSpy.isValid());
82 int rowCountSignalEmissions = 0;
83
84 const QHash<int, QByteArray> roleNames = model->roleNames();
85 QCOMPARE(roleNames.size(), 1);
86 QVERIFY(roleNames.values().contains("display"));
87 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
88 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
89 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
90 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
91
92 // Call remove() with a negative rowIndex.
93 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*removeRow\\(\\): \"rowIndex\" cannot be negative"));
94 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, -1)));
95 QCOMPARE(model->rowCount(), 2);
96 QCOMPARE(model->columnCount(), 2);
97 QCOMPARE(columnCountSpy.count(), 0);
98 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
99
100 // Call remove() with an rowIndex that is too large.
101 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
102 ".*removeRow\\(\\): \"rowIndex\" 2 is greater than or equal to rowCount\\(\\) of 2"));
103 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 2)));
104 QCOMPARE(model->rowCount(), 2);
105 QCOMPARE(model->columnCount(), 2);
106 QCOMPARE(columnCountSpy.count(), 0);
107 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
108
109 // Call remove() with a valid rowIndex but negative rows.
110 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*removeRow\\(\\): \"rows\" is less than or equal to zero"));
111 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, -1)));
112 QCOMPARE(model->rowCount(), 2);
113 QCOMPARE(model->columnCount(), 2);
114 QCOMPARE(columnCountSpy.count(), 0);
115 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
116
117 // Call remove() with a valid rowIndex but excessive rows.
118 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
119 ".*removeRow\\(\\): \"rows\" 3 exceeds available rowCount\\(\\) of 2 when removing from \"rowIndex\" 0"));
120 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, 3)));
121 QCOMPARE(model->rowCount(), 2);
122 QCOMPARE(model->columnCount(), 2);
123 QCOMPARE(columnCountSpy.count(), 0);
124 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
125
126 // Call remove() without specifying the number of rows to remove; it should remove one row.
127 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0)));
128 QCOMPARE(model->rowCount(), 1);
129 QCOMPARE(model->columnCount(), 2);
130 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
131 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
132 QCOMPARE(columnCountSpy.count(), 0);
133 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
134
135 // Call append() with a row that has an unexpected role; the row should be added and the extra data ignored.
136 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowExtraData"));
137 // Nothing should change.
138 QCOMPARE(model->rowCount(), 2);
139 QCOMPARE(model->columnCount(), 2);
140 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
141 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
142 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
143 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
144 QCOMPARE(columnCountSpy.count(), 0);
145 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
146
147 // Call append() with a row that is an int.
148 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
149 ".*appendRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)"));
150 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid1"));
151 // Nothing should change.
152 QCOMPARE(model->rowCount(), 2);
153 QCOMPARE(model->columnCount(), 2);
154 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
155 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
156 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
157 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
158 QCOMPARE(columnCountSpy.count(), 0);
159 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
160
161 // Call append() with a row with a role of the wrong type.
162 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
163 ".*appendRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead"));
164 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid2"));
165 // Nothing should change.
166 QCOMPARE(model->rowCount(), 2);
167 QCOMPARE(model->columnCount(), 2);
168 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
169 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
170 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
171 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
172 QCOMPARE(columnCountSpy.count(), 0);
173 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
174
175 // Call append() with a row that is an array instead of a simple object.
176 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
177 ".*appendRow\\(\\): row manipulation functions do not support complex rows \\(row index: -1\\)"));
178 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid3"));
179 // Nothing should change.
180 QCOMPARE(model->rowCount(), 2);
181 QCOMPARE(model->columnCount(), 2);
182 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
183 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
184 QCOMPARE(columnCountSpy.count(), 0);
185 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
186
187 // Call append() to insert one row.
188 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40)));
189 QCOMPARE(model->rowCount(), 3);
190 QCOMPARE(model->columnCount(), 2);
191 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
192 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
193 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
194 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
195 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
196 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
197 QCOMPARE(columnCountSpy.count(), 0);
198 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
199
200 // Call remove() and specify rowIndex and rows, removing all remaining rows.
201 QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, 3)));
202 QCOMPARE(model->rowCount(), 0);
203 QCOMPARE(model->columnCount(), 2);
204 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")), QVariant());
205 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant());
206 QCOMPARE(columnCountSpy.count(), 0);
207 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
208}
209
210void tst_QQmlTableModel::appendRowToEmptyModel()
211{
212 QQuickView view(testFileUrl(fileName: "empty.qml"));
213 QCOMPARE(view.status(), QQuickView::Ready);
214 view.show();
215 QVERIFY(QTest::qWaitForWindowActive(&view));
216
217 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
218 QVERIFY(model);
219 QCOMPARE(model->rowCount(), 0);
220 QCOMPARE(model->columnCount(), 2);
221
222 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
223 QVERIFY(columnCountSpy.isValid());
224
225 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
226 QVERIFY(rowCountSpy.isValid());
227
228 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
229 QVERIFY(tableView);
230 QCOMPARE(tableView->rows(), 0);
231 QCOMPARE(tableView->columns(), 2);
232
233 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendJohn"));
234 QCOMPARE(model->rowCount(), 1);
235 QCOMPARE(model->columnCount(), 2);
236 const QHash<int, QByteArray> roleNames = model->roleNames();
237 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
238 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
239 QCOMPARE(columnCountSpy.count(), 0);
240 QCOMPARE(rowCountSpy.count(), 1);
241 QTRY_COMPARE(tableView->rows(), 1);
242 QCOMPARE(tableView->columns(), 2);
243}
244
245void tst_QQmlTableModel::clear()
246{
247 QQuickView view(testFileUrl(fileName: "common.qml"));
248 QCOMPARE(view.status(), QQuickView::Ready);
249 view.show();
250 QVERIFY(QTest::qWaitForWindowActive(&view));
251
252 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
253 QVERIFY(model);
254 QCOMPARE(model->rowCount(), 2);
255 QCOMPARE(model->columnCount(), 2);
256
257 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
258 QVERIFY(columnCountSpy.isValid());
259
260 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
261 QVERIFY(rowCountSpy.isValid());
262
263 const QHash<int, QByteArray> roleNames = model->roleNames();
264 QVERIFY(roleNames.values().contains("display"));
265 QCOMPARE(roleNames.size(), 1);
266
267 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
268 QVERIFY(tableView);
269 QCOMPARE(tableView->rows(), 2);
270 QCOMPARE(tableView->columns(), 2);
271
272 QVERIFY(QMetaObject::invokeMethod(model, "clear"));
273 QCOMPARE(model->rowCount(), 0);
274 QCOMPARE(model->columnCount(), 2);
275 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")), QVariant());
276 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant());
277 QCOMPARE(columnCountSpy.count(), 0);
278 QCOMPARE(rowCountSpy.count(), 1);
279 // Wait until updatePolish() gets called, which is where the size is recalculated.
280 QTRY_COMPARE(tableView->rows(), 0);
281 QCOMPARE(tableView->columns(), 2);
282}
283
284void tst_QQmlTableModel::getRow()
285{
286 QQuickView view(testFileUrl(fileName: "common.qml"));
287 QCOMPARE(view.status(), QQuickView::Ready);
288 view.show();
289 QVERIFY(QTest::qWaitForWindowActive(&view));
290
291 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
292 QVERIFY(model);
293 QCOMPARE(model->rowCount(), 2);
294 QCOMPARE(model->columnCount(), 2);
295
296 // Call get() with a negative row index.
297 QVariant returnValue;
298 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*getRow\\(\\): \"rowIndex\" cannot be negative"));
299 QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, -1)));
300 QVERIFY(!returnValue.isValid());
301
302 // Call get() with a row index that is too large.
303 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
304 ".*getRow\\(\\): \"rowIndex\" 2 is greater than or equal to rowCount\\(\\) of 2"));
305 QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, 2)));
306 QVERIFY(!returnValue.isValid());
307
308 // Call get() with a valid row index.
309 QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, 0)));
310 const QVariantMap rowAsVariantMap = returnValue.toMap();
311 QCOMPARE(rowAsVariantMap.value(QLatin1String("name")).toString(), QLatin1String("John"));
312 QCOMPARE(rowAsVariantMap.value(QLatin1String("age")).toInt(), 22);
313}
314
315void tst_QQmlTableModel::insertRow()
316{
317 QQuickView view(testFileUrl(fileName: "common.qml"));
318 QCOMPARE(view.status(), QQuickView::Ready);
319 view.show();
320 QVERIFY(QTest::qWaitForWindowActive(&view));
321
322 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
323 QVERIFY(model);
324 QCOMPARE(model->rowCount(), 2);
325 QCOMPARE(model->columnCount(), 2);
326
327 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
328 QVERIFY(columnCountSpy.isValid());
329
330 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
331 QVERIFY(rowCountSpy.isValid());
332 int rowCountSignalEmissions = 0;
333
334 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
335 QVERIFY(tableView);
336 QCOMPARE(tableView->rows(), 2);
337 QCOMPARE(tableView->columns(), 2);
338
339 // Try to insert with a negative index.
340 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
341 ".*insertRow\\(\\): \"rowIndex\" cannot be negative"));
342 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow",
343 Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, -1)));
344 QCOMPARE(model->columnCount(), 2);
345 QCOMPARE(model->rowCount(), 2);
346 const QHash<int, QByteArray> roleNames = model->roleNames();
347 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
348 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
349 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
350 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
351 QCOMPARE(columnCountSpy.count(), 0);
352 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
353 QCOMPARE(tableView->rows(), 2);
354 QCOMPARE(tableView->columns(), 2);
355
356 // Try to insert past the last allowed index.
357 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
358 ".*insertRow\\(\\): \"rowIndex\" 3 is greater than rowCount\\(\\) of 2"));
359 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow",
360 Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, 3)));
361 QCOMPARE(model->rowCount(), 2);
362 QCOMPARE(model->columnCount(), 2);
363 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
364 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
365 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
366 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
367 QCOMPARE(columnCountSpy.count(), 0);
368 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
369 QCOMPARE(tableView->rows(), 2);
370 QCOMPARE(tableView->columns(), 2);
371
372 // Call insert() with a row that is an int.
373 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
374 ".*insertRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)"));
375 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid1"));
376 QCOMPARE(model->rowCount(), 2);
377 QCOMPARE(model->columnCount(), 2);
378 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
379 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
380 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
381 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
382 QCOMPARE(columnCountSpy.count(), 0);
383 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
384 QCOMPARE(tableView->rows(), 2);
385 QCOMPARE(tableView->columns(), 2);
386
387 // Try to insert a row with a role of the wrong type.
388 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
389 ".*insertRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead"));
390 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid2"));
391 QCOMPARE(model->rowCount(), 2);
392 QCOMPARE(model->columnCount(), 2);
393 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
394 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
395 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
396 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
397 QCOMPARE(columnCountSpy.count(), 0);
398 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
399 QCOMPARE(tableView->rows(), 2);
400 QCOMPARE(tableView->columns(), 2);
401
402 // Try to insert a row that is an array instead of a simple object.
403 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
404 ".*insertRow\\(\\): row manipulation functions do not support complex rows \\(row index: 0\\)"));
405 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid3"));
406 QCOMPARE(model->rowCount(), 2);
407 QCOMPARE(model->columnCount(), 2);
408 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
409 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
410 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
411 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
412 QCOMPARE(columnCountSpy.count(), 0);
413 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
414 QCOMPARE(tableView->rows(), 2);
415 QCOMPARE(tableView->columns(), 2);
416
417 // Try to insert a row has an unexpected role; the row should be added and the extra data ignored.
418 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowExtraData"));
419 QCOMPARE(model->rowCount(), 3);
420 QCOMPARE(model->columnCount(), 2);
421 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
422 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
423 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
424 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
425 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
426 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
427 QCOMPARE(columnCountSpy.count(), 0);
428 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
429 QTRY_COMPARE(tableView->rows(), 3);
430 QCOMPARE(tableView->columns(), 2);
431
432 // Insert a row at the bottom of the table.
433 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow",
434 Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, 3)));
435 QCOMPARE(model->rowCount(), 4);
436 QCOMPARE(model->columnCount(), 2);
437 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
438 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
439 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
440 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
441 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
442 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
443 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
444 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
445 QCOMPARE(columnCountSpy.count(), 0);
446 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
447 QTRY_COMPARE(tableView->rows(), 4);
448 QCOMPARE(tableView->columns(), 2);
449
450 // Insert a row in the middle of the table.
451 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow",
452 Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30), Q_ARG(QVariant, 2)));
453 QCOMPARE(model->rowCount(), 5);
454 QCOMPARE(model->columnCount(), 2);
455 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
456 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
457 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
458 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
459 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
460 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
461 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
462 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
463 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
464 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
465 QCOMPARE(columnCountSpy.count(), 0);
466 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
467 QTRY_COMPARE(tableView->rows(), 5);
468 QCOMPARE(tableView->columns(), 2);
469}
470
471void tst_QQmlTableModel::moveRow()
472{
473 QQuickView view(testFileUrl(fileName: "common.qml"));
474 QCOMPARE(view.status(), QQuickView::Ready);
475 view.show();
476 QVERIFY(QTest::qWaitForWindowActive(&view));
477
478 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
479 QVERIFY(model);
480 QCOMPARE(model->columnCount(), 2);
481 QCOMPARE(model->rowCount(), 2);
482
483 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
484 QVERIFY(columnCountSpy.isValid());
485
486 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
487 QVERIFY(rowCountSpy.isValid());
488 int rowCountSignalEmissions = 0;
489
490 const QHash<int, QByteArray> roleNames = model->roleNames();
491
492 // Append some rows.
493 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40)));
494 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30)));
495 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Trev")), Q_ARG(QVariant, 48)));
496 QCOMPARE(model->rowCount(), 5);
497 QCOMPARE(model->columnCount(), 2);
498 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
499 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
500 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
501 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
502 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
503 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
504 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
505 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
506 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
507 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
508 rowCountSignalEmissions = 3;
509 QCOMPARE(columnCountSpy.count(), 0);
510 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
511
512 // Try to move with a fromRowIndex that is negative.
513 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*moveRow\\(\\): \"fromRowIndex\" cannot be negative"));
514 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, -1), Q_ARG(int, 1)));
515 // Shouldn't have changed.
516 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
517 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
518 QCOMPARE(columnCountSpy.count(), 0);
519 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
520
521 // Try to move with a fromRowIndex that is too large.
522 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*moveRow\\(\\): \"fromRowIndex\" 5 is greater than or equal to rowCount\\(\\)"));
523 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 5), Q_ARG(int, 1)));
524 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
525 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
526 QCOMPARE(columnCountSpy.count(), 0);
527 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
528
529 // Try to move with a toRowIndex that is negative.
530 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*moveRow\\(\\): \"toRowIndex\" cannot be negative"));
531 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, -1)));
532 // Shouldn't have changed.
533 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
534 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
535 QCOMPARE(columnCountSpy.count(), 0);
536 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
537
538 // Try to move with a toRowIndex that is too large.
539 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*moveRow\\(\\): \"toRowIndex\" 5 is greater than or equal to rowCount\\(\\)"));
540 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 5)));
541 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
542 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
543 QCOMPARE(columnCountSpy.count(), 0);
544 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
545
546 // Move the first row to the end.
547 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 4)));
548 // The counts shouldn't have changed.
549 QCOMPARE(model->rowCount(), 5);
550 QCOMPARE(model->columnCount(), 2);
551 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
552 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
553 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
554 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
555 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
556 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
557 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
558 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
559 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
560 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
561 QCOMPARE(columnCountSpy.count(), 0);
562 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
563
564 // Move it back again.
565 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 4), Q_ARG(int, 0)));
566 QCOMPARE(model->rowCount(), 5);
567 QCOMPARE(model->columnCount(), 2);
568 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
569 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
570 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
571 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
572 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
573 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
574 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
575 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
576 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
577 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
578 QCOMPARE(columnCountSpy.count(), 0);
579 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
580
581 // Move the first row down one by one row.
582 QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 1)));
583 QCOMPARE(model->rowCount(), 5);
584 QCOMPARE(model->columnCount(), 2);
585 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
586 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
587 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
588 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
589 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
590 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
591 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
592 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
593 QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev"));
594 QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48);
595 QCOMPARE(columnCountSpy.count(), 0);
596 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
597}
598
599void tst_QQmlTableModel::setRow()
600{
601 QQuickView view(testFileUrl(fileName: "common.qml"));
602 QCOMPARE(view.status(), QQuickView::Ready);
603 view.show();
604 QVERIFY(QTest::qWaitForWindowActive(&view));
605
606 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
607 QVERIFY(model);
608 QCOMPARE(model->columnCount(), 2);
609 QCOMPARE(model->rowCount(), 2);
610
611 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
612 QVERIFY(columnCountSpy.isValid());
613
614 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
615 QVERIFY(rowCountSpy.isValid());
616 int rowCountSignalEmissions = 0;
617
618 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
619 QVERIFY(tableView);
620 QCOMPARE(tableView->rows(), 2);
621 QCOMPARE(tableView->columns(), 2);
622
623 // Try to set with a negative index.
624 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
625 ".*setRow\\(\\): \"rowIndex\" cannot be negative"));
626 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow",
627 Q_ARG(QVariant, -1), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40)));
628 QCOMPARE(model->rowCount(), 2);
629 QCOMPARE(model->columnCount(), 2);
630 const QHash<int, QByteArray> roleNames = model->roleNames();
631 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
632 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
633 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
634 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
635 QCOMPARE(columnCountSpy.count(), 0);
636 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
637 QCOMPARE(tableView->rows(), 2);
638 QCOMPARE(tableView->columns(), 2);
639
640 // Try to set at an index past the last allowed index.
641 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
642 ".*setRow\\(\\): \"rowIndex\" 3 is greater than rowCount\\(\\) of 2"));
643 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow",
644 Q_ARG(QVariant, 3), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40)));
645 QCOMPARE(model->rowCount(), 2);
646 QCOMPARE(model->columnCount(), 2);
647 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
648 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
649 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
650 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
651 QCOMPARE(columnCountSpy.count(), 0);
652 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
653 QCOMPARE(tableView->rows(), 2);
654 QCOMPARE(tableView->columns(), 2);
655
656 // Try to set a row that has an unexpected role; the row should be set and the extra data ignored.
657 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowExtraData"));
658 QCOMPARE(model->rowCount(), 2);
659 QCOMPARE(model->columnCount(), 2);
660 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
661 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
662 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
663 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
664 QCOMPARE(columnCountSpy.count(), 0);
665 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
666 QCOMPARE(tableView->rows(), 2);
667 QCOMPARE(tableView->columns(), 2);
668
669 // Try to insert a row that is not an array.
670 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
671 ".*setRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)"));
672 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid1"));
673 QCOMPARE(model->rowCount(), 2);
674 QCOMPARE(model->columnCount(), 2);
675 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
676 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
677 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
678 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
679 QCOMPARE(columnCountSpy.count(), 0);
680 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
681 QCOMPARE(tableView->rows(), 2);
682 QCOMPARE(tableView->columns(), 2);
683
684 // Try to insert a row with a role that is of the wrong type.
685 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
686 ".*setRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead"));
687 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid2"));
688 QCOMPARE(model->rowCount(), 2);
689 QCOMPARE(model->columnCount(), 2);
690 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
691 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
692 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
693 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
694 QCOMPARE(columnCountSpy.count(), 0);
695 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
696 QCOMPARE(tableView->rows(), 2);
697 QCOMPARE(tableView->columns(), 2);
698
699 // Try to insert a row that is an array instead of a simple object.
700 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
701 ".*setRow\\(\\): row manipulation functions do not support complex rows \\(row index: 0\\)"));
702 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid3"));
703 QCOMPARE(model->rowCount(), 2);
704 QCOMPARE(model->columnCount(), 2);
705 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo"));
706 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
707 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
708 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
709 QCOMPARE(columnCountSpy.count(), 0);
710 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
711 QCOMPARE(tableView->rows(), 2);
712 QCOMPARE(tableView->columns(), 2);
713
714 // Set the first row.
715 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow",
716 Q_ARG(QVariant, 0), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40)));
717 QCOMPARE(model->rowCount(), 2);
718 QCOMPARE(model->columnCount(), 2);
719 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
720 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
721 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
722 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
723 QCOMPARE(columnCountSpy.count(), 0);
724 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
725 QCOMPARE(tableView->rows(), 2);
726 QCOMPARE(tableView->columns(), 2);
727
728 // Set the last row.
729 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow",
730 Q_ARG(QVariant, 1), Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30)));
731 QCOMPARE(model->rowCount(), 2);
732 QCOMPARE(model->columnCount(), 2);
733 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
734 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
735 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
736 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
737 QCOMPARE(columnCountSpy.count(), 0);
738 QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions);
739 QCOMPARE(tableView->rows(), 2);
740 QCOMPARE(tableView->columns(), 2);
741
742 // Append a row by passing an index that is equal to rowCount().
743 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow",
744 Q_ARG(QVariant, 2), Q_ARG(QVariant, QLatin1String("Wot")), Q_ARG(QVariant, 99)));
745 QCOMPARE(model->rowCount(), 3);
746 QCOMPARE(model->columnCount(), 2);
747 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
748 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40);
749 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy"));
750 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30);
751 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Wot"));
752 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 99);
753 QCOMPARE(columnCountSpy.count(), 0);
754 QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions);
755 QTRY_COMPARE(tableView->rows(), 3);
756 QCOMPARE(tableView->columns(), 2);
757}
758
759void tst_QQmlTableModel::setDataThroughDelegate()
760{
761 QQuickView view(testFileUrl(fileName: "setDataThroughDelegate.qml"));
762 QCOMPARE(view.status(), QQuickView::Ready);
763 view.show();
764 QVERIFY(QTest::qWaitForWindowActive(&view));
765
766 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
767 QVERIFY(model);
768 QCOMPARE(model->rowCount(), 2);
769 QCOMPARE(model->columnCount(), 2);
770
771 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
772 QVERIFY(columnCountSpy.isValid());
773
774 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
775 QVERIFY(rowCountSpy.isValid());
776
777 const QHash<int, QByteArray> roleNames = model->roleNames();
778 QCOMPARE(roleNames.size(), 1);
779 QVERIFY(roleNames.values().contains("display"));
780 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
781 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
782 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
783 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
784 QCOMPARE(columnCountSpy.count(), 0);
785 QCOMPARE(rowCountSpy.count(), 0);
786
787 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modify"));
788 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
789 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
790 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
791 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
792 QCOMPARE(columnCountSpy.count(), 0);
793 QCOMPARE(rowCountSpy.count(), 0);
794
795 // Test setting a role that doesn't exist for a certain column.
796 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modifyInvalidRole"));
797 // Should be unchanged.
798 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
799 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
800 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
801 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
802 QCOMPARE(columnCountSpy.count(), 0);
803 QCOMPARE(rowCountSpy.count(), 0);
804
805 // Test setting a role with a value of the wrong type.
806 // There are two rows, so two delegates respond to the signal, which means we need to ignore two warnings.
807 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*setData\\(\\): failed converting value QVariant\\(QString, \"Whoops\"\\) " \
808 "set at row 0 column 1 with role \"display\" to \"int\""));
809 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(".*setData\\(\\): failed converting value QVariant\\(QString, \"Whoops\"\\) " \
810 "set at row 1 column 1 with role \"display\" to \"int\""));
811 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modifyInvalidType"));
812 // Should be unchanged.
813 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
814 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
815 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
816 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18);
817 QCOMPARE(columnCountSpy.count(), 0);
818 QCOMPARE(rowCountSpy.count(), 0);
819}
820
821// Start off with empty rows and then set them to test rowCountChanged().
822void tst_QQmlTableModel::setRowsImperatively()
823{
824 QQuickView view(testFileUrl(fileName: "empty.qml"));
825 QCOMPARE(view.status(), QQuickView::Ready);
826 view.show();
827 QVERIFY(QTest::qWaitForWindowActive(&view));
828
829 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
830 QVERIFY(model);
831 QCOMPARE(model->rowCount(), 0);
832 QCOMPARE(model->columnCount(), 2);
833
834 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
835 QVERIFY(columnCountSpy.isValid());
836
837 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
838 QVERIFY(rowCountSpy.isValid());
839
840 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
841 QVERIFY(tableView);
842 QCOMPARE(tableView->rows(), 0);
843 QCOMPARE(tableView->columns(), 2);
844
845 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRows"));
846 QCOMPARE(model->rowCount(), 2);
847 QCOMPARE(model->columnCount(), 2);
848 const QHash<int, QByteArray> roleNames = model->roleNames();
849 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
850 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
851 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
852 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
853 QCOMPARE(columnCountSpy.count(), 0);
854 QCOMPARE(rowCountSpy.count(), 1);
855 QTRY_COMPARE(tableView->rows(), 2);
856 QCOMPARE(tableView->columns(), 2);
857}
858
859void tst_QQmlTableModel::setRowsMultipleTimes()
860{
861 QQuickView view(testFileUrl(fileName: "setRowsMultipleTimes.qml"));
862 QCOMPARE(view.status(), QQuickView::Ready);
863 view.show();
864 QVERIFY(QTest::qWaitForWindowActive(&view));
865
866 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
867 QVERIFY(model);
868 QCOMPARE(model->rowCount(), 2);
869 QCOMPARE(model->columnCount(), 2);
870
871 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
872 QVERIFY(columnCountSpy.isValid());
873
874 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
875 QVERIFY(rowCountSpy.isValid());
876
877 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
878 QVERIFY(tableView);
879 QCOMPARE(tableView->rows(), 2);
880 QCOMPARE(tableView->columns(), 2);
881
882 // Set valid rows after they've already been declared.
883 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowsValid"));
884 QCOMPARE(model->rowCount(), 3);
885 QCOMPARE(model->columnCount(), 2);
886 const QHash<int, QByteArray> roleNames = model->roleNames();
887 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
888 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 20);
889 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Imum"));
890 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 41);
891 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Power"));
892 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 89);
893 QCOMPARE(columnCountSpy.count(), 0);
894 QCOMPARE(rowCountSpy.count(), 1);
895 QTRY_COMPARE(tableView->rows(), 3);
896 QCOMPARE(tableView->columns(), 2);
897
898 // Set invalid rows; we should get a warning and nothing should change.
899 QTest::ignoreMessage(type: QtWarningMsg, messagePattern: QRegularExpression(
900 ".*setRows\\(\\): expected a property named \"name\" in row at index 0, but couldn't find one"));
901 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowsInvalid"));
902 QCOMPARE(model->rowCount(), 3);
903 QCOMPARE(model->columnCount(), 2);
904 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max"));
905 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 20);
906 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Imum"));
907 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 41);
908 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Power"));
909 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 89);
910 QCOMPARE(columnCountSpy.count(), 0);
911 QCOMPARE(rowCountSpy.count(), 1);
912 QCOMPARE(tableView->rows(), 3);
913 QCOMPARE(tableView->columns(), 2);
914}
915
916void tst_QQmlTableModel::dataAndEditing()
917{
918 QQuickView view(testFileUrl(fileName: "dataAndSetData.qml"));
919 QCOMPARE(view.status(), QQuickView::Ready);
920 view.show();
921 QVERIFY(QTest::qWaitForWindowActive(&view));
922
923 auto *model = view.rootObject()->property(name: "model").value<QAbstractTableModel*>();
924 QVERIFY(model);
925
926 const QHash<int, QByteArray> roleNames = model->roleNames();
927 QVERIFY(roleNames.values().contains("display"));
928 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
929 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
930 QVERIFY(QMetaObject::invokeMethod(model, "happyBirthday", Q_ARG(QVariant, QLatin1String("Oliver"))));
931 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
932 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
933 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
934 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 34);
935}
936
937void tst_QQmlTableModel::omitTableModelColumnIndex()
938{
939 QQmlEngine engine;
940 QQmlComponent component(&engine, testFileUrl(fileName: "omitTableModelColumnIndex.qml"));
941 QCOMPARE(component.status(), QQmlComponent::Ready);
942
943 QScopedPointer<QAbstractTableModel> model(qobject_cast<QAbstractTableModel*>(object: component.create()));
944 QVERIFY(model);
945 QCOMPARE(model->rowCount(), 2);
946 QCOMPARE(model->columnCount(), 2);
947
948 const QHash<int, QByteArray> roleNames = model->roleNames();
949 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
950 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
951 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
952 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
953}
954
955void tst_QQmlTableModel::complexRow()
956{
957 QQuickView view(testFileUrl(fileName: "complex.qml"));
958 QCOMPARE(view.status(), QQuickView::Ready);
959 view.show();
960 QVERIFY(QTest::qWaitForWindowActive(&view));
961
962 QQuickTableView *tableView = qobject_cast<QQuickTableView*>(object: view.rootObject());
963 QVERIFY(tableView);
964 QCOMPARE(tableView->rows(), 2);
965 QCOMPARE(tableView->columns(), 2);
966
967 auto *model = tableView->model().value<QAbstractTableModel*>();
968 QVERIFY(model);
969 QCOMPARE(model->rowCount(), 2);
970 QCOMPARE(model->columnCount(), 2);
971
972 const QHash<int, QByteArray> roleNames = model->roleNames();
973 QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John"));
974 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22);
975 QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver"));
976 QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33);
977}
978
979void tst_QQmlTableModel::appendRowWithDouble()
980{
981 QQuickView view(testFileUrl(fileName: "intAndDouble.qml"));
982 QCOMPARE(view.status(), QQuickView::Ready);
983 view.show();
984 QVERIFY(QTest::qWaitForWindowActive(&view));
985
986 auto *model = view.rootObject()->property(name: "testModel").value<QAbstractTableModel*>();
987 QVERIFY(model);
988 QCOMPARE(model->rowCount(), 2);
989 QCOMPARE(model->columnCount(), 2);
990
991 QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged()));
992 QVERIFY(columnCountSpy.isValid());
993
994 QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged()));
995 QVERIFY(rowCountSpy.isValid());
996
997 QQuickTableView *tableView = view.rootObject()->property(name: "tableView").value<QQuickTableView*>();
998 QVERIFY(tableView);
999 QCOMPARE(tableView->rows(), 2);
1000 QCOMPARE(tableView->columns(), 2);
1001
1002 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendBanana"));
1003 QCOMPARE(model->rowCount(), 3);
1004 QCOMPARE(model->columnCount(), 2);
1005 const QHash<int, QByteArray> roleNames = model->roleNames();
1006 const int roleKey = roleNames.key(value: "display");
1007 QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleKey).toString(),
1008 QLatin1String("1"));
1009 QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleKey).toString(),
1010 QLatin1String("Banana"));
1011 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleKey).toDouble(), 3.5);
1012 QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleKey).toString(),
1013 QLatin1String("3.5"));
1014 QCOMPARE(columnCountSpy.count(), 0);
1015 QCOMPARE(rowCountSpy.count(), 1);
1016 QTRY_COMPARE(tableView->rows(), 3);
1017 QCOMPARE(tableView->columns(), 2);
1018
1019 rowCountSpy.clear();
1020
1021 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendStrawberry"));
1022 QCOMPARE(model->rowCount(), 4);
1023 QCOMPARE(model->columnCount(), 2);
1024 QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleKey).toString(),
1025 QLatin1String("Strawberry"));
1026 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleKey).toDouble(), 5);
1027 QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleKey).toString(),
1028 QLatin1String("5"));
1029 QCOMPARE(columnCountSpy.count(), 0);
1030 QCOMPARE(rowCountSpy.count(), 1);
1031 QTRY_COMPARE(tableView->rows(), 4);
1032 QCOMPARE(tableView->columns(), 2);
1033
1034 rowCountSpy.clear();
1035 QTest::ignoreMessage(type: QtWarningMsg,
1036 messagePattern: QRegularExpression(".*appendRow\\(\\): failed converting value "
1037 "QVariant\\(QString, \"Invalid\"\\) set at column 1 with "
1038 "role \"QString\" to \"int\""));
1039 QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendInvalid"));
1040 // Nothing should change
1041 QCOMPARE(model->rowCount(), 4);
1042 QCOMPARE(model->columnCount(), 2);
1043 QCOMPARE(columnCountSpy.count(), 0);
1044 QCOMPARE(rowCountSpy.count(), 0);
1045 QCOMPARE(tableView->rows(), 4);
1046 QCOMPARE(tableView->columns(), 2);
1047}
1048
1049QTEST_MAIN(tst_QQmlTableModel)
1050
1051#include "tst_qqmltablemodel.moc"
1052

source code of qtdeclarative/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp