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
48using namespace QQuickVisualTestUtil;
49
50struct ControlInfo
51{
52 QString type;
53 QStringList delegates;
54};
55
56static 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
110class tst_customization : public QQmlDataTest
111{
112 Q_OBJECT
113
114private 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
129private:
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
139typedef QHash<QObject *, QString> QObjectNameHash;
140Q_GLOBAL_STATIC(QObjectNameHash, qt_objectNames)
141Q_GLOBAL_STATIC(QStringList, qt_createdQObjects)
142Q_GLOBAL_STATIC(QStringList, qt_destroyedQObjects)
143Q_GLOBAL_STATIC(QStringList, qt_destroyedParentQObjects)
144static int qt_unparentedItemCount = 0;
145
146class ItemParentListener : public QQuickItem
147{
148 Q_OBJECT
149
150public:
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
160public 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
171private:
172 int m_slotIndex;
173 int m_signalIndex;
174};
175static ItemParentListener *qt_itemParentListener = nullptr;
176
177extern "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
199extern "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
214void tst_customization::initTestCase()
215{
216 QQmlDataTest::initTestCase();
217
218 qt_itemParentListener = new ItemParentListener;
219}
220
221void tst_customization::cleanupTestCase()
222{
223 delete qt_itemParentListener;
224 qt_itemParentListener = nullptr;
225}
226
227void 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
235void 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
248void tst_customization::reset()
249{
250 qt_unparentedItemCount = 0;
251 qt_createdQObjects()->clear();
252 qt_destroyedQObjects()->clear();
253 qt_destroyedParentQObjects()->clear();
254}
255
256QObject* 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
266void 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
293void 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
341void 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
375void 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
479void 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
534QTEST_MAIN(tst_customization)
535
536#include "tst_customization.moc"
537

source code of qtquickcontrols2/tests/auto/customization/tst_customization.cpp