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 | |
47 | class ModelsToTest { |
48 | |
49 | public: |
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 | |
77 | private: |
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 | */ |
89 | ModelsToTest::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 | */ |
125 | QAbstractItemModel *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) |
171 | QT_WARNING_PUSH |
172 | QT_WARNING_DISABLE_DEPRECATED |
173 | if (modelType == "QDirModel" ) { |
174 | QDirModel *model = new QDirModel(); |
175 | model->setReadOnly(false); |
176 | return model; |
177 | } |
178 | QT_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 | */ |
237 | QModelIndex 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 | */ |
369 | void 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 | |
379 | void 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 | |