591 lines
24 KiB
C++
591 lines
24 KiB
C++
// Copyright (C) 2017 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
#include <QtTest/qtest.h>
|
|
#include <QtCore/private/qhooks_p.h>
|
|
#include <QtCore/qregularexpression.h>
|
|
#include <QtQml/qqmlengine.h>
|
|
#include <QtQml/qqmlcomponent.h>
|
|
#include <QtQuick/qquickitem.h>
|
|
#include <QtQuick/qquickwindow.h>
|
|
#include <QtQuickTestUtils/private/qmlutils_p.h>
|
|
#include <QtQuickControlsTestUtils/private/controlstestutils_p.h>
|
|
#include <QtQuickControls2/qquickstyle.h>
|
|
#include <QtQuickControls2/private/qquickstyle_p.h>
|
|
#include <QtQuickTemplates2/private/qquickcontrol_p_p.h>
|
|
|
|
using namespace QQuickControlsTestUtils;
|
|
|
|
struct ControlInfo
|
|
{
|
|
QString type;
|
|
QStringList delegates;
|
|
};
|
|
|
|
static const ControlInfo ControlInfos[] = {
|
|
{ "AbstractButton", QStringList() << "background" << "contentItem" << "indicator" },
|
|
{ "ApplicationWindow", QStringList() << "background" },
|
|
{ "BusyIndicator", QStringList() << "background" << "contentItem" },
|
|
{ "Button", QStringList() << "background" << "contentItem" },
|
|
{ "CheckBox", QStringList() << "contentItem" << "indicator" },
|
|
{ "CheckDelegate", QStringList() << "background" << "contentItem" << "indicator" },
|
|
{ "ComboBox", QStringList() << "background" << "contentItem" << "indicator" }, // popup not created until needed
|
|
{ "Container", QStringList() << "background" << "contentItem" },
|
|
{ "Control", QStringList() << "background" << "contentItem" },
|
|
{ "DelayButton", QStringList() << "background" << "contentItem" },
|
|
{ "Dial", QStringList() << "background" << "handle" },
|
|
{ "Dialog", QStringList() << "background" << "contentItem" },
|
|
{ "DialogButtonBox", QStringList() << "background" << "contentItem" },
|
|
{ "Drawer", QStringList() << "background" << "contentItem" },
|
|
{ "Frame", QStringList() << "background" << "contentItem" },
|
|
{ "GroupBox", QStringList() << "background" << "contentItem" << "label" },
|
|
{ "ItemDelegate", QStringList() << "background" << "contentItem" },
|
|
{ "Label", QStringList() << "background" },
|
|
{ "Menu", QStringList() << "background" << "contentItem" },
|
|
{ "MenuBar", QStringList() << "background" << "contentItem" },
|
|
{ "MenuBarItem", QStringList() << "background" << "contentItem" },
|
|
{ "MenuItem", QStringList() << "arrow" << "background" << "contentItem" << "indicator" },
|
|
{ "MenuSeparator", QStringList() << "background" << "contentItem" },
|
|
{ "Page", QStringList() << "background" << "contentItem" },
|
|
{ "PageIndicator", QStringList() << "background" << "contentItem" },
|
|
{ "Pane", QStringList() << "background" << "contentItem" },
|
|
{ "Popup", QStringList() << "background" << "contentItem" },
|
|
{ "ProgressBar", QStringList() << "background" << "contentItem" },
|
|
{ "RadioButton", QStringList() << "contentItem" << "indicator" },
|
|
{ "RadioDelegate", QStringList() << "background" << "contentItem" << "indicator" },
|
|
{ "RangeSlider", QStringList() << "background" << "first.handle" << "second.handle" },
|
|
{ "RoundButton", QStringList() << "background" << "contentItem" },
|
|
{ "ScrollBar", QStringList() << "background" << "contentItem" },
|
|
{ "ScrollIndicator", QStringList() << "background" << "contentItem" },
|
|
{ "ScrollView", QStringList() << "background" },
|
|
{ "Slider", QStringList() << "background" << "handle" },
|
|
{ "SpinBox", QStringList() << "background" << "contentItem" << "up.indicator" << "down.indicator" },
|
|
{ "StackView", QStringList() << "background" << "contentItem" },
|
|
{ "SwipeDelegate", QStringList() << "background" << "contentItem" },
|
|
{ "SwipeView", QStringList() << "background" << "contentItem" },
|
|
{ "Switch", QStringList() << "contentItem" << "indicator" },
|
|
{ "SwitchDelegate", QStringList() << "background" << "contentItem" << "indicator" },
|
|
{ "TabBar", QStringList() << "background" << "contentItem" },
|
|
{ "TabButton", QStringList() << "background" << "contentItem" },
|
|
{ "TextField", QStringList() << "background" },
|
|
{ "TextArea", QStringList() << "background" },
|
|
{ "ToolBar", QStringList() << "background" << "contentItem" },
|
|
{ "ToolButton", QStringList() << "background" << "contentItem" },
|
|
{ "ToolSeparator", QStringList() << "background" << "contentItem" },
|
|
{ "ToolTip", QStringList() << "background" << "contentItem" },
|
|
{ "Tumbler", QStringList() << "background" << "contentItem" }
|
|
};
|
|
|
|
static const QString nonCustomizableWarning = ".*The current style does not support customization of this control.*";
|
|
|
|
class tst_customization : public QQmlDataTest
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
tst_customization();
|
|
|
|
private slots:
|
|
void initTestCase() override;
|
|
void cleanupTestCase();
|
|
|
|
void init() override;
|
|
void cleanup();
|
|
|
|
void creation_data();
|
|
void creation();
|
|
|
|
void override_data();
|
|
void override();
|
|
|
|
void comboPopup();
|
|
|
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
|
|
void noCustomizationWarningsForDefaultControls_data();
|
|
void noCustomizationWarningsForDefaultControls();
|
|
#endif
|
|
|
|
private:
|
|
void reset();
|
|
void addHooks();
|
|
void removeHooks();
|
|
|
|
QObject* createControl(const QString &type, const QString &qml, QString *error);
|
|
|
|
QQmlEngine *engine = nullptr;
|
|
};
|
|
|
|
typedef QHash<QObject *, QString> QObjectNameHash;
|
|
Q_GLOBAL_STATIC(QObjectNameHash, qt_objectNames)
|
|
Q_GLOBAL_STATIC(QStringList, qt_createdQObjects)
|
|
Q_GLOBAL_STATIC(QStringList, qt_destroyedQObjects)
|
|
Q_GLOBAL_STATIC(QStringList, qt_destroyedParentQObjects)
|
|
static int qt_unparentedItemCount = 0;
|
|
|
|
class ItemParentListener : public QQuickItem
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
ItemParentListener()
|
|
{
|
|
m_slotIndex = metaObject()->indexOfSlot("onParentChanged()");
|
|
m_signalIndex = QMetaObjectPrivate::signalIndex(QMetaMethod::fromSignal(&QQuickItem::parentChanged));
|
|
}
|
|
|
|
int signalIndex() const { return m_signalIndex; }
|
|
int slotIndex() const { return m_slotIndex; }
|
|
|
|
public slots:
|
|
void onParentChanged()
|
|
{
|
|
const QQuickItem *item = qobject_cast<QQuickItem *>(sender());
|
|
if (!item)
|
|
return;
|
|
|
|
if (!item->parentItem())
|
|
++qt_unparentedItemCount;
|
|
}
|
|
|
|
private:
|
|
int m_slotIndex;
|
|
int m_signalIndex;
|
|
};
|
|
static ItemParentListener *qt_itemParentListener = nullptr;
|
|
|
|
extern "C" Q_DECL_EXPORT void qt_addQObject(QObject *object)
|
|
{
|
|
// objectName is not set at construction time
|
|
QObject::connect(object, &QObject::objectNameChanged, [object](const QString &objectName) {
|
|
QString oldObjectName = qt_objectNames()->value(object);
|
|
if (!oldObjectName.isEmpty())
|
|
qt_createdQObjects()->removeOne(oldObjectName);
|
|
// Only track object names from our QML files,
|
|
// not e.g. contentItem object names (like "ApplicationWindow").
|
|
if (objectName.contains("-")) {
|
|
qt_createdQObjects()->append(objectName);
|
|
qt_objectNames()->insert(object, objectName);
|
|
}
|
|
});
|
|
|
|
if (qt_itemParentListener) {
|
|
static const int signalIndex = qt_itemParentListener->signalIndex();
|
|
static const int slotIndex = qt_itemParentListener->slotIndex();
|
|
QMetaObject::connect(object, signalIndex, qt_itemParentListener, slotIndex);
|
|
}
|
|
}
|
|
|
|
extern "C" Q_DECL_EXPORT void qt_removeQObject(QObject *object)
|
|
{
|
|
QString objectName = object->objectName();
|
|
if (!objectName.isEmpty())
|
|
qt_destroyedQObjects()->append(objectName);
|
|
qt_objectNames()->remove(object);
|
|
|
|
QObject *parent = object->parent();
|
|
if (parent) {
|
|
QString parentName = parent->objectName();
|
|
if (!parentName.isEmpty())
|
|
qt_destroyedParentQObjects()->append(parentName);
|
|
}
|
|
}
|
|
|
|
// We don't want to fail on warnings until QTBUG-98964 is fixed,
|
|
// as we deliberately prevent deferred execution in some of the tests here,
|
|
// which causes warnings.
|
|
tst_customization::tst_customization()
|
|
: QQmlDataTest(QT_QMLTEST_DATADIR, FailOnWarningsPolicy::DoNotFailOnWarnings)
|
|
{
|
|
}
|
|
|
|
void tst_customization::initTestCase()
|
|
{
|
|
QQmlDataTest::initTestCase();
|
|
|
|
qt_itemParentListener = new ItemParentListener;
|
|
}
|
|
|
|
void tst_customization::cleanupTestCase()
|
|
{
|
|
delete qt_itemParentListener;
|
|
qt_itemParentListener = nullptr;
|
|
}
|
|
|
|
void tst_customization::init()
|
|
{
|
|
QQmlDataTest::init();
|
|
|
|
engine = new QQmlEngine(this);
|
|
engine->addImportPath(testFile("styles"));
|
|
|
|
qtHookData[QHooks::AddQObject] = reinterpret_cast<quintptr>(&qt_addQObject);
|
|
qtHookData[QHooks::RemoveQObject] = reinterpret_cast<quintptr>(&qt_removeQObject);
|
|
}
|
|
|
|
void tst_customization::cleanup()
|
|
{
|
|
qtHookData[QHooks::AddQObject] = 0;
|
|
qtHookData[QHooks::RemoveQObject] = 0;
|
|
|
|
delete engine;
|
|
engine = nullptr;
|
|
|
|
qmlClearTypeRegistrations();
|
|
|
|
reset();
|
|
}
|
|
|
|
void tst_customization::reset()
|
|
{
|
|
qt_unparentedItemCount = 0;
|
|
qt_createdQObjects()->clear();
|
|
qt_destroyedQObjects()->clear();
|
|
qt_destroyedParentQObjects()->clear();
|
|
}
|
|
|
|
QObject* tst_customization::createControl(const QString &name, const QString &qml, QString *error)
|
|
{
|
|
QQmlComponent component(engine);
|
|
component.setData("import QtQuick; import QtQuick.Window; import QtQuick.Controls; " + name.toUtf8() + " { " + qml.toUtf8() + " }", QUrl());
|
|
QObject *obj = component.create();
|
|
if (!obj)
|
|
*error = component.errorString();
|
|
return obj;
|
|
}
|
|
|
|
void tst_customization::creation_data()
|
|
{
|
|
QTest::addColumn<QString>("style");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QStringList>("delegates");
|
|
|
|
// the "empty" style does not contain any delegates
|
|
for (const ControlInfo &control : ControlInfos)
|
|
QTest::newRow(qPrintable("empty:" + control.type)) << "empty" << control.type << QStringList();
|
|
|
|
// the "incomplete" style is missing bindings to the delegates (must be created regardless)
|
|
for (const ControlInfo &control : ControlInfos)
|
|
QTest::newRow(qPrintable("incomplete:" + control.type)) << "incomplete" << control.type << control.delegates;
|
|
|
|
// the "identified" style has IDs in the delegates (prevents deferred execution)
|
|
for (const ControlInfo &control : ControlInfos)
|
|
QTest::newRow(qPrintable("identified:" + control.type)) << "identified" << control.type << control.delegates;
|
|
|
|
// the "simple" style simulates a proper style and contains bindings to/in delegates
|
|
for (const ControlInfo &control : ControlInfos)
|
|
QTest::newRow(qPrintable("simple:" + control.type)) << "simple" << control.type << control.delegates;
|
|
|
|
// the "override" style overrides all delegates in the "simple" style
|
|
for (const ControlInfo &control : ControlInfos)
|
|
QTest::newRow(qPrintable("override:" + control.type)) << "override" << control.type << control.delegates;
|
|
}
|
|
|
|
void tst_customization::creation()
|
|
{
|
|
QFETCH(QString, style);
|
|
QFETCH(QString, type);
|
|
QFETCH(QStringList, delegates);
|
|
|
|
QQuickStyle::setStyle(style);
|
|
|
|
QString error;
|
|
QScopedPointer<QObject> control(createControl(type, "", &error));
|
|
QVERIFY2(control, qPrintable(error));
|
|
|
|
QByteArray templateType = "QQuick" + type.toUtf8();
|
|
QVERIFY2(control->inherits(templateType), qPrintable(type + " does not inherit " + templateType + " (" + control->metaObject()->className() + ")"));
|
|
|
|
// <control>-<style>
|
|
QString controlName = type.toLower() + "-" + style;
|
|
QCOMPARE(control->objectName(), controlName);
|
|
QVERIFY2(qt_createdQObjects()->removeOne(controlName), qPrintable(controlName + " was not created as expected"));
|
|
|
|
for (QString delegate : std::as_const(delegates)) {
|
|
QStringList properties = delegate.split(".", Qt::SkipEmptyParts);
|
|
|
|
// <control>-<delegate>-<style>(-<override>)
|
|
delegate.append("-" + style);
|
|
delegate.prepend(type.toLower() + "-");
|
|
|
|
QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected"));
|
|
|
|
// verify that the delegate instance has the expected object name
|
|
// in case of grouped properties, we must query the properties step by step
|
|
QObject *instance = control.data();
|
|
while (!properties.isEmpty()) {
|
|
QString property = properties.takeFirst();
|
|
instance = instance->property(property.toUtf8()).value<QObject *>();
|
|
QVERIFY2(instance, qPrintable("property was null: " + property));
|
|
}
|
|
QCOMPARE(instance->objectName(), delegate);
|
|
}
|
|
|
|
QEXPECT_FAIL("identified:ComboBox", "ComboBox::popup with an ID is created at construction time", Continue);
|
|
|
|
QVERIFY2(qt_createdQObjects()->isEmpty(), qPrintable("unexpectedly created: " + qt_createdQObjects->join(", ")));
|
|
QVERIFY2(qt_destroyedQObjects()->isEmpty(), qPrintable("unexpectedly destroyed: " + qt_destroyedQObjects->join(", ") + " were unexpectedly destroyed"));
|
|
|
|
QVERIFY2(qt_destroyedParentQObjects()->isEmpty(), qPrintable("delegates/children of: " + qt_destroyedParentQObjects->join(", ") + " were unexpectedly destroyed"));
|
|
}
|
|
|
|
void tst_customization::override_data()
|
|
{
|
|
QTest::addColumn<QString>("style");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QStringList>("delegates");
|
|
QTest::addColumn<QString>("nonDeferred");
|
|
QTest::addColumn<bool>("identify");
|
|
QTest::addColumn<bool>("controlNotCustomizable");
|
|
|
|
static const bool controlCustomizable = false;
|
|
|
|
// NOTE: delegates with IDs prevent deferred execution
|
|
|
|
// default delegates with IDs, override with custom delegates with no IDs
|
|
for (const ControlInfo &control : ControlInfos) {
|
|
QTest::newRow(qPrintable("identified:" + control.type)) << "identified" << control.type << control.delegates
|
|
<< "identified" << false << controlCustomizable;
|
|
}
|
|
|
|
// default delegates with no IDs, override with custom delegates with IDs
|
|
for (const ControlInfo &control : ControlInfos) {
|
|
QTest::newRow(qPrintable("simple:" + control.type)) << "simple" << control.type << control.delegates
|
|
<< "" << true << controlCustomizable;
|
|
}
|
|
|
|
// default delegates with IDs, override with custom delegates with IDs
|
|
for (const ControlInfo &control : ControlInfos) {
|
|
QTest::newRow(qPrintable("overidentified:" + control.type)) << "identified" << control.type << control.delegates
|
|
<< "identified" << true << controlCustomizable;
|
|
}
|
|
|
|
// Some styles don't support customization. Their fallbacks may, though,
|
|
// so only warn about the controls that that style provides.
|
|
const QHash<QString, QStringList> nonCustomizableControls = {
|
|
{
|
|
"macOS",
|
|
{
|
|
"Button", "CheckBox", "CheckDelegate", "ComboBox", "DelayButton", "Dial", "Frame",
|
|
"GroupBox", "ProgressBar", "RadioButton", "RadioDelegate", "SelectionRectangle",
|
|
"RangeSlider", "Slider", "SpinBox", "TextArea", "TextField", "TreeViewDelegate"
|
|
// TODO: ScrollView, ScrollBar
|
|
}
|
|
},
|
|
{
|
|
"Windows",
|
|
{
|
|
"Button", "CheckBox", "CheckDelegate", "ComboBox", "DelayButton", "Frame", "GroupBox",
|
|
"ProgressBar", "RadioButton", "RadioDelegate", "RangeSlider", "SelectionRectangle",
|
|
"ScrollBar", "Slider", "SpinBox", "Switch", "SwitchDelegate", "TextArea", "TextField"
|
|
}
|
|
}
|
|
};
|
|
|
|
// test that the built-in styles don't have undesired IDs in their delegates
|
|
const QStringList styles = QQuickStylePrivate::builtInStyles();
|
|
for (const QString &style : styles) {
|
|
for (const ControlInfo &control : ControlInfos) {
|
|
QTest::newRow(qPrintable(style + ":" + control.type)) << style << control.type << control.delegates
|
|
<< "" << false << nonCustomizableControls.value(style).contains(control.type);
|
|
}
|
|
}
|
|
}
|
|
|
|
void tst_customization::override()
|
|
{
|
|
QFETCH(QString, style);
|
|
QFETCH(QString, type);
|
|
QFETCH(QStringList, delegates);
|
|
QFETCH(QString, nonDeferred);
|
|
QFETCH(bool, identify);
|
|
QFETCH(bool, controlNotCustomizable);
|
|
|
|
QQuickStyle::setStyle(style);
|
|
|
|
QString qml;
|
|
qml += QString("objectName: '%1-%2-override'; ").arg(type.toLower()).arg(style);
|
|
for (const QString &delegate : delegates) {
|
|
QString id = identify ? QString("id: %1;").arg(delegate) : QString();
|
|
qml += QString("%1: Item { %2 objectName: '%3-%1-%4-override' } ").arg(delegate).arg(id.replace(".", "")).arg(type.toLower()).arg(style);
|
|
}
|
|
|
|
for (int i = 0; i < delegates.size(); ++i) {
|
|
if (controlNotCustomizable) {
|
|
// If the control can't be customized, ensure that we inform the user of that by checking that a warning is issued.
|
|
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(nonCustomizableWarning));
|
|
} else {
|
|
// By failing on warnings, we are notified when a control is made non-customizable without also updating this test.
|
|
QTest::failOnWarning(QRegularExpression(nonCustomizableWarning));
|
|
}
|
|
}
|
|
|
|
QString error;
|
|
QScopedPointer<QObject> control(createControl(type, qml, &error));
|
|
QVERIFY2(control, qPrintable(error));
|
|
|
|
// If there are no intentional IDs in the default delegates nor in the overridden custom
|
|
// delegates, no item should get un-parented during the creation process. An item being
|
|
// unparented means that a delegate got destroyed, so there must be an internal ID in one
|
|
// of the delegates in the tested style.
|
|
if (!identify && nonDeferred.isEmpty()) {
|
|
QEXPECT_FAIL("Universal:ApplicationWindow", "ApplicationWindow.qml contains an intentionally unparented FocusRectangle", Continue);
|
|
QCOMPARE(qt_unparentedItemCount, 0);
|
|
}
|
|
|
|
// <control>-<style>-override
|
|
QString controlName = type.toLower() + "-" + style + "-override";
|
|
QCOMPARE(control->objectName(), controlName);
|
|
QVERIFY2(qt_createdQObjects()->removeOne(controlName), qPrintable(controlName + " was not created as expected"));
|
|
|
|
for (QString delegate : std::as_const(delegates)) {
|
|
QStringList properties = delegate.split(".", Qt::SkipEmptyParts);
|
|
|
|
// <control>-<delegate>-<style>(-override)
|
|
delegate.append("-" + style);
|
|
delegate.prepend(type.toLower() + "-");
|
|
|
|
if (!nonDeferred.isEmpty())
|
|
QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected"));
|
|
|
|
delegate.append("-override");
|
|
QVERIFY2(qt_createdQObjects()->removeOne(delegate), qPrintable(delegate + " was not created as expected"));
|
|
|
|
// verify that the delegate instance has the expected object name
|
|
// in case of grouped properties, we must query the properties step by step
|
|
QObject *instance = control.data();
|
|
while (!properties.isEmpty()) {
|
|
QString property = properties.takeFirst();
|
|
instance = instance->property(property.toUtf8()).value<QObject *>();
|
|
QVERIFY2(instance, qPrintable("property was null: " + property));
|
|
}
|
|
QCOMPARE(instance->objectName(), delegate);
|
|
}
|
|
|
|
QEXPECT_FAIL("identified:ComboBox", "ComboBox::popup with an ID is created at construction time", Continue);
|
|
QEXPECT_FAIL("overidentified:ComboBox", "ComboBox::popup with an ID is created at construction time", Continue);
|
|
QVERIFY2(qt_createdQObjects()->isEmpty(), qPrintable("unexpectedly created: " + qt_createdQObjects->join(", ")));
|
|
|
|
if (!nonDeferred.isEmpty()) {
|
|
// There were items for which deferred execution was not possible.
|
|
for (QString delegateName : std::as_const(delegates)) {
|
|
if (!delegateName.contains("-"))
|
|
delegateName.append("-" + nonDeferred);
|
|
delegateName.prepend(type.toLower() + "-");
|
|
|
|
const int delegateIndex = qt_destroyedQObjects()->indexOf(delegateName);
|
|
QVERIFY2(delegateIndex == -1, qPrintable(delegateName + " was unexpectedly destroyed"));
|
|
|
|
const auto controlChildren = control->children();
|
|
const auto childIt = std::find_if(controlChildren.constBegin(), controlChildren.constEnd(), [delegateName](const QObject *child) {
|
|
return child->objectName() == delegateName;
|
|
});
|
|
// We test other delegates (like the background) here, so make sure we don't end up with XPASSes by using the wrong delegate.
|
|
if (delegateName.contains(QLatin1String("handle"))) {
|
|
QEXPECT_FAIL("identified:RangeSlider", "For some reason, items that are belong to grouped properties fail here", Abort);
|
|
QEXPECT_FAIL("overidentified:RangeSlider", "For some reason, items that are belong to grouped properties fail here", Abort);
|
|
}
|
|
if (delegateName.contains(QLatin1String("indicator"))) {
|
|
QEXPECT_FAIL("identified:SpinBox", "For some reason, items that are belong to grouped properties fail here", Abort);
|
|
QEXPECT_FAIL("overidentified:SpinBox", "For some reason, items that are belong to grouped properties fail here", Abort);
|
|
}
|
|
QVERIFY2(childIt != controlChildren.constEnd(), qPrintable(QString::fromLatin1(
|
|
"Expected delegate \"%1\" to still be a QObject child of \"%2\"").arg(delegateName).arg(controlName)));
|
|
|
|
const auto *delegate = qobject_cast<QQuickItem*>(*childIt);
|
|
// Ensure that the item is hidden, etc.
|
|
QVERIFY(delegate);
|
|
QCOMPARE(delegate->isVisible(), false);
|
|
QCOMPARE(delegate->parentItem(), nullptr);
|
|
}
|
|
}
|
|
|
|
QVERIFY2(qt_destroyedQObjects()->isEmpty(), qPrintable("unexpectedly destroyed: " + qt_destroyedQObjects->join(", ")));
|
|
}
|
|
|
|
void tst_customization::comboPopup()
|
|
{
|
|
QQuickStyle::setStyle("simple");
|
|
|
|
{
|
|
// test that ComboBox::popup is created when accessed
|
|
QQmlComponent component(engine);
|
|
component.setData("import QtQuick.Controls; ComboBox { }", QUrl());
|
|
QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(component.create()));
|
|
QVERIFY(comboBox);
|
|
|
|
QVERIFY(!qt_createdQObjects()->contains("combobox-popup-simple"));
|
|
|
|
QObject *popup = comboBox->property("popup").value<QObject *>();
|
|
QVERIFY(popup);
|
|
QVERIFY(qt_createdQObjects()->contains("combobox-popup-simple"));
|
|
}
|
|
|
|
reset();
|
|
|
|
{
|
|
// test that ComboBox::popup is created when it becomes visible
|
|
QQuickWindow window;
|
|
window.resize(300, 300);
|
|
window.show();
|
|
window.requestActivate();
|
|
QVERIFY(QTest::qWaitForWindowActive(&window));
|
|
|
|
QQmlComponent component(engine);
|
|
component.setData("import QtQuick.Controls; ComboBox { }", QUrl());
|
|
QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(component.create()));
|
|
QVERIFY(comboBox);
|
|
|
|
comboBox->setParentItem(window.contentItem());
|
|
QVERIFY(!qt_createdQObjects()->contains("combobox-popup-simple"));
|
|
|
|
QTest::mouseClick(&window, Qt::LeftButton, Qt::NoModifier, QPoint(1, 1));
|
|
QVERIFY(qt_createdQObjects()->contains("combobox-popup-simple"));
|
|
}
|
|
|
|
reset();
|
|
|
|
{
|
|
// test that ComboBox::popup is completed upon component completion (if appropriate)
|
|
QQmlComponent component(engine);
|
|
component.setData("import QtQuick; import QtQuick.Controls; ComboBox { id: control; contentItem: Item { visible: !control.popup.visible } popup: Popup { property bool wasCompleted: false; Component.onCompleted: wasCompleted = true } }", QUrl());
|
|
QScopedPointer<QQuickItem> comboBox(qobject_cast<QQuickItem *>(component.create()));
|
|
QVERIFY(comboBox);
|
|
|
|
QObject *popup = comboBox->property("popup").value<QObject *>();
|
|
QVERIFY(popup);
|
|
QCOMPARE(popup->property("wasCompleted"), QVariant(true));
|
|
}
|
|
}
|
|
|
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WINDOWS)
|
|
void tst_customization::noCustomizationWarningsForDefaultControls_data()
|
|
{
|
|
QTest::addColumn<QString>("style");
|
|
|
|
#ifdef Q_OS_MACOS
|
|
QTest::addRow("macOS") << "macOS";
|
|
#elif defined(Q_OS_WINDOWS)
|
|
QTest::addRow("Windows") << "Windows";
|
|
#endif
|
|
}
|
|
|
|
void tst_customization::noCustomizationWarningsForDefaultControls()
|
|
{
|
|
QFETCH(QString, style);
|
|
|
|
QQuickStyle::setStyle(style);
|
|
|
|
QTest::failOnWarning(QRegularExpression(nonCustomizableWarning));
|
|
|
|
for (const ControlInfo &controlInfo : ControlInfos) {
|
|
QString errorMessage;
|
|
QScopedPointer<QObject> control(createControl(controlInfo.type, {}, &errorMessage));
|
|
QVERIFY2(control, qPrintable(errorMessage));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
QTEST_MAIN(tst_customization)
|
|
|
|
#include "tst_customization.moc"
|