1/****************************************************************************
2**
3** Copyright (C) 2016 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
30#include <QtTest/QtTest>
31#include <QtCore/QCoreApplication>
32#include <QtSql/QtSql>
33#include <QtWidgets/QtWidgets>
34#include <QtCore/QSortFilterProxyModel>
35
36/*
37 To add a model to be tested add the header file to the includes
38 and impliment what is needed in the four functions below.
39
40 You can add more then one model, several Qt models and included as examples.
41
42 In tst_qitemmodel.cpp a new ModelsToTest object is created for each test.
43
44 When you have errors fix the first ones first. Later tests depend upon them working
45*/
46
47class ModelsToTest {
48
49public:
50 ModelsToTest();
51
52 QAbstractItemModel *createModel(const QString &modelType);
53 QModelIndex populateTestArea(QAbstractItemModel *model);
54 void cleanupTestArea(QAbstractItemModel *model);
55
56 enum Read {
57 ReadOnly, // won't perform remove(), insert(), and setData()
58 ReadWrite
59 };
60 enum Contains {
61 Empty, // Confirm that rowCount() == 0 etc throughout the test
62 HasData // Confirm that rowCount() != 0 etc throughout the test
63 };
64
65 struct test {
66 test(QString m, Read r, Contains c) : modelType(m), read(r), contains(c){};
67
68 QString modelType;
69 Read read;
70 Contains contains;
71 };
72
73 QList<test> tests;
74
75 static void setupDatabase();
76
77private:
78 QScopedPointer<QTemporaryDir> m_dirModelTempDir;
79};
80
81
82/*!
83 Add new tests, they can be the same model, but in a different state.
84
85 The name of the model is passed to createModel
86 If readOnly is true the remove tests will be skipped. Example: QDirModel is disabled.
87 If createModel returns an empty model. Example: QDirModel does not
88 */
89ModelsToTest::ModelsToTest()
90{
91 setupDatabase();
92
93#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
94 tests.append(t: test("QDirModel", ReadOnly, HasData));
95#endif
96 tests.append(t: test("QStringListModel", ReadWrite, HasData));
97 tests.append(t: test("QStringListModelEmpty", ReadWrite, Empty));
98
99 tests.append(t: test("QStandardItemModel", ReadWrite, HasData));
100 tests.append(t: test("QStandardItemModelEmpty", ReadWrite, Empty));
101
102 // QSortFilterProxyModel test uses QStandardItemModel so test it first
103 tests.append(t: test("QSortFilterProxyModel", ReadWrite, HasData));
104 tests.append(t: test("QSortFilterProxyModelEmpty", ReadWrite, Empty));
105 tests.append(t: test("QSortFilterProxyModelRegExp", ReadWrite, HasData));
106
107 tests.append(t: test("QListModel", ReadWrite, HasData));
108 tests.append(t: test("QListModelEmpty", ReadWrite, Empty));
109 tests.append(t: test("QTableModel", ReadWrite, HasData));
110 tests.append(t: test("QTableModelEmpty", ReadWrite, Empty));
111
112 tests.append(t: test("QTreeModel", ReadWrite, HasData));
113 tests.append(t: test("QTreeModelEmpty", ReadWrite, Empty));
114
115 tests.append(t: test("QSqlQueryModel", ReadOnly, HasData));
116 tests.append(t: test("QSqlQueryModelEmpty", ReadOnly, Empty));
117
118 // Fails on remove
119 tests.append(t: test("QSqlTableModel", ReadOnly, HasData));
120}
121
122/*!
123 Return a new modelType.
124 */
125QAbstractItemModel *ModelsToTest::createModel(const QString &modelType)
126{
127 if (modelType == "QStringListModelEmpty")
128 return new QStringListModel();
129
130 if (modelType == "QStringListModel") {
131 QStringListModel *model = new QStringListModel();
132 populateTestArea(model);
133 return model;
134 }
135
136 if (modelType == "QStandardItemModelEmpty") {
137 return new QStandardItemModel();
138 }
139
140 if (modelType == "QStandardItemModel") {
141 QStandardItemModel *model = new QStandardItemModel();
142 populateTestArea(model);
143 return model;
144 }
145
146 if (modelType == "QSortFilterProxyModelEmpty") {
147 QSortFilterProxyModel *model = new QSortFilterProxyModel;
148 QStandardItemModel *standardItemModel = new QStandardItemModel(model);
149 model->setSourceModel(standardItemModel);
150 return model;
151 }
152
153 if (modelType == "QSortFilterProxyModelRegExp") {
154 QSortFilterProxyModel *model = new QSortFilterProxyModel;
155 QStandardItemModel *standardItemModel = new QStandardItemModel(model);
156 model->setSourceModel(standardItemModel);
157 populateTestArea(model);
158 model->setFilterRegularExpression(QRegularExpression("(^$|I.*)"));
159 return model;
160 }
161
162 if (modelType == "QSortFilterProxyModel") {
163 QSortFilterProxyModel *model = new QSortFilterProxyModel;
164 QStandardItemModel *standardItemModel = new QStandardItemModel(model);
165 model->setSourceModel(standardItemModel);
166 populateTestArea(model);
167 return model;
168 }
169
170#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
171QT_WARNING_PUSH
172QT_WARNING_DISABLE_DEPRECATED
173 if (modelType == "QDirModel") {
174 QDirModel *model = new QDirModel();
175 model->setReadOnly(false);
176 return model;
177 }
178QT_WARNING_POP
179#endif
180
181 if (modelType == "QSqlQueryModel") {
182 QSqlQueryModel *model = new QSqlQueryModel();
183 populateTestArea(model);
184 return model;
185 }
186
187 if (modelType == "QSqlQueryModelEmpty") {
188 QSqlQueryModel *model = new QSqlQueryModel();
189 return model;
190 }
191
192 if (modelType == "QSqlTableModel") {
193 QSqlTableModel *model = new QSqlTableModel();
194 populateTestArea(model);
195 return model;
196 }
197
198 if (modelType == "QListModelEmpty")
199 return (new QListWidget)->model();
200
201 if (modelType == "QListModel") {
202 QListWidget *widget = new QListWidget;
203 populateTestArea(model: widget->model());
204 return widget->model();
205 }
206
207 if (modelType == "QTableModelEmpty")
208 return (new QTableWidget)->model();
209
210 if (modelType == "QTableModel") {
211 QTableWidget *widget = new QTableWidget;
212 populateTestArea(model: widget->model());
213 return widget->model();
214 }
215
216 if (modelType == "QTreeModelEmpty") {
217 QTreeWidget *widget = new QTreeWidget;
218 return widget->model();
219 }
220
221 if (modelType == "QTreeModel") {
222 QTreeWidget *widget = new QTreeWidget;
223 populateTestArea(model: widget->model());
224 return widget->model();
225 }
226
227 return 0;
228}
229
230/*!
231 Fills model with some test data at least twenty rows and if possible twenty or more columns.
232 Return the parent of a row/column that can be tested.
233
234 NOTE: If readOnly is true tests will try to remove and add rows and columns.
235 If you have a tree model returning not the root index will help catch more errors.
236 */
237QModelIndex ModelsToTest::populateTestArea(QAbstractItemModel *model)
238{
239 if (QStringListModel *stringListModel = qobject_cast<QStringListModel *>(object: model)) {
240 QString alphabet = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z";
241 stringListModel->setStringList( alphabet.split(sep: QLatin1Char(',')) );
242 return QModelIndex();
243 }
244
245 if (qobject_cast<QStandardItemModel *>(object: model)) {
246 // Basic tree StandardItemModel
247 QModelIndex parent;
248 QVariant blue = QVariant(QColor(Qt::blue));
249 for (int i = 0; i < 4; ++i) {
250 parent = model->index(row: 0, column: 0, parent);
251 model->insertRows(row: 0, count: 26 + i, parent);
252 model->insertColumns(column: 0, count: 4 + i, parent);
253 // Fill in some values to make it easier to debug
254 /*
255 for (int x = 0; x < 26 + i; ++x) {
256 QString xval = QString::number(x);
257 for (int y = 0; y < 26 + i; ++y) {
258 QString val = xval + QString::number(y) + QString::number(i);
259 QModelIndex index = model->index(x, y, parent);
260 model->setData(index, val);
261 model->setData(index, blue, Qt::ForegroundRole);
262 }
263 }
264 */
265 }
266 return model->index(row: 0,column: 0);
267 }
268
269 if (qobject_cast<QSortFilterProxyModel *>(object: model)) {
270 QAbstractItemModel *realModel = (qobject_cast<QSortFilterProxyModel *>(object: model))->sourceModel();
271 // Basic tree StandardItemModel
272 QModelIndex parent;
273 QVariant blue = QVariant(QColor(Qt::blue));
274 for (int i = 0; i < 4; ++i) {
275 parent = realModel->index(row: 0, column: 0, parent);
276 realModel->insertRows(row: 0, count: 26+i, parent);
277 realModel->insertColumns(column: 0, count: 4, parent);
278 // Fill in some values to make it easier to debug
279 /*
280 for (int x = 0; x < 26+i; ++x) {
281 QString xval = QString::number(x);
282 for (int y = 0; y < 26 + i; ++y) {
283 QString val = xval + QString::number(y) + QString::number(i);
284 QModelIndex index = realModel->index(x, y, parent);
285 realModel->setData(index, val);
286 realModel->setData(index, blue, Qt::ForegroundRole);
287 }
288 }
289 */
290 }
291 QModelIndex returnIndex = model->index(row: 0,column: 0);
292 if (!returnIndex.isValid())
293 qFatal(msg: "%s: model index to be returned is invalid", Q_FUNC_INFO);
294 return returnIndex;
295 }
296
297#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
298 if (QDirModel *dirModel = qobject_cast<QDirModel *>(object: model)) {
299 m_dirModelTempDir.reset(other: new QTemporaryDir);
300 if (!m_dirModelTempDir->isValid())
301 qFatal(msg: "Cannot create temporary directory \"%s\": %s",
302 qPrintable(QDir::toNativeSeparators(m_dirModelTempDir->path())),
303 qPrintable(m_dirModelTempDir->errorString()));
304
305 QDir tempDir(m_dirModelTempDir->path());
306 for (int i = 0; i < 26; ++i) {
307 const QString subdir = QLatin1String("foo_") + QString::number(i);
308 if (!tempDir.mkdir(dirName: subdir))
309 qFatal(msg: "Cannot create directory %s",
310 qPrintable(QDir::toNativeSeparators(tempDir.path() + QLatin1Char('/') +subdir)));
311 }
312 return dirModel->index(path: tempDir.path());
313 }
314#endif // QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
315
316 if (QSqlQueryModel *queryModel = qobject_cast<QSqlQueryModel *>(object: model)) {
317 QSqlQuery q;
318 q.exec(query: "CREATE TABLE test(id int primary key, name varchar(30))");
319 q.prepare(query: "INSERT INTO test(id, name) values (?, ?)");
320 for (int i = 0; i < 1024; ++i) {
321 q.addBindValue(val: i);
322 q.addBindValue(val: "Mr. Smith" + QString::number(i));
323 q.exec();
324 }
325 if (QSqlTableModel *tableModel = qobject_cast<QSqlTableModel *>(object: model)) {
326 tableModel->setTable("test");
327 tableModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
328 tableModel->select();
329 } else {
330 queryModel->setQuery(query: "select * from test");
331 }
332 return QModelIndex();
333 }
334
335 if (QListWidget *listWidget = qobject_cast<QListWidget *>(object: model->parent())) {
336 int items = 50;
337 while (items--)
338 listWidget->addItem(label: QLatin1String("item ") + QString::number(items));
339 return QModelIndex();
340 }
341
342 if (QTableWidget *tableWidget = qobject_cast<QTableWidget *>(object: model->parent())) {
343 tableWidget->setColumnCount(20);
344 tableWidget->setRowCount(20);
345 return QModelIndex();
346 }
347
348 if (QTreeWidget *treeWidget = qobject_cast<QTreeWidget *>(object: model->parent())) {
349 int topItems = 20;
350 treeWidget->setColumnCount(1);
351 QTreeWidgetItem *parent;
352 while (topItems--){
353 const QString tS = QString::number(topItems);
354 parent = new QTreeWidgetItem(treeWidget, QStringList(QLatin1String("top ") + tS));
355 for (int i = 0; i < 20; ++i)
356 new QTreeWidgetItem(parent, QStringList(QLatin1String("child ") + tS));
357 }
358 return QModelIndex();
359 }
360
361 qFatal(msg: "%s: unknown type of model", Q_FUNC_INFO);
362 return QModelIndex();
363}
364
365/*!
366 If you need to cleanup from populateTest() do it here.
367 Note that this is called after every test even if populateTestArea isn't called.
368 */
369void ModelsToTest::cleanupTestArea(QAbstractItemModel *model)
370{
371 if (qobject_cast<QSqlQueryModel *>(object: model))
372 QSqlQuery q("DROP TABLE test");
373#if QT_CONFIG(dirmodel) && QT_DEPRECATED_SINCE(5, 15)
374 if (qobject_cast<QDirModel *>(object: model))
375 m_dirModelTempDir.reset();
376#endif
377}
378
379void ModelsToTest::setupDatabase()
380{
381 if (!QSqlDatabase::database().isValid()) {
382 QSqlDatabase db = QSqlDatabase::addDatabase(type: "QSQLITE");
383 db.setDatabaseName(":memory:");
384 if (!db.open()) {
385 qWarning() << "Unable to open database" << db.lastError();
386 return;
387 }
388 }
389}
390
391

source code of qtbase/tests/auto/corelib/itemmodels/qitemmodel/modelstotest.cpp