1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
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 http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include <QtTest/qtest.h> |
38 | #include <QtCore/private/qhooks_p.h> |
39 | #include <QtCore/qregularexpression.h> |
40 | #include <QtQml/qqmlengine.h> |
41 | #include <QtQml/qqmlcomponent.h> |
42 | #include <QtQuick/qquickitem.h> |
43 | #include <QtQuick/qquickwindow.h> |
44 | #include <QtQuickControls2/qquickstyle.h> |
45 | #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> |
46 | #include "../shared/visualtestutil.h" |
47 | |
48 | using namespace QQuickVisualTestUtil; |
49 | |
50 | struct ControlInfo |
51 | { |
52 | QString type; |
53 | QStringList delegates; |
54 | }; |
55 | |
56 | static const ControlInfo ControlInfos[] = { |
57 | { .type: "AbstractButton" , .delegates: QStringList() << "background" << "contentItem" << "indicator" }, |
58 | { .type: "ApplicationWindow" , .delegates: QStringList() << "background" }, |
59 | { .type: "BusyIndicator" , .delegates: QStringList() << "background" << "contentItem" }, |
60 | { .type: "Button" , .delegates: QStringList() << "background" << "contentItem" }, |
61 | { .type: "CheckBox" , .delegates: QStringList() << "contentItem" << "indicator" }, |
62 | { .type: "CheckDelegate" , .delegates: QStringList() << "background" << "contentItem" << "indicator" }, |
63 | { .type: "ComboBox" , .delegates: QStringList() << "background" << "contentItem" << "indicator" }, // popup not created until needed |
64 | { .type: "Container" , .delegates: QStringList() << "background" << "contentItem" }, |
65 | { .type: "Control" , .delegates: QStringList() << "background" << "contentItem" }, |
66 | { .type: "DelayButton" , .delegates: QStringList() << "background" << "contentItem" }, |
67 | { .type: "Dial" , .delegates: QStringList() << "background" << "handle" }, |
68 | { .type: "Dialog" , .delegates: QStringList() << "background" << "contentItem" }, |
69 | { .type: "DialogButtonBox" , .delegates: QStringList() << "background" << "contentItem" }, |
70 | { .type: "Drawer" , .delegates: QStringList() << "background" << "contentItem" }, |
71 | { .type: "Frame" , .delegates: QStringList() << "background" << "contentItem" }, |
72 | { .type: "GroupBox" , .delegates: QStringList() << "background" << "contentItem" << "label" }, |
73 | { .type: "ItemDelegate" , .delegates: QStringList() << "background" << "contentItem" }, |
74 | { .type: "Label" , .delegates: QStringList() << "background" }, |
75 | { .type: "Menu" , .delegates: QStringList() << "background" << "contentItem" }, |
76 | { .type: "MenuBar" , .delegates: QStringList() << "background" << "contentItem" }, |
77 | { .type: "MenuBarItem" , .delegates: QStringList() << "background" << "contentItem" }, |
78 | { .type: "MenuItem" , .delegates: QStringList() << "arrow" << "background" << "contentItem" << "indicator" }, |
79 | { .type: "MenuSeparator" , .delegates: QStringList() << "background" << "contentItem" }, |
80 | { .type: "Page" , .delegates: QStringList() << "background" << "contentItem" }, |
81 | { .type: "PageIndicator" , .delegates: QStringList() << "background" << "contentItem" }, |
82 | { .type: "Pane" , .delegates: QStringList() << "background" << "contentItem" }, |
83 | { .type: "Popup" , .delegates: QStringList() << "background" << "contentItem" }, |
84 | { .type: "ProgressBar" , .delegates: QStringList() << "background" << "contentItem" }, |
85 | { .type: "RadioButton" , .delegates: QStringList() << "contentItem" << "indicator" }, |
86 | { .type: "RadioDelegate" , .delegates: QStringList() << "background" << "contentItem" << "indicator" }, |
87 | { .type: "RangeSlider" , .delegates: QStringList() << "background" << "first.handle" << "second.handle" }, |
88 | { .type: "RoundButton" , .delegates: QStringList() << "background" << "contentItem" }, |
89 | { .type: "ScrollBar" , .delegates: QStringList() << "background" << "contentItem" }, |
90 | { .type: "ScrollIndicator" , .delegates: QStringList() << "background" << "contentItem" }, |
91 | { .type: "ScrollView" , .delegates: QStringList() << "background" }, |
92 | { .type: "Slider" , .delegates: QStringList() << "background" << "handle" }, |
93 | { .type: "SpinBox" , .delegates: QStringList() << "background" << "contentItem" << "up.indicator" << "down.indicator" }, |
94 | { .type: "StackView" , .delegates: QStringList() << "background" << "contentItem" }, |
95 | { .type: "SwipeDelegate" , .delegates: QStringList() << "background" << "contentItem" }, |
96 | { .type: "SwipeView" , .delegates: QStringList() << "background" << "contentItem" }, |
97 | { .type: "Switch" , .delegates: QStringList() << "contentItem" << "indicator" }, |
98 | { .type: "SwitchDelegate" , .delegates: QStringList() << "background" << "contentItem" << "indicator" }, |
99 | { .type: "TabBar" , .delegates: QStringList() << "background" << "contentItem" }, |
100 | { .type: "TabButton" , .delegates: QStringList() << "background" << "contentItem" }, |
101 | { .type: "TextField" , .delegates: QStringList() << "background" }, |
102 | { .type: "TextArea" , .delegates: QStringList() << "background" }, |
103 | { .type: "ToolBar" , .delegates: QStringList() << "background" << "contentItem" }, |
104 | { .type: "ToolButton" , .delegates: QStringList() << "background" << "contentItem" }, |
105 | { .type: "ToolSeparator" , .delegates: QStringList() << "background" << "contentItem" }, |
106 | { .type: "ToolTip" , .delegates: QStringList() << "background" << "contentItem" }, |
107 | { .type: "Tumbler" , .delegates: QStringList() << "background" << "contentItem" } |
108 | }; |
109 | |
110 | class tst_customization : public QQmlDataTest |
111 | { |
112 | Q_OBJECT |
113 | |
114 | private slots: |
115 | void initTestCase(); |
116 | void cleanupTestCase(); |
117 | |
118 | void init(); |
119 | void cleanup(); |
120 | |
121 | void creation_data(); |
122 | void creation(); |
123 | |
124 | void override_data(); |
125 | void override(); |
126 | |
127 | void comboPopup(); |
128 | |
129 | private: |
130 | void reset(); |
131 | void addHooks(); |
132 | void removeHooks(); |
133 | |
134 | QObject* createControl(const QString &type, const QString &qml, QString *error); |
135 | |
136 | QQmlEngine *engine = nullptr; |
137 | }; |
138 | |
139 | typedef QHash<QObject *, QString> QObjectNameHash; |
140 | Q_GLOBAL_STATIC(QObjectNameHash, qt_objectNames) |
141 | Q_GLOBAL_STATIC(QStringList, qt_createdQObjects) |
142 | Q_GLOBAL_STATIC(QStringList, qt_destroyedQObjects) |
143 | Q_GLOBAL_STATIC(QStringList, qt_destroyedParentQObjects) |
144 | static int qt_unparentedItemCount = 0; |
145 | |
146 | class ItemParentListener : public QQuickItem |
147 | { |
148 | Q_OBJECT |
149 | |
150 | public: |
151 | ItemParentListener() |
152 | { |
153 | m_slotIndex = metaObject()->indexOfSlot(slot: "onParentChanged()" ); |
154 | m_signalIndex = QMetaObjectPrivate::signalIndex(m: QMetaMethod::fromSignal(signal: &QQuickItem::parentChanged)); |
155 | } |
156 | |
157 | int signalIndex() const { return m_signalIndex; } |
158 | int slotIndex() const { return m_slotIndex; } |
159 | |
160 | public slots: |
161 | void onParentChanged() |
162 | { |
163 | const QQuickItem *item = qobject_cast<QQuickItem *>(object: sender()); |
164 | if (!item) |
165 | return; |
166 | |
167 | if (!item->parentItem()) |
168 | ++qt_unparentedItemCount; |
169 | } |
170 | |
171 | private: |
172 | int m_slotIndex; |
173 | int m_signalIndex; |
174 | }; |
175 | static ItemParentListener *qt_itemParentListener = nullptr; |
176 | |
177 | extern "C" Q_DECL_EXPORT void qt_addQObject(QObject *object) |
178 | { |
179 | // objectName is not set at construction time |
180 | QObject::connect(sender: object, signal: &QObject::objectNameChanged, slot: [object](const QString &objectName) { |
181 | QString oldObjectName = qt_objectNames()->value(key: object); |
182 | if (!oldObjectName.isEmpty()) |
183 | qt_createdQObjects()->removeOne(t: oldObjectName); |
184 | // Only track object names from our QML files, |
185 | // not e.g. contentItem object names (like "ApplicationWindow"). |
186 | if (objectName.contains(s: "-" )) { |
187 | qt_createdQObjects()->append(t: objectName); |
188 | qt_objectNames()->insert(key: object, value: objectName); |
189 | } |
190 | }); |
191 | |
192 | if (qt_itemParentListener) { |
193 | static const int signalIndex = qt_itemParentListener->signalIndex(); |
194 | static const int slotIndex = qt_itemParentListener->slotIndex(); |
195 | QMetaObject::connect(sender: object, signal_index: signalIndex, receiver: qt_itemParentListener, method_index: slotIndex); |
196 | } |
197 | } |
198 | |
199 | extern "C" Q_DECL_EXPORT void qt_removeQObject(QObject *object) |
200 | { |
201 | QString objectName = object->objectName(); |
202 | if (!objectName.isEmpty()) |
203 | qt_destroyedQObjects()->append(t: objectName); |
204 | qt_objectNames()->remove(key: object); |
205 | |
206 | QObject *parent = object->parent(); |
207 | if (parent) { |
208 | QString parentName = parent->objectName(); |
209 | if (!parentName.isEmpty()) |
210 | qt_destroyedParentQObjects()->append(t: parentName); |
211 | } |
212 | } |
213 | |
214 | void tst_customization::initTestCase() |
215 | { |
216 | QQmlDataTest::initTestCase(); |
217 | |
218 | qt_itemParentListener = new ItemParentListener; |
219 | } |
220 | |
221 | void tst_customization::cleanupTestCase() |
222 | { |
223 | delete qt_itemParentListener; |
224 | qt_itemParentListener = nullptr; |
225 | } |
226 | |
227 | void tst_customization::init() |
228 | { |
229 | engine = new QQmlEngine(this); |
230 | |
231 | qtHookData[QHooks::AddQObject] = reinterpret_cast<quintptr>(&qt_addQObject); |
232 | qtHookData[QHooks::RemoveQObject] = reinterpret_cast<quintptr>(&qt_removeQObject); |
233 | } |
234 | |
235 | void tst_customization::cleanup() |
236 | { |
237 | qtHookData[QHooks::AddQObject] = 0; |
238 | qtHookData[QHooks::RemoveQObject] = 0; |
239 | |
240 | delete engine; |
241 | engine = nullptr; |
242 | |
243 | qmlClearTypeRegistrations(); |
244 | |
245 | reset(); |
246 | } |
247 | |
248 | void tst_customization::reset() |
249 | { |
250 | qt_unparentedItemCount = 0; |
251 | qt_createdQObjects()->clear(); |
252 | qt_destroyedQObjects()->clear(); |
253 | qt_destroyedParentQObjects()->clear(); |
254 | } |
255 | |
256 | QObject* tst_customization::createControl(const QString &name, const QString &qml, QString *error) |
257 | { |
258 | QQmlComponent component(engine); |
259 | component.setData("import QtQuick 2.10; import QtQuick.Window 2.2; import QtQuick.Controls 2.3; " + name.toUtf8() + " { " + qml.toUtf8() + " }" , baseUrl: QUrl()); |
260 | QObject *obj = component.create(); |
261 | if (!obj) |
262 | *error = component.errorString(); |
263 | return obj; |
264 | } |
265 | |
266 | void tst_customization::creation_data() |
267 | { |
268 | QTest::addColumn<QString>(name: "style" ); |
269 | QTest::addColumn<QString>(name: "type" ); |
270 | QTest::addColumn<QStringList>(name: "delegates" ); |
271 | |
272 | // the "empty" style does not contain any delegates |
273 | for (const ControlInfo &control : ControlInfos) |
274 | QTest::newRow(qPrintable("empty:" + control.type)) << "empty" << control.type << QStringList(); |
275 | |
276 | // the "incomplete" style is missing bindings to the delegates (must be created regardless) |
277 | for (const ControlInfo &control : ControlInfos) |
278 | QTest::newRow(qPrintable("incomplete:" + control.type)) << "incomplete" << control.type << control.delegates; |
279 | |
280 | // the "identified" style has IDs in the delegates (prevents deferred execution) |
281 | for (const ControlInfo &control : ControlInfos) |
282 | QTest::newRow(qPrintable("identified:" + control.type)) << "identified" << control.type << control.delegates; |
283 | |
284 | // the "simple" style simulates a proper style and contains bindings to/in delegates |
285 | for (const ControlInfo &control : ControlInfos) |
286 | QTest::newRow(qPrintable("simple:" + control.type)) << "simple" << control.type << control.delegates; |
287 | |
288 | // the "override" style overrides all delegates in the "simple" style |
289 | for (const ControlInfo &control : ControlInfos) |
290 | QTest::newRow(qPrintable("override:" + control.type)) << "override" << control.type << control.delegates; |
291 | } |
292 | |
293 | void tst_customization::creation() |
294 | { |
295 | QFETCH(QString, style); |
296 | QFETCH(QString, type); |
297 | QFETCH(QStringList, delegates); |
298 | |
299 | QQuickStyle::setStyle(testFile(fileName: "styles/" + style)); |
300 | |
301 | QString error; |
302 | QScopedPointer<QObject> control(createControl(name: type, qml: "" , error: &error)); |
303 | QVERIFY2(control, qPrintable(error)); |
304 | |
305 | QByteArray templateType = "QQuick" + type.toUtf8(); |
306 | QVERIFY2(control->inherits(templateType), qPrintable(type + " does not inherit " + templateType + " (" + control->metaObject()->className() + ")" )); |
307 | |
308 | // <control>-<style> |
309 | QString controlName = type.toLower() + "-" + style; |
310 | QCOMPARE(control->objectName(), controlName); |
311 | QVERIFY2(qt_createdQObjects()->removeOne(controlName), qPrintable(controlName + " was not created as expected" )); |
312 | |
313 | for (QString delegate : qAsConst(t&: delegates)) { |
314 | QStringList properties = delegate.split(sep: "." , behavior: Qt::SkipEmptyParts); |
315 | |
316 | // <control>-<delegate>-<style>(-<override>) |
317 | delegate.append(s: "-" + style); |
318 | delegate.prepend(s: type.toLower() + "-" ); |
319 | |
320 | QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected" )); |
321 | |
322 | // verify that the delegate instance has the expected object name |
323 | // in case of grouped properties, we must query the properties step by step |
324 | QObject *instance = control.data(); |
325 | while (!properties.isEmpty()) { |
326 | QString property = properties.takeFirst(); |
327 | instance = instance->property(name: property.toUtf8()).value<QObject *>(); |
328 | QVERIFY2(instance, qPrintable("property was null: " + property)); |
329 | } |
330 | QCOMPARE(instance->objectName(), delegate); |
331 | } |
332 | |
333 | QEXPECT_FAIL("identified:ComboBox" , "ComboBox::popup with an ID is created at construction time" , Continue); |
334 | |
335 | QVERIFY2(qt_createdQObjects()->isEmpty(), qPrintable("unexpectedly created: " + qt_createdQObjects->join(", " ))); |
336 | QVERIFY2(qt_destroyedQObjects()->isEmpty(), qPrintable("unexpectedly destroyed: " + qt_destroyedQObjects->join(", " ) + " were unexpectedly destroyed" )); |
337 | |
338 | QVERIFY2(qt_destroyedParentQObjects()->isEmpty(), qPrintable("delegates/children of: " + qt_destroyedParentQObjects->join(", " ) + " were unexpectedly destroyed" )); |
339 | } |
340 | |
341 | void tst_customization::override_data() |
342 | { |
343 | QTest::addColumn<QString>(name: "style" ); |
344 | QTest::addColumn<QString>(name: "type" ); |
345 | QTest::addColumn<QStringList>(name: "delegates" ); |
346 | QTest::addColumn<QString>(name: "nonDeferred" ); |
347 | QTest::addColumn<bool>(name: "identify" ); |
348 | |
349 | // NOTE: delegates with IDs prevent deferred execution |
350 | |
351 | // default delegates with IDs, override with custom delegates with no IDs |
352 | for (const ControlInfo &control : ControlInfos) |
353 | QTest::newRow(qPrintable("identified:" + control.type)) << "identified" << control.type << control.delegates << "identified" << false; |
354 | |
355 | // default delegates with no IDs, override with custom delegates with IDs |
356 | for (const ControlInfo &control : ControlInfos) |
357 | QTest::newRow(qPrintable("simple:" + control.type)) << "simple" << control.type << control.delegates << "" << true; |
358 | |
359 | // default delegates with IDs, override with custom delegates with IDs |
360 | for (const ControlInfo &control : ControlInfos) |
361 | QTest::newRow(qPrintable("overidentified:" + control.type)) << "identified" << control.type << control.delegates << "identified" << true; |
362 | |
363 | #ifndef Q_OS_MACOS // QTBUG-65671 |
364 | |
365 | // test that the built-in styles don't have undesired IDs in their delegates |
366 | const QStringList styles = QStringList() << "Default" << "Fusion" << "Material" << "Universal" ; // ### TODO: QQuickStyle::availableStyles(); |
367 | for (const QString &style : styles) { |
368 | for (const ControlInfo &control : ControlInfos) |
369 | QTest::newRow(qPrintable(style + ":" + control.type)) << style << control.type << control.delegates << "" << false; |
370 | } |
371 | |
372 | #endif |
373 | } |
374 | |
375 | void tst_customization::override() |
376 | { |
377 | QFETCH(QString, style); |
378 | QFETCH(QString, type); |
379 | QFETCH(QStringList, delegates); |
380 | QFETCH(QString, nonDeferred); |
381 | QFETCH(bool, identify); |
382 | |
383 | const QString testStyle = testFile(fileName: "styles/" + style); |
384 | if (QDir(testStyle).exists()) |
385 | QQuickStyle::setStyle(testStyle); |
386 | else |
387 | QQuickStyle::setStyle(style); |
388 | |
389 | QString qml; |
390 | qml += QString("objectName: '%1-%2-override'; " ).arg(a: type.toLower()).arg(a: style); |
391 | for (const QString &delegate : delegates) { |
392 | QString id = identify ? QString("id: %1;" ).arg(a: delegate) : QString(); |
393 | qml += QString("%1: Item { %2 objectName: '%3-%1-%4-override' } " ).arg(a: delegate).arg(a: id.replace(before: "." , after: "" )).arg(a: type.toLower()).arg(a: style); |
394 | } |
395 | |
396 | QString error; |
397 | QScopedPointer<QObject> control(createControl(name: type, qml, error: &error)); |
398 | QVERIFY2(control, qPrintable(error)); |
399 | |
400 | // If there are no intentional IDs in the default delegates nor in the overridden custom |
401 | // delegates, no item should get un-parented during the creation process. An item being |
402 | // unparented means that a delegate got destroyed, so there must be an internal ID in one |
403 | // of the delegates in the tested style. |
404 | if (!identify && nonDeferred.isEmpty()) { |
405 | QEXPECT_FAIL("Universal:ApplicationWindow" , "ApplicationWindow.qml contains an intentionally unparented FocusRectangle" , Continue); |
406 | QCOMPARE(qt_unparentedItemCount, 0); |
407 | } |
408 | |
409 | // <control>-<style>-override |
410 | QString controlName = type.toLower() + "-" + style + "-override" ; |
411 | QCOMPARE(control->objectName(), controlName); |
412 | QVERIFY2(qt_createdQObjects()->removeOne(controlName), qPrintable(controlName + " was not created as expected" )); |
413 | |
414 | for (QString delegate : qAsConst(t&: delegates)) { |
415 | QStringList properties = delegate.split(sep: "." , behavior: Qt::SkipEmptyParts); |
416 | |
417 | // <control>-<delegate>-<style>(-override) |
418 | delegate.append(s: "-" + style); |
419 | delegate.prepend(s: type.toLower() + "-" ); |
420 | |
421 | if (!nonDeferred.isEmpty()) |
422 | QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected" )); |
423 | |
424 | delegate.append(s: "-override" ); |
425 | QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected" )); |
426 | |
427 | // verify that the delegate instance has the expected object name |
428 | // in case of grouped properties, we must query the properties step by step |
429 | QObject *instance = control.data(); |
430 | while (!properties.isEmpty()) { |
431 | QString property = properties.takeFirst(); |
432 | instance = instance->property(name: property.toUtf8()).value<QObject *>(); |
433 | QVERIFY2(instance, qPrintable("property was null: " + property)); |
434 | } |
435 | QCOMPARE(instance->objectName(), delegate); |
436 | } |
437 | |
438 | QEXPECT_FAIL("identified:ComboBox" , "ComboBox::popup with an ID is created at construction time" , Continue); |
439 | QEXPECT_FAIL("overidentified:ComboBox" , "ComboBox::popup with an ID is created at construction time" , Continue); |
440 | QVERIFY2(qt_createdQObjects()->isEmpty(), qPrintable("unexpectedly created: " + qt_createdQObjects->join(", " ))); |
441 | |
442 | if (!nonDeferred.isEmpty()) { |
443 | // There were items for which deferred execution was not possible. |
444 | for (QString delegateName : qAsConst(t&: delegates)) { |
445 | if (!delegateName.contains(s: "-" )) |
446 | delegateName.append(s: "-" + nonDeferred); |
447 | delegateName.prepend(s: type.toLower() + "-" ); |
448 | |
449 | const int delegateIndex = qt_destroyedQObjects()->indexOf(t: delegateName); |
450 | QVERIFY2(delegateIndex == -1, qPrintable(delegateName + " was unexpectedly destroyed" )); |
451 | |
452 | const auto controlChildren = control->children(); |
453 | const auto childIt = std::find_if(first: controlChildren.constBegin(), last: controlChildren.constEnd(), pred: [delegateName](const QObject *child) { |
454 | return child->objectName() == delegateName; |
455 | }); |
456 | // We test other delegates (like the background) here, so make sure we don't end up with XPASSes by using the wrong delegate. |
457 | if (delegateName.contains(s: QLatin1String("handle" ))) { |
458 | QEXPECT_FAIL("identified:RangeSlider" , "For some reason, items that are belong to grouped properties fail here" , Abort); |
459 | QEXPECT_FAIL("overidentified:RangeSlider" , "For some reason, items that are belong to grouped properties fail here" , Abort); |
460 | } |
461 | if (delegateName.contains(s: QLatin1String("indicator" ))) { |
462 | QEXPECT_FAIL("identified:SpinBox" , "For some reason, items that are belong to grouped properties fail here" , Abort); |
463 | QEXPECT_FAIL("overidentified:SpinBox" , "For some reason, items that are belong to grouped properties fail here" , Abort); |
464 | } |
465 | QVERIFY2(childIt != controlChildren.constEnd(), qPrintable(QString::fromLatin1( |
466 | "Expected delegate \"%1\" to still be a QObject child of \"%2\"" ).arg(delegateName).arg(controlName))); |
467 | |
468 | const auto *delegate = qobject_cast<QQuickItem*>(object: *childIt); |
469 | // Ensure that the item is hidden, etc. |
470 | QVERIFY(delegate); |
471 | QCOMPARE(delegate->isVisible(), false); |
472 | QCOMPARE(delegate->parentItem(), nullptr); |
473 | } |
474 | } |
475 | |
476 | QVERIFY2(qt_destroyedQObjects()->isEmpty(), qPrintable("unexpectedly destroyed: " + qt_destroyedQObjects->join(", " ))); |
477 | } |
478 | |
479 | void tst_customization::() |
480 | { |
481 | QQuickStyle::setStyle(testFile(fileName: "styles/simple" )); |
482 | |
483 | { |
484 | // test that ComboBox::popup is created when accessed |
485 | QQmlComponent component(engine); |
486 | component.setData("import QtQuick.Controls 2.2; ComboBox { }" , baseUrl: QUrl()); |
487 | QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(object: component.create())); |
488 | QVERIFY(comboBox); |
489 | |
490 | QVERIFY(!qt_createdQObjects()->contains("combobox-popup-simple" )); |
491 | |
492 | QObject * = comboBox->property(name: "popup" ).value<QObject *>(); |
493 | QVERIFY(popup); |
494 | QVERIFY(qt_createdQObjects()->contains("combobox-popup-simple" )); |
495 | } |
496 | |
497 | reset(); |
498 | |
499 | { |
500 | // test that ComboBox::popup is created when it becomes visible |
501 | QQuickWindow window; |
502 | window.resize(w: 300, h: 300); |
503 | window.show(); |
504 | window.requestActivate(); |
505 | QVERIFY(QTest::qWaitForWindowActive(&window)); |
506 | |
507 | QQmlComponent component(engine); |
508 | component.setData("import QtQuick.Controls 2.2; ComboBox { }" , baseUrl: QUrl()); |
509 | QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(object: component.create())); |
510 | QVERIFY(comboBox); |
511 | |
512 | comboBox->setParentItem(window.contentItem()); |
513 | QVERIFY(!qt_createdQObjects()->contains("combobox-popup-simple" )); |
514 | |
515 | QTest::mouseClick(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(1, 1)); |
516 | QVERIFY(qt_createdQObjects()->contains("combobox-popup-simple" )); |
517 | } |
518 | |
519 | reset(); |
520 | |
521 | { |
522 | // test that ComboBox::popup is completed upon component completion (if appropriate) |
523 | QQmlComponent component(engine); |
524 | component.setData("import QtQuick 2.9; import QtQuick.Controls 2.2; ComboBox { id: control; contentItem: Item { visible: !control.popup.visible } popup: Popup { property bool wasCompleted: false; Component.onCompleted: wasCompleted = true } }" , baseUrl: QUrl()); |
525 | QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(object: component.create())); |
526 | QVERIFY(comboBox); |
527 | |
528 | QObject * = comboBox->property(name: "popup" ).value<QObject *>(); |
529 | QVERIFY(popup); |
530 | QCOMPARE(popup->property("wasCompleted" ), QVariant(true)); |
531 | } |
532 | } |
533 | |
534 | QTEST_MAIN(tst_customization) |
535 | |
536 | #include "tst_customization.moc" |
537 | |