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::comboPopup() |
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 *popup = 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 *popup = 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 |
Definitions
- ControlInfo
- ControlInfos
- tst_customization
- qt_objectNames
- qt_createdQObjects
- qt_destroyedQObjects
- qt_destroyedParentQObjects
- qt_unparentedItemCount
- ItemParentListener
- ItemParentListener
- signalIndex
- slotIndex
- onParentChanged
- qt_itemParentListener
- qt_addQObject
- qt_removeQObject
- initTestCase
- cleanupTestCase
- init
- cleanup
- reset
- createControl
- creation_data
- creation
- override_data
- override
Learn Advanced QML with KDAB
Find out more