diff options
author | Laszlo Agocs <laszlo.agocs@theqtcompany.com> | 2016-03-08 12:15:03 +0100 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@theqtcompany.com> | 2016-03-08 12:15:50 +0100 |
commit | 75423185e4b03fabfeeee403f3bd9a6063078ee6 (patch) | |
tree | 6ad81ff44dda72126463fd3473be1567638b2436 /tests | |
parent | a2d4f1610db204e42722f20ff64716a14dc109c0 (diff) | |
parent | 3879b3f883dd66feddc190eb8bad330367a31ace (diff) |
Merge remote-tracking branch 'origin/dev' into wip/scenegraphng
Change-Id: I05f4e29dd3435bb843f9b2250574a61d688d5992
Diffstat (limited to 'tests')
48 files changed, 3014 insertions, 105 deletions
diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index fe3efcd948..253b050b15 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -185,6 +185,8 @@ private slots: void argumentEvaluationOrder(); + void v4FunctionWithoutQML(); + signals: void testSignal(); }; @@ -3794,6 +3796,30 @@ void tst_QJSEngine::argumentEvaluationOrder() } +class TestObject : public QObject +{ + Q_OBJECT +public: + TestObject() : called(false) {} + + bool called; + + Q_INVOKABLE void callMe(QQmlV4Function *) { + called = true; + } +}; + +void tst_QJSEngine::v4FunctionWithoutQML() +{ + TestObject obj; + QJSEngine engine; + QJSValue wrapper = engine.newQObject(&obj); + QQmlEngine::setObjectOwnership(&obj, QQmlEngine::CppOwnership); + QVERIFY(!obj.called); + wrapper.property("callMe").call(); + QVERIFY(obj.called); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qqmlbinding/data/disabledReadonly.qml b/tests/auto/qml/qqmlbinding/data/disabledReadonly.qml new file mode 100644 index 0000000000..2d1715364c --- /dev/null +++ b/tests/auto/qml/qqmlbinding/data/disabledReadonly.qml @@ -0,0 +1,14 @@ +import QtQuick 2.0 + +Item { + id: root + + readonly property string name: "John" + + Binding { + target: root + property: "name" + value: "Doe" + when: false + } +} diff --git a/tests/auto/qml/qqmlbinding/data/disabledUnknown.qml b/tests/auto/qml/qqmlbinding/data/disabledUnknown.qml new file mode 100644 index 0000000000..08e1cff5d1 --- /dev/null +++ b/tests/auto/qml/qqmlbinding/data/disabledUnknown.qml @@ -0,0 +1,12 @@ +import QtQuick 2.0 + +Item { + id: root + + Binding { + target: root + property: "unknown" + value: 42 + when: false + } +} diff --git a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp index 6eff3cce1f..3e8dfbdb12 100644 --- a/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp +++ b/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp @@ -47,6 +47,8 @@ private slots: void deletedObject(); void warningOnUnknownProperty(); void warningOnReadOnlyProperty(); + void disabledOnUnknownProperty(); + void disabledOnReadonlyProperty(); private: QQmlEngine engine; @@ -253,6 +255,32 @@ void tst_qqmlbinding::warningOnReadOnlyProperty() QCOMPARE(messageHandler.messages().first(), expectedMessage); } +void tst_qqmlbinding::disabledOnUnknownProperty() +{ + QQmlTestMessageHandler messageHandler; + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("disabledUnknown.qml")); + QQuickItem *item = qobject_cast<QQuickItem*>(c.create()); + QVERIFY(item); + delete item; + + QCOMPARE(messageHandler.messages().count(), 0); +} + +void tst_qqmlbinding::disabledOnReadonlyProperty() +{ + QQmlTestMessageHandler messageHandler; + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("disabledReadonly.qml")); + QQuickItem *item = qobject_cast<QQuickItem*>(c.create()); + QVERIFY(item); + delete item; + + QCOMPARE(messageHandler.messages().count(), 0); +} + QTEST_MAIN(tst_qqmlbinding) #include "tst_qqmlbinding.moc" diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_method_arg.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_arg.qml new file mode 100644 index 0000000000..b64a2e23c0 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_arg.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: false + Component.onCompleted: { + var data = new Uint8Array([1, 2, 3]); + var sum = byteArrayMethod_Sum(data.buffer); + ok = sum == 6; + } +} diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_method_overload.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_overload.qml new file mode 100644 index 0000000000..9e1c91810a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_overload.qml @@ -0,0 +1,7 @@ +import QtQuick 2.7 +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: false + Component.onCompleted: ok = byteArrayMethod_Overloaded(new ArrayBuffer()); +} diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_method_return.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_return.qml new file mode 100644 index 0000000000..5a4f9fec0b --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_method_return.qml @@ -0,0 +1,15 @@ +import QtQuick 2.7 +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: false + Component.onCompleted: { + var buf = byteArrayMethod_CountUp(1, 3); + var view = new DataView(buf); + ok = buf instanceof ArrayBuffer + && buf.byteLength == 3 + && view.getUint8(0) == 1 + && view.getUint8(1) == 2 + && view.getUint8(2) == 3; + } +} diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_property_get.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_property_get.qml new file mode 100644 index 0000000000..78ebb1abe1 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_property_get.qml @@ -0,0 +1,5 @@ +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: byteArrayProperty instanceof ArrayBuffer +} diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_property_set.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_property_set.qml new file mode 100644 index 0000000000..e8a51273ca --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_property_set.qml @@ -0,0 +1,11 @@ +import QtQuick 2.7 +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: false + onByteArraySignal: ok = byteArrayProperty instanceof ArrayBuffer + Component.onCompleted: { + byteArrayProperty = new ArrayBuffer(42); + ok = byteArrayProperty instanceof ArrayBuffer && byteArrayProperty.byteLength == 42; + } +} diff --git a/tests/auto/qml/qqmllanguage/data/arraybuffer_signal_arg.qml b/tests/auto/qml/qqmllanguage/data/arraybuffer_signal_arg.qml new file mode 100644 index 0000000000..d9f436e788 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/arraybuffer_signal_arg.qml @@ -0,0 +1,14 @@ +import QtQuick 2.7 +import Test 1.0 + +MyArrayBufferTestClass { + property bool ok: false + onByteArraySignal: { + var view = new DataView(arg); + ok = arg instanceof ArrayBuffer + && arg.byteLength == 2 + && view.getUint8(0) == 42 + && view.getUint8(1) == 43; + } + Component.onCompleted: emitByteArraySignal(42, 2) +} diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.errors.txt b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.errors.txt new file mode 100644 index 0000000000..33360e96cf --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.errors.txt @@ -0,0 +1 @@ +5:5:Invalid property assignment: Enum value "lowercaseEnumVal" cannot start with a lowercase letter diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.qml b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.qml new file mode 100644 index 0000000000..f6c3e9b404 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.1.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 +import Test 1.0 + +MyTypeObject { + intProperty: MyTypeObject.lowercaseEnumVal +} diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.errors.txt b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.errors.txt new file mode 100644 index 0000000000..33360e96cf --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.errors.txt @@ -0,0 +1 @@ +5:5:Invalid property assignment: Enum value "lowercaseEnumVal" cannot start with a lowercase letter diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.qml b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.qml new file mode 100644 index 0000000000..0dfe26c71d --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumCompileTime.2.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 +import Test 1.0 + +MyTypeObject { + enumProperty: MyTypeObjectSingleton.lowercaseEnumVal +} diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.1.qml b/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.1.qml new file mode 100644 index 0000000000..866b49e1d5 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.1.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import Test 1.0 + +MyTypeObject { + enumProperty: MyTypeObject.EnumVal1 + Component.onCompleted: { + var a = MyTypeObject.EnumVal1; + var b = MyTypeObject.lowercaseEnumVal + } +} diff --git a/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.2.qml b/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.2.qml new file mode 100644 index 0000000000..686977a11a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/lowercaseEnumRuntime.2.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 +import Test 1.0 + +MyTypeObject { + intProperty: MyTypeObjectSingleton.EnumVal1 + Component.onCompleted: { + var a = MyTypeObjectSingleton.EnumVal1; + var b = MyTypeObjectSingleton.lowercaseEnumVal; + } +} diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index 4153aae1dc..cc39422dce 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -29,6 +29,14 @@ #include <private/qqmlcompiler_p.h> +static QObject *myTypeObjectSingleton(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(engine) + Q_UNUSED(scriptEngine) + + return new MyTypeObject(); +} + void registerTypes() { qmlRegisterInterface<MyInterface>("MyInterface"); @@ -88,6 +96,10 @@ void registerTypes() qmlRegisterType<RootObjectInCreationTester>("Test", 1, 0, "RootObjectInCreationTester"); qmlRegisterType<MyCompositeBaseType>("Test", 1, 0, "MyCompositeBaseType"); + + qmlRegisterSingletonType<MyTypeObjectSingleton>("Test", 1, 0, "MyTypeObjectSingleton", myTypeObjectSingleton); + + qmlRegisterType<MyArrayBufferTestClass>("Test", 1, 0, "MyArrayBufferTestClass"); } QVariant myCustomVariantTypeConverter(const QString &data) diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 851ada9383..2344b6a03b 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -292,7 +292,7 @@ public: emit flagPropertyChanged(); } - enum MyEnum { EnumVal1, EnumVal2 }; + enum MyEnum { EnumVal1, EnumVal2, lowercaseEnumVal }; MyEnum enumPropertyValue; MyEnum enumProperty() const { return enumPropertyValue; @@ -592,6 +592,12 @@ signals: }; Q_DECLARE_OPERATORS_FOR_FLAGS(MyTypeObject::MyFlags) +// FIXME: If no subclass is used for the singleton registration with qmlRegisterSingletonType(), +// the valueTypes() test will fail. +class MyTypeObjectSingleton : public MyTypeObject +{ + Q_OBJECT +}; class MyContainer : public QObject { @@ -1084,6 +1090,58 @@ public: static QObject *qmlAttachedProperties(QObject *parent) { return new QObject(parent); } }; +class MyArrayBufferTestClass : public QObject +{ + Q_OBJECT + Q_PROPERTY(QByteArray byteArrayProperty READ byteArrayProperty WRITE setByteArrayProperty NOTIFY byteArrayPropertyChanged) + +signals: + void byteArrayPropertyChanged(); + void byteArraySignal(QByteArray arg); + +public: + QByteArray byteArrayPropertyValue; + QByteArray byteArrayProperty() const { + return byteArrayPropertyValue; + } + void setByteArrayProperty(const QByteArray &v) { + byteArrayPropertyValue = v; + emit byteArrayPropertyChanged(); + } + Q_INVOKABLE void emitByteArraySignal(char begin, char num) { + byteArraySignal(byteArrayMethod_CountUp(begin, num)); + } + Q_INVOKABLE int byteArrayMethod_Sum(QByteArray arg) { + int sum = 0; + for (int i = 0; i < arg.size(); ++i) { + sum += arg[i]; + } + return sum; + } + Q_INVOKABLE QByteArray byteArrayMethod_CountUp(char begin, int num) { + QByteArray ret; + for (int i = 0; i < num; ++i) { + ret.push_back(begin++); + } + return ret; + } + Q_INVOKABLE bool byteArrayMethod_Overloaded(QByteArray) { + return true; + } + Q_INVOKABLE bool byteArrayMethod_Overloaded(int) { + return false; + } + Q_INVOKABLE bool byteArrayMethod_Overloaded(QString) { + return false; + } + Q_INVOKABLE bool byteArrayMethod_Overloaded(QJSValue) { + return false; + } + Q_INVOKABLE bool byteArrayMethod_Overloaded(QVariant) { + return false; + } +}; + Q_DECLARE_METATYPE(MyEnum2Class::EnumB) Q_DECLARE_METATYPE(MyEnum1Class::EnumA) Q_DECLARE_METATYPE(Qt::TextFormat) diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 45507e83ea..dd7410dfd3 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -198,6 +198,10 @@ private slots: void crash2(); void globalEnums(); + void lowercaseEnumRuntime_data(); + void lowercaseEnumRuntime(); + void lowercaseEnumCompileTime_data(); + void lowercaseEnumCompileTime(); void literals_data(); void literals(); @@ -246,6 +250,9 @@ private slots: void deleteSingletons(); + void arrayBuffer_data(); + void arrayBuffer(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -3495,6 +3502,45 @@ void tst_qqmllanguage::globalEnums() delete o; } +void tst_qqmllanguage::lowercaseEnumRuntime_data() +{ + QTest::addColumn<QString>("file"); + QTest::addColumn<QString>("errorMessage"); + + QTest::newRow("enum from normal type") << "lowercaseEnumRuntime.1.qml" << ":8: TypeError: Cannot access enum value 'lowercaseEnumVal' of 'MyTypeObject', enum values need to start with an uppercase letter."; + QTest::newRow("enum from singleton type") << "lowercaseEnumRuntime.2.qml" << ":8: TypeError: Cannot access enum value 'lowercaseEnumVal' of 'MyTypeObjectSingleton', enum values need to start with an uppercase letter."; +} + +void tst_qqmllanguage::lowercaseEnumRuntime() +{ + QFETCH(QString, file); + QFETCH(QString, errorMessage); + + QQmlComponent component(&engine, testFileUrl(file)); + VERIFY_ERRORS(0); + QString warning = component.url().toString() + errorMessage; + QTest::ignoreMessage(QtWarningMsg, qPrintable(warning)); + delete component.create(); +} + +void tst_qqmllanguage::lowercaseEnumCompileTime_data() +{ + QTest::addColumn<QString>("file"); + QTest::addColumn<QString>("errorFile"); + + QTest::newRow("assignment to int property") << "lowercaseEnumCompileTime.1.qml" << "lowercaseEnumCompileTime.1.errors.txt"; + QTest::newRow("assignment to enum property") << "lowercaseEnumCompileTime.2.qml" << "lowercaseEnumCompileTime.2.errors.txt"; +} + +void tst_qqmllanguage::lowercaseEnumCompileTime() +{ + QFETCH(QString, file); + QFETCH(QString, errorFile); + + QQmlComponent component(&engine, testFileUrl(file)); + VERIFY_ERRORS(qPrintable(errorFile)); +} + void tst_qqmllanguage::literals_data() { QTest::addColumn<QString>("property"); @@ -4102,6 +4148,27 @@ void tst_qqmllanguage::deleteSingletons() QVERIFY(singleton.data() == 0); } +void tst_qqmllanguage::arrayBuffer_data() +{ + QTest::addColumn<QString>("file"); + QTest::newRow("arraybuffer_property_get") << "arraybuffer_property_get.qml"; + QTest::newRow("arraybuffer_property_set") << "arraybuffer_property_set.qml"; + QTest::newRow("arraybuffer_signal_arg") << "arraybuffer_signal_arg.qml"; + QTest::newRow("arraybuffer_method_arg") << "arraybuffer_method_arg.qml"; + QTest::newRow("arraybuffer_method_return") << "arraybuffer_method_return.qml"; + QTest::newRow("arraybuffer_method_overload") << "arraybuffer_method_overload.qml"; +} + +void tst_qqmllanguage::arrayBuffer() +{ + QFETCH(QString, file); + QQmlComponent component(&engine, testFile(file)); + VERIFY_ERRORS(0); + QObject *object = component.create(); + QVERIFY(object != 0); + QCOMPARE(object->property("ok").toBool(), true); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" diff --git a/tests/auto/quick/examples/tst_examples.cpp b/tests/auto/quick/examples/tst_examples.cpp index 240e8a791e..548b20a80a 100644 --- a/tests/auto/quick/examples/tst_examples.cpp +++ b/tests/auto/quick/examples/tst_examples.cpp @@ -291,7 +291,6 @@ void tst_examples::sgsnippets_data() void tst_examples::sgsnippets() { - QQuickWindow window; QFETCH(QString, file); @@ -301,19 +300,26 @@ void tst_examples::sgsnippets() QCOMPARE(component.status(), QQmlComponent::Ready); QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); + QQuickWindow *window = qobject_cast<QQuickWindow*>(object.data()); QQuickItem *root = qobject_cast<QQuickItem *>(object.data()); - if (!root) + if (!root && !window) { component.completeCreate(); - QVERIFY(root); + QVERIFY(false); + } + if (!window) + window = new QQuickWindow; - window.resize(240, 320); - window.show(); - QVERIFY(QTest::qWaitForWindowExposed(&window)); + window->resize(240, 320); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); - root->setParentItem(window.contentItem()); + if (root) + root->setParentItem(window->contentItem()); component.completeCreate(); qApp->processEvents(); + if (root) + delete window; } QTEST_MAIN(tst_examples) diff --git a/tests/auto/quick/qquickanimators/data/positionerWithAnimator.qml b/tests/auto/quick/qquickanimators/data/positionerWithAnimator.qml new file mode 100644 index 0000000000..bfd475266e --- /dev/null +++ b/tests/auto/quick/qquickanimators/data/positionerWithAnimator.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 + +Column { + width: 200 + height: 200 + + property alias repeater: repeater + property alias transition: transition + + anchors.centerIn: parent + populate: Transition { + id: transition + ScaleAnimator { + from: 0 + to: 1 + } + } + + Repeater { + id: repeater + model: ["red", "green", "blue"] + + Rectangle { + width: 100 + height: 100 + color: modelData + scale: 0 + } + } +} diff --git a/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp b/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp index 6943bf5045..29142dee12 100644 --- a/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp +++ b/tests/auto/quick/qquickanimators/tst_qquickanimators.cpp @@ -30,6 +30,8 @@ #include <QtQuick> #include <private/qquickanimator_p.h> +#include <private/qquickrepeater_p.h> +#include <private/qquicktransition_p.h> #include <QtQml> @@ -40,6 +42,7 @@ class tst_Animators: public QObject private slots: void testMultiWinAnimator_data(); void testMultiWinAnimator(); + void testTransitions(); }; void tst_Animators::testMultiWinAnimator_data() @@ -94,6 +97,28 @@ void tst_Animators::testMultiWinAnimator() QVERIFY(true); } +void tst_Animators::testTransitions() +{ + QQuickView view(QUrl::fromLocalFile("data/positionerWithAnimator.qml")); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + QVERIFY(view.rootObject()); + + QQuickRepeater *repeater = view.rootObject()->property("repeater").value<QQuickRepeater *>(); + QVERIFY(repeater); + + QQuickItem *child = repeater->itemAt(0); + QVERIFY(child); + QCOMPARE(child->scale(), qreal(0.0)); + + QQuickTransition *transition = view.rootObject()->property("transition").value<QQuickTransition *>(); + QVERIFY(transition); + + QTRY_VERIFY(transition->running()); + QTRY_VERIFY(!transition->running()); + QCOMPARE(child->scale(), qreal(1.0)); +} + #include "tst_qquickanimators.moc" QTEST_MAIN(tst_Animators) diff --git a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp index 1bd163fc4a..114f906736 100644 --- a/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp +++ b/tests/auto/quick/qquickapplication/tst_qquickapplication.cpp @@ -47,6 +47,7 @@ private slots: void active(); void state(); void layoutDirection(); + void font(); void inputMethod(); void styleHints(); void cleanup(); @@ -196,6 +197,21 @@ void tst_qquickapplication::layoutDirection() QCOMPARE(Qt::LayoutDirection(item->property("layoutDirection").toInt()), Qt::LeftToRight); } +void tst_qquickapplication::font() +{ + QQmlComponent component(&engine); + component.setData("import QtQuick 2.0; Item { property font defaultFont: Qt.application.font }", QUrl::fromLocalFile("")); + QQuickItem *item = qobject_cast<QQuickItem *>(component.create()); + QVERIFY(item); + QQuickView view; + item->setParentItem(view.rootObject()); + + QVariant defaultFontProperty = item->property("defaultFont"); + QVERIFY(defaultFontProperty.isValid()); + QCOMPARE(defaultFontProperty.type(), QVariant::Font); + QCOMPARE(defaultFontProperty.value<QFont>(), qApp->font()); +} + void tst_qquickapplication::inputMethod() { // technically not in QQuickApplication, but testing anyway here diff --git a/tests/auto/quick/qquickgridview/data/qtbug48870.qml b/tests/auto/quick/qquickgridview/data/qtbug48870.qml new file mode 100644 index 0000000000..7c0783fbb5 --- /dev/null +++ b/tests/auto/quick/qquickgridview/data/qtbug48870.qml @@ -0,0 +1,30 @@ +import QtQuick 2.6 + +Rectangle { + width: 500 + height: 500 + color: "blue" + + GridView { + id: view + objectName: "view" + anchors.fill: parent + model: testModel + cellWidth: 150 + cellHeight: 150 + readonly property int columns: Math.floor(width / cellWidth) + + delegate: Rectangle { + width: GridView.view.cellWidth + height: GridView.view.cellHeight + color: (row & 1) != (col & 1) ? "green" : "red" + readonly property int row: index / view.columns + readonly property int col: index % view.columns + + Text { + anchors.centerIn: parent + text: "Item " + index + } + } + } +} diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index b3474db7de..07c03a57d7 100644 --- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -36,6 +36,7 @@ #include <QtQml/qqmlincubator.h> #include <QtQml/qqmlcontext.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickitemview_p_p.h> #include <QtQuick/private/qquickgridview_p.h> #include <QtQuick/private/qquicktext_p.h> #include <QtQml/private/qqmllistmodel_p.h> @@ -205,6 +206,7 @@ private slots: void contentHeightWithDelayRemove(); void QTBUG_45640(); + void QTBUG_48870_fastModelUpdates(); void keyNavigationEnabled(); @@ -6628,6 +6630,42 @@ void tst_QQuickGridView::keyNavigationEnabled() QCOMPARE(gridView->currentIndex(), 1); } +void tst_QQuickGridView::QTBUG_48870_fastModelUpdates() +{ + StressTestModel model; + + QScopedPointer<QQuickView> window(createView()); + QQmlContext *ctxt = window->rootContext(); + ctxt->setContextProperty("testModel", &model); + + window->setSource(testFileUrl("qtbug48870.qml")); + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window.data())); + + QQuickGridView *view = findItem<QQuickGridView>(window->rootObject(), "view"); + QTRY_VERIFY(view != 0); + + QQuickItemViewPrivate *priv = QQuickItemViewPrivate::get(view); + bool nonUnique; + FxViewItem *item = Q_NULLPTR; + int expectedIdx; + QVERIFY(testVisibleItems(priv, &nonUnique, &item, &expectedIdx)); + + for (int i = 0; i < 10; i++) { + QTest::qWait(100); + QVERIFY2(testVisibleItems(priv, &nonUnique, &item, &expectedIdx), + qPrintable(!item ? QString("Unexpected null item") + : nonUnique ? QString("Non-unique item at %1 and %2").arg(item->index).arg(expectedIdx) + : QString("Found index %1, expected index is %3").arg(item->index).arg(expectedIdx))); + if (i % 3 != 0) { + if (i & 1) + flick(window.data(), QPoint(100, 200), QPoint(100, 0), 100); + else + flick(window.data(), QPoint(100, 200), QPoint(100, 400), 100); + } + } +} + QTEST_MAIN(tst_QQuickGridView) #include "tst_qquickgridview.moc" diff --git a/tests/auto/quick/qquickitem/data/childAtRectangle.qml b/tests/auto/quick/qquickitem/data/childAtRectangle.qml new file mode 100644 index 0000000000..d459c2b3f1 --- /dev/null +++ b/tests/auto/quick/qquickitem/data/childAtRectangle.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + width: 20 + height: 20 + + Rectangle { + id: rect1 + + color: "red" + x: 0 + y: 0 + width: 16 + height: 16 + } + + Rectangle { + id: rect2 + + color: "red" + x: 18 + y: 0 + width: 1 + height: 1 + } + + Rectangle { + id: rect3 + + x: 19 + y: 19 + width: 0 + height: 0 + } + +} + diff --git a/tests/auto/quick/qquickitem/tst_qquickitem.cpp b/tests/auto/quick/qquickitem/tst_qquickitem.cpp index 143f8f2b1c..4b2c86697e 100644 --- a/tests/auto/quick/qquickitem/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem/tst_qquickitem.cpp @@ -167,6 +167,8 @@ private slots: void contains_data(); void contains(); + void childAt(); + private: enum PaintOrderOp { @@ -1966,6 +1968,46 @@ void tst_qquickitem::contains() QCOMPARE(result.toBool(), contains); } +void tst_qquickitem::childAt() +{ + QQuickView view; + view.setSource(testFileUrl("childAtRectangle.qml")); + QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject()); + + int found = 0; + for (int i = 0; i < 16; i++) + { + if (root->childAt(i, 0)) + found++; + } + QCOMPARE(found, 16); + + found = 0; + for (int i = 0; i < 16; i++) + { + if (root->childAt(0, i)) + found++; + } + QCOMPARE(found, 16); + + found = 0; + for (int i = 0; i < 2; i++) + { + if (root->childAt(18 + i, 0)) + found++; + } + QCOMPARE(found, 1); + + found = 0; + for (int i = 0; i < 16; i++) + { + if (root->childAt(18, i)) + found++; + } + QCOMPARE(found, 1); + + QVERIFY(!root->childAt(19,19)); +} QTEST_MAIN(tst_qquickitem) diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_1.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_1.qml new file mode 100644 index 0000000000..d8b7833467 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_1.qml @@ -0,0 +1,13 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + Item { + objectName: "item" + focus: true + } +} diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_2.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_2.qml new file mode 100644 index 0000000000..445dc6d2a1 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_2.qml @@ -0,0 +1,12 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + Item { + objectName: "item" + } +} diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_3.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_3.qml new file mode 100644 index 0000000000..806d48ebeb --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_3.qml @@ -0,0 +1,16 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + Item { + objectName: "item2" + focus: true + } + Item { + objectName: "item2" + } +} diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_4.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_4.qml new file mode 100644 index 0000000000..6fcf513b51 --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_4.qml @@ -0,0 +1,15 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + Item { + objectName: "item2" + } + Item { + objectName: "item2" + } +} diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_5.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_5.qml new file mode 100644 index 0000000000..4f7b68a8de --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_5.qml @@ -0,0 +1,16 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + TextInput { + objectName: "item1" + activeFocusOnTab: true + } + Item { + objectName: "item2" + } +} diff --git a/tests/auto/quick/qquickitem2/data/qtbug_50516_2_6.qml b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_6.qml new file mode 100644 index 0000000000..223476327a --- /dev/null +++ b/tests/auto/quick/qquickitem2/data/qtbug_50516_2_6.qml @@ -0,0 +1,17 @@ +import QtQuick 2.1 +import Test 1.0 + +TabFence2 { + objectName: "root" + focus: true + width: 800 + height: 600 + TextInput { + objectName: "item1" + activeFocusOnTab: true + } + TextInput { + objectName: "item2" + activeFocusOnTab: true + } +} diff --git a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp index 607dbccaed..62ff09e698 100644 --- a/tests/auto/quick/qquickitem2/tst_qquickitem.cpp +++ b/tests/auto/quick/qquickitem2/tst_qquickitem.cpp @@ -70,6 +70,8 @@ private slots: void tabFence(); void qtbug_50516(); + void qtbug_50516_2_data(); + void qtbug_50516_2(); void keys(); void standardKeys_data(); @@ -305,6 +307,22 @@ public: QML_DECLARE_TYPE(TabFenceItem); +class TabFenceItem2 : public QQuickItem +{ + Q_OBJECT + +public: + TabFenceItem2(QQuickItem *parent = Q_NULLPTR) + : QQuickItem(parent) + { + QQuickItemPrivate *d = QQuickItemPrivate::get(this); + d->isTabFence = true; + setFlag(ItemIsFocusScope); + } +}; + +QML_DECLARE_TYPE(TabFenceItem2); + tst_QQuickItem::tst_QQuickItem() { } @@ -315,6 +333,7 @@ void tst_QQuickItem::initTestCase() qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem"); qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem"); qmlRegisterType<TabFenceItem>("Test", 1, 0, "TabFence"); + qmlRegisterType<TabFenceItem2>("Test", 1, 0, "TabFence2"); } void tst_QQuickItem::cleanup() @@ -1212,6 +1231,51 @@ void tst_QQuickItem::qtbug_50516() delete window; } +void tst_QQuickItem::qtbug_50516_2_data() +{ + QTest::addColumn<QString>("filename"); + QTest::addColumn<QString>("item1"); + QTest::addColumn<QString>("item2"); + + QTest::newRow("FocusScope TabFence with one Item(focused)") + << QStringLiteral("qtbug_50516_2_1.qml") << QStringLiteral("root") << QStringLiteral("root"); + QTest::newRow("FocusScope TabFence with one Item(unfocused)") + << QStringLiteral("qtbug_50516_2_2.qml") << QStringLiteral("root") << QStringLiteral("root"); + QTest::newRow("FocusScope TabFence with two Items(focused)") + << QStringLiteral("qtbug_50516_2_3.qml") << QStringLiteral("root") << QStringLiteral("root"); + QTest::newRow("FocusScope TabFence with two Items(unfocused)") + << QStringLiteral("qtbug_50516_2_4.qml") << QStringLiteral("root") << QStringLiteral("root"); + QTest::newRow("FocusScope TabFence with one Item and one TextInput(unfocused)") + << QStringLiteral("qtbug_50516_2_5.qml") << QStringLiteral("item1") << QStringLiteral("item1"); + QTest::newRow("FocusScope TabFence with two TextInputs(unfocused)") + << QStringLiteral("qtbug_50516_2_6.qml") << QStringLiteral("item1") << QStringLiteral("item2"); +} + +void tst_QQuickItem::qtbug_50516_2() +{ + QFETCH(QString, filename); + QFETCH(QString, item1); + QFETCH(QString, item2); + + QQuickView *window = new QQuickView(0); + window->setBaseSize(QSize(800,600)); + + window->setSource(testFileUrl(filename)); + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window)); + QVERIFY(QGuiApplication::focusWindow() == window); + QVERIFY(window->rootObject()->hasActiveFocus()); + + QQuickItem *contentItem = window->rootObject(); + QQuickItem *next = contentItem->nextItemInFocusChain(true); + QCOMPARE(next->objectName(), item1); + next = contentItem->nextItemInFocusChain(false); + QCOMPARE(next->objectName(), item2); + + delete window; +} + void tst_QQuickItem::keys() { QQuickView *window = new QQuickView(0); @@ -2931,12 +2995,12 @@ void tst_QQuickItem::childAt() child3.setParentItem(&parent); QCOMPARE(parent.childAt(0, 0), &child1); - QCOMPARE(parent.childAt(0, 100), &child1); + QCOMPARE(parent.childAt(0, 99), &child1); QCOMPARE(parent.childAt(25, 25), &child1); QCOMPARE(parent.childAt(25, 75), &child1); QCOMPARE(parent.childAt(75, 25), &child1); QCOMPARE(parent.childAt(75, 75), &child2); - QCOMPARE(parent.childAt(150, 150), &child2); + QCOMPARE(parent.childAt(149, 149), &child2); QCOMPARE(parent.childAt(25, 200), &child3); QCOMPARE(parent.childAt(0, 150), static_cast<QQuickItem *>(0)); QCOMPARE(parent.childAt(300, 300), static_cast<QQuickItem *>(0)); diff --git a/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml new file mode 100644 index 0000000000..6a1c0632ad --- /dev/null +++ b/tests/auto/quick/qquicklayouts/data/tst_gridlayout.qml @@ -0,0 +1,1036 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Layouts 1.1 + +Item { + id: container + width: 200 + height: 200 + TestCase { + id: testCase + name: "Tests_GridLayout" + when: windowShown + width: 200 + height: 200 + + Component { + id: layout_flow_Component + GridLayout { + columns: 4 + columnSpacing: 0 + rowSpacing: 0 + Repeater { + model: 6 + Rectangle { + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Text { text: index } + } + } + } + } + + function test_flow() + { + var layout = layout_flow_Component.createObject(container); + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[3], "itemRect", [30, 0, 10, 10]) + + tryCompare(layout.children[4], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[5], "itemRect", [10, 10, 10, 10]) + + layout.rows = 4 + layout.flow = GridLayout.TopToBottom + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 20, 10, 10]) + tryCompare(layout.children[3], "itemRect", [ 0, 30, 10, 10]) + + tryCompare(layout.children[4], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[5], "itemRect", [10, 10, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_flowLeftToRight_Component + GridLayout { + columns: 4 + columnSpacing: 0 + rowSpacing: 0 + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,1) + id: r2 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + Layout.rowSpan: 2 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // (0,1) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 0 + Layout.column: 1 + } + Rectangle { + // This one won't fit on the left and right sides of the big black box + // inserted at (3,0) + id: r4 + color: "red" + width: 20 + height: 20 + Layout.columnSpan: 2 + Layout.rowSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // continue flow from (0,2) + id: r5 + color: "black" + width: 20 + height: 20 + Layout.row: 0 + Layout.column: 2 + } + Repeater { + // ...and let the rest of the items automatically fill in the empty cells + model: 8 + Rectangle { + color: "red" + width: 20 + height: 20 + Text { text: index } + } + } + } + } + + function test_flowLeftToRight() { + var layout = layout_flowLeftToRight_Component.createObject(container); + compare(layout.implicitWidth, 80); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 20); + compare(layout.children[2].y, 0); + compare(layout.children[3].x, 0); + compare(layout.children[3].y, 60); + compare(layout.children[4].x, 40); + compare(layout.children[4].y, 0); + + // assumes that the repeater is the last item among the items it creates + compare(layout.children[5].x, 60); + compare(layout.children[5].y, 00); + compare(layout.children[6].x, 00); + compare(layout.children[6].y, 20); + compare(layout.children[7].x, 60); + compare(layout.children[7].y, 20); + compare(layout.children[8].x, 00); + compare(layout.children[8].y, 40); + compare(layout.children[9].x, 60); + compare(layout.children[9].y, 40); + compare(layout.children[10].x, 40); + compare(layout.children[10].y, 60); + compare(layout.children[11].x, 60); + compare(layout.children[11].y, 60); + compare(layout.children[12].x, 40); + compare(layout.children[12].y, 80); + + layout.destroy(); + } + + + Component { + id: layout_flowLeftToRightDefaultPositions_Component + GridLayout { + columns: 2 + columnSpacing: 0 + rowSpacing: 0 + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + // gray rectangles are items with just one row or just one column specified + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,0) + id: r2 + color: "gray" + width: 20 + height: 20 + Layout.row: 1 + } + Rectangle { + // (1,1) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + } + Rectangle { + // (1,0), warning emitted + id: r4 + color: "gray" + width: 20 + height: 20 + Layout.row: 1 + } + } + } + + function test_flowLeftToRightDefaultPositions() { + ignoreWarning("QGridLayoutEngine::addItem: Cell (1, 0) already taken"); + var layout = layout_flowLeftToRightDefaultPositions_Component.createObject(container); + compare(layout.implicitWidth, 40); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 0); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 20); + compare(layout.children[2].y, 20); + layout.destroy(); + } + + + Component { + id: layout_flowTopToBottom_Component + GridLayout { + rows: 4 + columnSpacing: 0 + rowSpacing: 0 + flow: GridLayout.TopToBottom + // red rectangles are auto-positioned + // black rectangles are explicitly positioned with row,column + Rectangle { + // First one should auto position itself at (0,0) + id: r1 + color: "red" + width: 20 + height: 20 + } + Rectangle { + // (1,1) + id: r2 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 1 + Layout.rowSpan: 2 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + } + Rectangle { + // (2,0) + id: r3 + color: "black" + width: 20 + height: 20 + Layout.row: 2 + Layout.column: 0 + } + Rectangle { + // This one won't fit on the left and right sides of the big black box + // inserted at (0,3) + id: r4 + color: "red" + width: 20 + height: 20 + Layout.rowSpan: 2 + Layout.fillHeight: true + } + Rectangle { + // continue flow from (1,0) + id: r5 + color: "black" + width: 20 + height: 20 + Layout.row: 1 + Layout.column: 0 + } + Repeater { + // ...and let the rest of the items automatically fill in the empty cells + model: 8 + Rectangle { + color: "red" + width: 20 + height: 20 + Text { text: index } + } + } + } + } + + function test_flowTopToBottom() { + var layout = layout_flowTopToBottom_Component.createObject(container); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 20); + compare(layout.children[2].x, 0); + compare(layout.children[2].y, 40); + compare(layout.children[3].x, 60); + compare(layout.children[3].y, 0); + compare(layout.children[4].x, 0); + compare(layout.children[4].y, 20); + + // The repeated items + compare(layout.children[5].x, 0); + compare(layout.children[5].y, 60); + compare(layout.children[6].x, 20); + compare(layout.children[6].y, 0); + compare(layout.children[7].x, 20); + compare(layout.children[7].y, 60); + compare(layout.children[8].x, 40); + compare(layout.children[8].y, 0); + compare(layout.children[9].x, 40); + compare(layout.children[9].y, 60); + compare(layout.children[10].x, 60); + compare(layout.children[10].y, 40); + compare(layout.children[11].x, 60); + compare(layout.children[11].y, 60); + compare(layout.children[12].x, 80); + compare(layout.children[12].y, 0); + + layout.destroy(); + } + + Component { + id: layout_spanAcrossEmptyRows_Component + /* This test has a large number of empty rows and columns, but there is one item + that spans across some of these empty rows/columns. + Do not modify (especially do not add items unless you understand what this is + testing) + */ + + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + // black rectangles are explicitly positioned with row,column + Rectangle { + // (0,0) + id: r0 + color: "black" + Layout.row: 0 + Layout.column: 0 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,1) + id: r1 + color: "black" + Layout.row: 0 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.rowSpan: 2 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,99) + id: r2 + color: "black" + Layout.row: 0 + Layout.column: 99 + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.maximumWidth: 40 + Layout.maximumHeight: 40 + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + + function test_spanAcrossEmptyRows() { + var layout = layout_spanAcrossEmptyRows_Component.createObject(container); + compare(layout.children[0].x, 0); + compare(layout.children[0].y, 0); + compare(layout.children[1].x, 20); + compare(layout.children[1].y, 0); + compare(layout.children[2].x, 40); + compare(layout.children[2].y, 0); + + compare(layout.implicitWidth, 60); + compare(layout.Layout.maximumWidth, 120); + + layout.destroy(); + } + + Component { + id: layout_spanIsMoreThanColumns_Component + + GridLayout { + columnSpacing: 1 + rowSpacing: 1 + columns: 2 + + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.columnSpan: 3 + } + } + } + + function test_spanIsMoreThanColumns() { + var layout = layout_spanIsMoreThanColumns_Component.createObject(container); + // item was not added, therefore implicit width is 0 + compare(layout.implicitWidth, 0); + layout.destroy(); + } + + function test_sizeHints() { + var layout = layout_spanAcrossEmptyRows_Component.createObject(container); + compare(layout.visible, true) + + var minWidth = layout.Layout.minimumWidth + var minHeight = layout.Layout.minimumHeight + + var prefWidth = layout.implicitWidth + var prefHeight = layout.implicitHeight + + var maxWidth = layout.Layout.maximumWidth + var maxHeight = layout.Layout.maximumHeight + + layout.visible = false + compare(minWidth, layout.Layout.minimumWidth) + compare(minHeight, layout.Layout.minimumHeight) + compare(prefWidth, layout.implicitWidth) + compare(prefHeight, layout.implicitHeight) + compare(maxWidth, layout.Layout.maximumWidth) + compare(maxHeight, layout.Layout.maximumHeight) + + layout.destroy(); + } + + Component { + id: layout_alignment_Component + GridLayout { + columns: 2 + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + // First one should auto position itself at (0,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.fillWidth: true + Layout.fillHeight: true + } + Rectangle { + // (0,1) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignBottom + } + Rectangle { + // (1,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignRight + } + Rectangle { + // (1,1) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignHCenter | Qt.AlignTop + } + Rectangle { + // (2,0) + property var itemRect: [x, y, width, height] + color: "red" + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + Layout.alignment: Qt.AlignRight + Layout.columnSpan: 2 + } + Rectangle { + // (3,0) + property var itemRect: [x, y, width, height] + baselineOffset: 7 + color: "red" + Layout.row: 3 + Layout.column: 0 + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignLeft | Qt.AlignBaseline + } + Rectangle { + // (3,1) + property var itemRect: [x, y, width, height] + baselineOffset: 7 + color: "red" + Layout.preferredWidth: 10 + Layout.preferredHeight: 10 + Layout.alignment: Qt.AlignRight | Qt.AlignBaseline + } + + } + } + + function test_alignment() + { + var layout = layout_alignment_Component.createObject(container); + layout.width = 60; + layout.height = 100; + + + tryCompare(layout.children[0], "itemRect", [ 0, 0, 40, 40]); + tryCompare(layout.children[1], "itemRect", [40, 20, 20, 20]); + tryCompare(layout.children[2], "itemRect", [20, 40, 20, 20]); + tryCompare(layout.children[3], "itemRect", [45, 40, 10, 10]); + tryCompare(layout.children[4], "itemRect", [30, 60, 30, 30]); + tryCompare(layout.children[5], "itemRect", [ 0, 90, 10, 10]); + tryCompare(layout.children[6], "itemRect", [50, 90, 10, 10]); + + + layout.children[1].Layout.alignment = Qt.AlignTop + tryCompare(layout.children[1], "x", 40); + tryCompare(layout.children[1], "y", 0); + + layout.children[2].Layout.alignment = Qt.AlignLeft + tryCompare(layout.children[2], "x", 0); + tryCompare(layout.children[2], "y", 40); + + layout.children[3].Layout.alignment = Qt.AlignLeft|Qt.AlignVCenter + tryCompare(layout.children[3], "x", 40); + tryCompare(layout.children[3], "y", 45); + + layout.children[4].Layout.alignment = Qt.AlignLeft + tryCompare(layout.children[4], "x", 0); + tryCompare(layout.children[4], "y", 60); + + layout.destroy(); + } + + + Component { + id: layout_rightToLeft_Component + GridLayout { + layoutDirection: Qt.RightToLeft + columnSpacing: 0 + rowSpacing: 0 + columns: 3 + Rectangle { + property var itemRect: [x, y, width, height] + color: "#cbffc4" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignCenter + } + Rectangle { + property var itemRect: [x, y, width, height] + color: "#c4d1ff" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignRight + } + Rectangle { + property var itemRect: [x, y, width, height] + color: "#ffd5c4" + Layout.preferredWidth: 50 + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignLeft + } + } + } + + function verifyIsRightToLeft(layout) + { + tryCompare(layout.children[0], "itemRect", [125, 0, 50, 50]); + tryCompare(layout.children[1], "itemRect", [60, 0, 50, 50]); + tryCompare(layout.children[2], "itemRect", [10, 0, 50, 50]); + } + + function verifyIsLeftToRight(layout) + { + tryCompare(layout.children[0], "itemRect", [5, 0, 50, 50]); + tryCompare(layout.children[1], "itemRect", [70, 0, 50, 50]); + tryCompare(layout.children[2], "itemRect", [120, 0, 50, 50]); + } + + function test_rightToLeft() + { + var layout = layout_rightToLeft_Component.createObject(container); + layout.width = 180; + layout.height = 50; + + // Right To Left + verifyIsRightToLeft(layout) + layout.LayoutMirroring.enabled = true + layout.layoutDirection = Qt.LeftToRight + verifyIsRightToLeft(layout) + + // Left To Right + layout.LayoutMirroring.enabled = false + layout.layoutDirection = Qt.LeftToRight + verifyIsLeftToRight(layout); + layout.LayoutMirroring.enabled = true + layout.layoutDirection = Qt.RightToLeft + verifyIsLeftToRight(layout); + + layout.LayoutMirroring.enabled = false + verifyIsRightToLeft(layout) + + layout.layoutDirection = Qt.LeftToRight + verifyIsLeftToRight(layout); + + layout.LayoutMirroring.enabled = true + verifyIsRightToLeft(layout) + + layout.destroy(); + } + + Component { + id: layout_columnsOrRowsChanged_Component + GridLayout { + id: layout + rowSpacing: 0 + columnSpacing: 0 + Repeater { + model: 4 + Rectangle { + property var itemRect: [x, y, width, height] + width: 10 + height: 10 + color: "#ff0000" + } + } + } + } + + function test_columnsChanged() + { + var layout = layout_columnsOrRowsChanged_Component.createObject(container); + layout.width = 40; + layout.height = 20; + tryCompare(layout.children[0], "itemRect", [ 0, 5, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 5, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 5, 10, 10]) + tryCompare(layout.children[3], "itemRect", [30, 5, 10, 10]) + + layout.columns = 2 + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[3], "itemRect", [20, 10, 10, 10]) + + layout.destroy() + } + + function test_rowsChanged() + { + var layout = layout_columnsOrRowsChanged_Component.createObject(container); + layout.flow = GridLayout.TopToBottom + layout.width = 20; + layout.height = 40; + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 10, 10, 10]) + tryCompare(layout.children[2], "itemRect", [ 0, 20, 10, 10]) + tryCompare(layout.children[3], "itemRect", [ 0, 30, 10, 10]) + + layout.rows = 2 + tryCompare(layout.children[0], "itemRect", [ 0, 5, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 25, 10, 10]) + tryCompare(layout.children[2], "itemRect", [10, 5, 10, 10]) + tryCompare(layout.children[3], "itemRect", [10, 25, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_columnOrRowChanged_Component + GridLayout { + id: layout + rowSpacing: 0 + columnSpacing: 0 + Rectangle { + property var itemRect: [x, y, width, height] + width: 10 + height: 10 + Layout.column: 0 + color: "#ff0000" + } + Rectangle { + property var itemRect: [x, y, width, height] + Layout.column: 1 + width: 10 + height: 10 + color: "#ff0000" + } + Rectangle { + property var itemRect: [x, y, width, height] + //Layout.column: 2 + width: 10 + height: 10 + color: "#ff0000" + } + } + } + + function test_columnOrRowChanged() + { + var layout = layout_columnOrRowChanged_Component.createObject(container); + layout.width = layout.implicitWidth + layout.height = layout.implicitHeight + // c0-c1-c2 + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.children[0].Layout.column = 3 + //c1-c2-c0 + tryCompare(layout.children[0], "itemRect", [20, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [10, 0, 10, 10]) + + layout.children[2].Layout.column = 4 + //c1-c0-c2 + tryCompare(layout.children[0], "itemRect", [10, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.children[0].Layout.row = 1 + // two rows, so we adjust it to its new implicitHeight + layout.height = layout.implicitHeight + //c1 c2 + // c0 + tryCompare(layout.children[0], "itemRect", [10, 10, 10, 10]) + tryCompare(layout.children[1], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[2], "itemRect", [20, 0, 10, 10]) + + layout.destroy() + } + + Component { + id: layout_baselines_Component + GridLayout { + id: layout + columnSpacing: 0 + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + baselineOffset: 10 + } + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + } + } + } + function test_baselines() + { + var layout = layout_baselines_Component.createObject(container); + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 0, 10, 10]) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 10) + + layout.children[0].Layout.alignment = Qt.AlignBaseline + layout.children[1].Layout.alignment = Qt.AlignBaseline + + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10, 10, 10, 10]) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 20) + + layout.destroy(); + } + + Component { + id: layout_spacings_Component + GridLayout { + id: layout + Repeater { + model: 2 + Rectangle { + property var itemRect: [x, y, width, height] + implicitWidth: 10 + implicitHeight: 10 + } + } + } + } + + function test_spacings() + { + var layout = layout_spacings_Component.createObject(container); + + // breaks down below -19. This is acceptable, since it means that the implicit size of the layout is negative + var testSpacings = [Number.NaN, 0, 10, -5, -19] + layout.rowSpacing = 0 + for (var i = 0; i < testSpacings.length; ++i) { + var sp = testSpacings[i] + if (isNaN(sp)) { + sp = 5 // Test defaults + } else { + layout.columnSpacing = sp + } + tryCompare(layout.children[0], "itemRect", [ 0, 0, 10, 10]) + tryCompare(layout.children[1], "itemRect", [10 + sp, 0, 10, 10]) + compare(layout.implicitWidth, 20 + sp) + } + + // do not crash + layout.columnSpacing = -100 + waitForRendering(layout) + verify(isFinite(layout.implicitWidth)) + layout.destroy(); + } + + Component { + id: layout_alignToPixelGrid_Component + GridLayout { + columns: 3 + rowSpacing: 0 + columnSpacing: 2 + Repeater { + model: 3*3 + Rectangle { + color: "red" + Layout.fillWidth: true + Layout.fillHeight: true + } + } + } + } + + function test_alignToPixelGrid() + { + var layout = layout_alignToPixelGrid_Component.createObject(container) + layout.width = 30 + layout.height = 28 + + var rectWidth = (layout.width - 2 * layout.columnSpacing)/3 + var rectHeight = layout.height/3 + + waitForRendering(layout); + + var sp = layout.columnSpacing + var idealGeom = [0,0,rectWidth,rectHeight] + for (var r = 0; r < 3; ++r) { + idealGeom[0] = 0 + idealGeom[2] = rectWidth + for (var c = 0; c < 3; ++c) { + var child = layout.children[3*r + c] + var visualGeom = [child.x, child.y, child.x + child.width, child.y + child.height] + + // verify that visualGeom is an integer number + for (var i = 0; i < 2; ++i) + compare(visualGeom[i] % 1, 0) + + // verify that x,y is no more than one pixel from idealGeom + fuzzyCompare(visualGeom[0], idealGeom[0], 1) + fuzzyCompare(visualGeom[1], idealGeom[1], 1) + + // verify that the visual size is no more than 1 pixel taller/wider than the ideal size. + verify(visualGeom[2] <= idealGeom[2] + 1) + verify(visualGeom[3] <= idealGeom[3] + 1) + idealGeom[0] = idealGeom[2] + sp + idealGeom[2] = idealGeom[0] + rectWidth + } + idealGeom[1] = idealGeom[3] + idealGeom[3] = idealGeom[1] + rectHeight + } + + layout.destroy() + } + + Component { + + id: layout_Margins_Component + GridLayout { + columns: 2 + rowSpacing: 0 + columnSpacing: 0 + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.margins: 10 + Layout.leftMargin: 2 + Layout.topMargin: 3 + Layout.rightMargin: 4 + Layout.bottomMargin: 4 + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.leftMargin: 4 + Layout.topMargin: 5 + Layout.rightMargin: 6 + Layout.bottomMargin: 6 + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.leftMargin: 3 + Layout.topMargin: 4 + Layout.rightMargin: 5 + Layout.bottomMargin: 5 + } + } + } + + function test_Margins() + { + var layout = layout_Margins_Component.createObject(container) + + compare(layout.implicitWidth, 3 + 20 + 5 + 4 + 20 + 6) + compare(layout.implicitHeight, 5 + 20 + 6 + 4 + 20 + 5) + layout.width = layout.implicitWidth + layout.height = layout.implicitHeight + + waitForRendering(layout) + + var c0 = layout.children[0] + var c1 = layout.children[1] + var c2 = layout.children[2] + + compare(c0.x, 2) + compare(c0.y, 5) + compare(c1.x, 3 + 20 + 5 + 4) + compare(c1.y, 5) + compare(c2.x, 3) + compare(c2.y, 5 + 20 + 6 + 4) + + // reset left|rightMargin. It should then use the generic "margins" property + c0.Layout.leftMargin = undefined + compare(layout.implicitWidth, 10 + 20 + 4 + 4 + 20 + 6) + c0.Layout.bottomMargin = undefined + compare(layout.implicitHeight, 3 + 20 + 10 + 4 + 20 + 5) + } + + Component { + id: layout_invalidateWhileRearranging_Component + + GridLayout { + columns: 1 + Rectangle { + height: 50 + Layout.fillWidth: true + color: 'blue' + } + + Rectangle { + height: 50 + Layout.fillWidth: true + color: 'red' + onYChanged: { + visible = false; + } + } + } + } + + function test_invalidateWhileRearranging_QTBUG_44139() + { + var layout = layout_invalidateWhileRearranging_Component.createObject(container) + + waitForRendering(layout); + verify(layout.children[1].visible == false); + layout.destroy() + } + } +} diff --git a/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml new file mode 100644 index 0000000000..4b47b396a3 --- /dev/null +++ b/tests/auto/quick/qquicklayouts/data/tst_rowlayout.qml @@ -0,0 +1,921 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Layouts 1.0 + +Item { + id: container + width: 200 + height: 200 + TestCase { + id: testCase + name: "Tests_RowLayout" + when: windowShown + width: 200 + height: 200 + + function itemRect(item) + { + return [item.x, item.y, item.width, item.height]; + } + + function test_fixedAndExpanding() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + width: 15; \ + spacing: 0; \ + property alias r1: _r1; \ + Rectangle { \ + id: _r1; \ + width: 5; \ + height: 10; \ + color: "#8080ff"; \ + Layout.fillWidth: false \ + } \ + property alias r2: _r2; \ + Rectangle { \ + id: _r2; \ + width: 10; \ + height: 20; \ + color: "#c0c0ff"; \ + Layout.fillWidth: true \ + } \ + } ' + + var lay = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(lay, 'implicitWidth', 15); + compare(lay.implicitHeight, 20); + compare(lay.height, 20); + lay.width = 30 + compare(lay.r1.x, 0); + compare(lay.r1.width, 5); + compare(lay.r2.x, 5); + compare(lay.r2.width, 25); + lay.destroy() + } + + function test_allExpanding() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + width: 15; \ + spacing: 0; \ + property alias r1: _r1; \ + Rectangle { \ + id: _r1; \ + width: 5; \ + height: 10; \ + color: "#8080ff"; \ + Layout.fillWidth: true \ + } \ + property alias r2: _r2; \ + Rectangle { \ + id: _r2; \ + width: 10; \ + height: 20; \ + color: "#c0c0ff"; \ + Layout.fillWidth: true \ + } \ + } ' + + var tmp = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(tmp, 'implicitWidth', 15); + compare(tmp.implicitHeight, 20); + compare(tmp.height, 20); + tmp.width = 30 + compare(tmp.r1.width, 10); + compare(tmp.r2.width, 20); + compare(tmp.Layout.minimumWidth, 0) + compare(tmp.Layout.maximumWidth, Number.POSITIVE_INFINITY) + tmp.destroy() + } + + function test_initialNestedLayouts() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + ColumnLayout { \ + id : col; \ + property alias row: _row; \ + objectName: "col"; \ + anchors.fill: parent; \ + RowLayout { \ + id : _row; \ + property alias r1: _r1; \ + property alias r2: _r2; \ + objectName: "row"; \ + spacing: 0; \ + Rectangle { \ + id: _r1; \ + color: "red"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + } \ + Rectangle { \ + id: _r2; \ + color: "green"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + Layout.fillWidth: true; \ + } \ + } \ + } ' + var col = Qt.createQmlObject(test_layoutStr, container, ''); + tryCompare(col, 'width', 200); + tryCompare(col.row, 'width', 200); + tryCompare(col.row.r1, 'width', 50); + tryCompare(col.row.r2, 'width', 150); + col.destroy() + } + + function test_implicitSize() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + RowLayout { \ + id: row; \ + objectName: "row"; \ + spacing: 0; \ + height: 30; \ + anchors.left: parent.left; \ + anchors.right: parent.right; \ + Rectangle { \ + color: "red"; \ + height: 2; \ + Layout.minimumWidth: 50; \ + } \ + Rectangle { \ + color: "green"; \ + width: 10; \ + Layout.minimumHeight: 4; \ + } \ + Rectangle { \ + implicitWidth: 1000; \ + Layout.maximumWidth: 40; \ + implicitHeight: 6 \ + } \ + } ' + var row = Qt.createQmlObject(test_layoutStr, container, ''); + compare(row.implicitWidth, 50 + 10 + 40); + compare(row.implicitHeight, 6); + row.destroy() + } + + function test_countGeometryChanges() { + var test_layoutStr = + 'import QtQuick 2.2; \ + import QtQuick.Layouts 1.0; \ + ColumnLayout { \ + id : col; \ + property alias row: _row; \ + objectName: "col"; \ + anchors.fill: parent; \ + RowLayout { \ + id : _row; \ + property alias r1: _r1; \ + property alias r2: _r2; \ + objectName: "row"; \ + spacing: 0; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Rectangle { \ + id: _r1; \ + color: "red"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Layout.fillWidth: true; \ + } \ + Rectangle { \ + id: _r2; \ + color: "green"; \ + implicitWidth: 50; \ + implicitHeight: 20; \ + property int counter : 0; \ + onWidthChanged: { ++counter; } \ + Layout.fillWidth: true; \ + } \ + } \ + } ' + var col = Qt.createQmlObject(test_layoutStr, container, ''); + compare(col.width, 200); + compare(col.row.width, 200); + compare(col.row.r1.width, 100); + compare(col.row.r2.width, 100); + compare(col.row.r1.counter, 1); + compare(col.row.r2.counter, 1); + verify(col.row.counter <= 2); + col.destroy() + } + + Component { + id: layoutItem_Component + Rectangle { + implicitWidth: 20 + implicitHeight: 20 + } + } + + Component { + id: columnLayoutItem_Component + ColumnLayout { + spacing: 0 + } + } + + Component { + id: layout_addAndRemoveItems_Component + RowLayout { + spacing: 0 + } + } + + function test_addAndRemoveItems() + { + var layout = layout_addAndRemoveItems_Component.createObject(container) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + + var rect0 = layoutItem_Component.createObject(layout) + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 20) + + var rect1 = layoutItem_Component.createObject(layout) + rect1.Layout.preferredWidth = 30; + rect1.Layout.preferredHeight = 30; + compare(layout.implicitWidth, 50) + compare(layout.implicitHeight, 30) + + var col = columnLayoutItem_Component.createObject(layout) + var rect2 = layoutItem_Component.createObject(col) + rect2.Layout.fillHeight = true + var rect3 = layoutItem_Component.createObject(col) + rect3.Layout.fillHeight = true + + compare(layout.implicitWidth, 70) + compare(col.implicitHeight, 40) + compare(layout.implicitHeight, 40) + + rect3.destroy() + wait(0) // this will hopefully effectuate the destruction of the object + + col.destroy() + wait(0) + compare(layout.implicitWidth, 50) + compare(layout.implicitHeight, 30) + + rect0.destroy() + wait(0) + compare(layout.implicitWidth, 30) + compare(layout.implicitHeight, 30) + + rect1.destroy() + wait(0) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + + layout.destroy() + } + + Component { + id: layout_alignment_Component + RowLayout { + spacing: 0 + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.fillHeight: true + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + // use default alignment + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignTop + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignVCenter + } + Rectangle { + color: "red" + Layout.preferredWidth: 20 + Layout.preferredHeight: 20 + Layout.alignment: Qt.AlignBottom + } + } + } + + function test_alignment() + { + var layout = layout_alignment_Component.createObject(container); + layout.width = 100; + layout.height = 40; + + compare(itemRect(layout.children[0]), [ 0, 0, 20, 40]); + compare(itemRect(layout.children[1]), [20, 10, 20, 20]); + compare(itemRect(layout.children[2]), [40, 0, 20, 20]); + compare(itemRect(layout.children[3]), [60, 10, 20, 20]); + compare(itemRect(layout.children[4]), [80, 20, 20, 20]); + layout.destroy(); + } + + Component { + id: layout_sizeHintNormalization_Component + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + id: r1 + color: "red" + Layout.minimumWidth: 1 + Layout.preferredWidth: 2 + Layout.maximumWidth: 3 + + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 + Layout.maximumHeight: 20 + Layout.fillWidth: true + } + } + } + + function test_sizeHintNormalization_data() { + return [ + { tag: "fallbackValues", widthHints: [-1, -1, -1], implicitWidth: 42, expected:[0,42,Number.POSITIVE_INFINITY]}, + { tag: "acceptZeroWidths", widthHints: [0, 0, 0], implicitWidth: 42, expected:[0,0,0]}, + { tag: "123", widthHints: [1,2,3], expected:[1,2,3]}, + { tag: "132", widthHints: [1,3,2], expected:[1,2,2]}, + { tag: "213", widthHints: [2,1,3], expected:[2,2,3]}, + { tag: "231", widthHints: [2,3,1], expected:[1,1,1]}, + { tag: "321", widthHints: [3,2,1], expected:[1,1,1]}, + { tag: "312", widthHints: [3,1,2], expected:[2,2,2]}, + + { tag: "1i3", widthHints: [1,-1,3], implicitWidth: 2, expected:[1,2,3]}, + { tag: "1i2", widthHints: [1,-1,2], implicitWidth: 3, expected:[1,2,2]}, + { tag: "2i3", widthHints: [2,-1,3], implicitWidth: 1, expected:[2,2,3]}, + { tag: "2i1", widthHints: [2,-1,1], implicitWidth: 3, expected:[1,1,1]}, + { tag: "3i1", widthHints: [3,-1,1], implicitWidth: 2, expected:[1,1,1]}, + { tag: "3i2", widthHints: [3,-1,2], implicitWidth: 1, expected:[2,2,2]}, + ]; + } + + function test_sizeHintNormalization(data) { + var layout = layout_sizeHintNormalization_Component.createObject(container); + if (data.implicitWidth !== undefined) { + layout.children[0].implicitWidth = data.implicitWidth + } + layout.children[0].Layout.minimumWidth = data.widthHints[0]; + layout.children[0].Layout.preferredWidth = data.widthHints[1]; + layout.children[0].Layout.maximumWidth = data.widthHints[2]; + wait(0); // Trigger processEvents() (allow LayoutRequest to be processed) + var normalizedResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth] + compare(normalizedResult, data.expected); + layout.destroy(); + } + + Component { + id: layout_sizeHint_Component + RowLayout { + property int implicitWidthChangedCount : 0 + onImplicitWidthChanged: { ++implicitWidthChangedCount } + GridLayout { + columnSpacing: 0 + rowSpacing: 0 + Rectangle { + id: r1 + color: "red" + Layout.minimumWidth: 1 + Layout.preferredWidth: 2 + Layout.maximumWidth: 3 + + Layout.minimumHeight: 20 + Layout.preferredHeight: 20 + Layout.maximumHeight: 20 + Layout.fillWidth: true + } + } + } + } + + function test_sizeHint_data() { + return [ + { tag: "propagateNone", layoutHints: [10, 20, 30], childHints: [11, 21, 31], expected:[10, 20, 30]}, + { tag: "propagateMinimumWidth", layoutHints: [-1, 20, 30], childHints: [10, 21, 31], expected:[10, 20, 30]}, + { tag: "propagatePreferredWidth", layoutHints: [10, -1, 30], childHints: [11, 20, 31], expected:[10, 20, 30]}, + { tag: "propagateMaximumWidth", layoutHints: [10, 20, -1], childHints: [11, 21, 30], expected:[10, 20, 30]}, + { tag: "propagateAll", layoutHints: [-1, -1, -1], childHints: [10, 20, 30], expected:[10, 20, 30]}, + { tag: "propagateCrazy", layoutHints: [-1, -1, -1], childHints: [40, 21, 30], expected:[30, 30, 30]}, + { tag: "expandMinToExplicitPref", layoutHints: [-1, 1, -1], childHints: [11, 21, 31], expected:[ 1, 1, 31]}, + { tag: "expandMaxToExplicitPref", layoutHints: [-1, 99, -1], childHints: [11, 21, 31], expected:[11, 99, 99]}, + { tag: "expandAllToExplicitMin", layoutHints: [99, -1, -1], childHints: [11, 21, 31], expected:[99, 99, 99]}, + { tag: "expandPrefToExplicitMin", layoutHints: [24, -1, -1], childHints: [11, 21, 31], expected:[24, 24, 31]}, + { tag: "boundPrefToExplicitMax", layoutHints: [-1, -1, 19], childHints: [11, 21, 31], expected:[11, 19, 19]}, + { tag: "boundAllToExplicitMax", layoutHints: [-1, -1, 9], childHints: [11, 21, 31], expected:[ 9, 9, 9]}, + ]; + } + + function itemSizeHints(item) { + return [item.Layout.minimumWidth, item.implicitWidth, item.Layout.maximumWidth] + } + + function test_sizeHint(data) { + var layout = layout_sizeHint_Component.createObject(container) + + var grid = layout.children[0] + grid.Layout.minimumWidth = data.layoutHints[0] + grid.Layout.preferredWidth = data.layoutHints[1] + grid.Layout.maximumWidth = data.layoutHints[2] + + var child = grid.children[0] + if (data.implicitWidth !== undefined) { + child.implicitWidth = data.implicitWidth + } + child.Layout.minimumWidth = data.childHints[0] + child.Layout.preferredWidth = data.childHints[1] + child.Layout.maximumWidth = data.childHints[2] + + var effectiveSizeHintResult = [layout.Layout.minimumWidth, layout.implicitWidth, layout.Layout.maximumWidth] + compare(effectiveSizeHintResult, data.expected) + layout.destroy() + } + + function test_sizeHintPropagationCount() { + var layout = layout_sizeHint_Component.createObject(container) + var child = layout.children[0].children[0] + + child.Layout.minimumWidth = -1 + compare(itemSizeHints(layout), [0, 2, 3]) + child.Layout.preferredWidth = -1 + compare(itemSizeHints(layout), [0, 0, 3]) + child.Layout.maximumWidth = -1 + compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY]) + layout.Layout.maximumWidth = 1000 + compare(itemSizeHints(layout), [0, 0, 1000]) + layout.Layout.maximumWidth = -1 + compare(itemSizeHints(layout), [0, 0, Number.POSITIVE_INFINITY]) + + layout.implicitWidthChangedCount = 0 + child.Layout.minimumWidth = 10 + compare(itemSizeHints(layout), [10, 10, Number.POSITIVE_INFINITY]) + compare(layout.implicitWidthChangedCount, 1) + + child.Layout.preferredWidth = 20 + compare(itemSizeHints(layout), [10, 20, Number.POSITIVE_INFINITY]) + compare(layout.implicitWidthChangedCount, 2) + + child.Layout.maximumWidth = 30 + compare(itemSizeHints(layout), [10, 20, 30]) + compare(layout.implicitWidthChangedCount, 2) + + child.Layout.maximumWidth = 15 + compare(itemSizeHints(layout), [10, 15, 15]) + compare(layout.implicitWidthChangedCount, 3) + + child.Layout.maximumWidth = 30 + compare(itemSizeHints(layout), [10, 20, 30]) + compare(layout.implicitWidthChangedCount, 4) + + layout.Layout.maximumWidth = 29 + compare(layout.Layout.maximumWidth, 29) + layout.Layout.maximumWidth = -1 + compare(layout.Layout.maximumWidth, 30) + + layout.destroy() + } + + Component { + id: layout_change_implicitWidth_during_rearrange + ColumnLayout { + width: 100 + height: 20 + RowLayout { + spacing: 0 + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: false + implicitWidth: height + color: "red" + } + Rectangle { + Layout.fillHeight: true + Layout.fillWidth: true + color: "blue" + } + } + } + } + + function test_change_implicitWidth_during_rearrange() { + var layout = layout_change_implicitWidth_during_rearrange.createObject(container) + var red = layout.children[0].children[0] + var blue = layout.children[0].children[1] + waitForRendering(layout); + tryCompare(red, 'width', 20) + tryCompare(blue, 'width', 80) + layout.height = 40 + tryCompare(red, 'width', 40) + tryCompare(blue, 'width', 60) + layout.destroy() + } + + Component { + id: layout_addIgnoredItem_Component + RowLayout { + spacing: 0 + Rectangle { + id: r + } + } + } + + function test_addIgnoredItem() + { + var layout = layout_addIgnoredItem_Component.createObject(container) + compare(layout.implicitWidth, 0) + compare(layout.implicitHeight, 0) + var r = layout.children[0] + r.Layout.preferredWidth = 20 + r.Layout.preferredHeight = 30 + compare(layout.implicitWidth, 20) + compare(layout.implicitHeight, 30) + + layout.destroy(); + } + + + Component { + id: layout_rowLayout_Component + RowLayout { + } + } + + function test_stretchItem_data() + { + return [ + { expectedWidth: 0}, + { preferredWidth: 20, expectedWidth: 20}, + { preferredWidth: 0, expectedWidth: 0}, + { preferredWidth: 20, fillWidth: true, expectedWidth: 100}, + { width: 20, fillWidth: true, expectedWidth: 100}, + { width: 0, fillWidth: true, expectedWidth: 100}, + { preferredWidth: 0, fillWidth: true, expectedWidth: 100}, + { preferredWidth: 1, maximumWidth: 0, fillWidth: true, expectedWidth: 0}, + { preferredWidth: 0, minimumWidth: 1, expectedWidth: 1}, + ]; + } + + function test_stretchItem(data) + { + var layout = layout_rowLayout_Component.createObject(container) + var r = layoutItem_Component.createObject(layout) + // Reset previously relevant properties + r.width = 0 + r.implicitWidth = 0 + compare(layout.implicitWidth, 0) + + if (data.preferredWidth !== undefined) + r.Layout.preferredWidth = data.preferredWidth + if (data.fillWidth !== undefined) + r.Layout.fillWidth = data.fillWidth + if (data.width !== undefined) + r.width = data.width + if (data.minimumWidth !== undefined) + r.Layout.minimumWidth = data.minimumWidth + if (data.maximumWidth !== undefined) + r.Layout.maximumWidth = data.maximumWidth + + layout.width = 100 + + compare(r.width, data.expectedWidth) + + layout.destroy(); + } + + Component { + id: layout_alignToPixelGrid_Component + RowLayout { + spacing: 2 + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.alignment: Qt.AlignVCenter + } + Rectangle { + implicitWidth: 10 + implicitHeight: 10 + Layout.alignment: Qt.AlignVCenter + } + } + } + function test_alignToPixelGrid() + { + var layout = layout_alignToPixelGrid_Component.createObject(container) + layout.width = 21 + layout.height = 21 + var r0 = layout.children[0] + compare(r0.x, 0) // 0.0 + compare(r0.y, 6) // 5.5 + var r1 = layout.children[1] + compare(r1.x, 12) // 11.5 + compare(r1.y, 6) // 5.5 + layout.destroy(); + } + + Component { + id: test_distributeToPixelGrid_Component + RowLayout { + spacing: 0 + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 50 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 20 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + Rectangle { + color: 'red' + Layout.minimumWidth: 10 + Layout.preferredWidth: 70 + Layout.maximumWidth: 90 + Layout.fillWidth: true + implicitHeight: 10 + } + } + } + + function test_distributeToPixelGrid_data() { + return [ + { tag: "narrow", spacing: 0, width: 60 }, + { tag: "belowPreferred", spacing: 0, width: 130 }, + { tag: "belowPreferredWithSpacing", spacing: 10, width: 130 }, + { tag: "abovePreferred", spacing: 0, width: 150 }, + { tag: "stretchSomethingToMaximum", spacing: 0, width: 240, + expected: [90, 60, 90] }, + { tag: "minSizeHasFractions", spacing: 2, width: 31 + 4, hints: [{min: 10+1/3}, {min: 10+1/3}, {min: 10+1/3}], + /*expected: [11, 11, 11]*/ }, /* verify that nothing gets allocated a size smaller than its minimum */ + { tag: "maxSizeHasFractions", spacing: 2, width: 271 + 4, hints: [{max: 90+1/3}, {max: 90+1/3}, {max: 90+1/3}], + /*expected: [90, 90, 90]*/ }, /* verify that nothing gets allocated a size larger than its maximum */ + { tag: "fixedSizeHasFractions", spacing: 2, width: 31 + 4, hints: [{min: 10+1/3, max: 10+1/3}, {min: 10+1/3, max: 10+1/3}, {min: 10+1/3, max: 10+1/3}], + /*expected: [11, 11, 11]*/ }, /* verify that nothing gets allocated a size smaller than its minimum */ + ]; + } + + function test_distributeToPixelGrid(data) + { + // CONFIGURATION + var layout = test_distributeToPixelGrid_Component.createObject(container) + layout.spacing = data.spacing + layout.width = data.width + layout.height = 10 + var kids = layout.children + + if (data.hasOwnProperty('hints')) { + var hints = data.hints + for (var i = 0; i < hints.length; ++i) { + var h = hints[i] + if (h.hasOwnProperty('min')) + kids[i].Layout.minimumWidth = h.min + if (h.hasOwnProperty('pref')) + kids[i].Layout.preferredWidth = h.pref + if (h.hasOwnProperty('max')) + kids[i].Layout.maximumWidth = h.max + } + } + waitForRendering(layout) + + var sum = 2 * layout.spacing + // TEST + for (var i = 0; i < kids.length; ++i) { + compare(kids[i].x % 1, 0) // checks if position is a whole integer + // verify if the items are within the size constraints as specified + verify(kids[i].width >= kids[i].Layout.minimumWidth) + verify(kids[i].width <= kids[i].Layout.maximumWidth) + if (data.hasOwnProperty('expected')) + compare(kids[i].width, data.expected[i]) + sum += kids[i].width + } + fuzzyCompare(sum, layout.width, 1) + + layout.destroy(); + } + + + + Component { + id: layout_deleteLayout + ColumnLayout { + property int dummyproperty: 0 // yes really - its needed + RowLayout { + Text { text: "label1" } // yes, both are needed + Text { text: "label2" } + } + } + } + + function test_destroyLayout() + { + var layout = layout_deleteLayout.createObject(container) + layout.children[0].children[0].visible = true + layout.visible = false + layout.destroy() // Do not crash + } + + function test_sizeHintWithHiddenChildren(data) { + var layout = layout_sizeHint_Component.createObject(container) + var grid = layout.children[0] + var child = grid.children[0] + + // Implicit sizes are not affected by the visibility of the parent layout. + // This is in order for the layout to know the preferred size it should show itself at. + compare(grid.visible, true) // LAYOUT SHOWN + compare(grid.implicitWidth, 2); + child.visible = false + compare(grid.implicitWidth, 0); + child.visible = true + compare(grid.implicitWidth, 2); + + grid.visible = false // LAYOUT HIDDEN + compare(grid.implicitWidth, 2); + child.visible = false + expectFail('', 'If GridLayout is hidden, GridLayout is not notified when child is explicitly hidden') + compare(grid.implicitWidth, 0); + child.visible = true + compare(grid.implicitWidth, 2); + + layout.destroy(); + } + + Component { + id: row_sizeHint_Component + Row { + Rectangle { + id: r1 + color: "red" + width: 2 + height: 20 + } + } + } + + function test_sizeHintWithHiddenChildrenForRow(data) { + var row = row_sizeHint_Component.createObject(container) + var child = row.children[0] + compare(row.visible, true) // POSITIONER SHOWN + compare(row.implicitWidth, 2); + child.visible = false + tryCompare(row, 'implicitWidth', 0); + child.visible = true + tryCompare(row, 'implicitWidth', 2); + + row.visible = false // POSITIONER HIDDEN + compare(row.implicitWidth, 2); + child.visible = false + expectFail('', 'If Row is hidden, Row is not notified when child is explicitly hidden') + compare(row.implicitWidth, 0); + child.visible = true + compare(row.implicitWidth, 2); + } + + Component { + id: rearrangeNestedLayouts_Component + RowLayout { + id: layout + anchors.fill: parent + width: 200 + height: 20 + RowLayout { + id: row + spacing: 0 + + Rectangle { + id: fixed + color: 'red' + implicitWidth: 20 + implicitHeight: 20 + } + Rectangle { + id: filler + color: 'grey' + Layout.fillWidth: true + implicitHeight: 20 + } + } + } + } + + function test_rearrangeNestedLayouts() + { + var layout = rearrangeNestedLayouts_Component.createObject(container) + var fixed = layout.children[0].children[0] + var filler = layout.children[0].children[1] + + compare(itemRect(fixed), [0,0,20,20]) + compare(itemRect(filler), [20,0,180,20]) + + fixed.implicitWidth = 100 + waitForRendering(layout) + compare(itemRect(fixed), [0,0,100,20]) + compare(itemRect(filler), [100,0,100,20]) + } + + Component { + id: changeChildrenOfHiddenLayout_Component + RowLayout { + property int childCount: 1 + Repeater { + model: parent.childCount + Text { + text: 'Just foo it' + } + } + } + } + function test_changeChildrenOfHiddenLayout() + { + var layout = changeChildrenOfHiddenLayout_Component.createObject(container) + var child = layout.children[0] + waitForRendering(layout) + layout.visible = false + waitForRendering(layout) + // Remove and add children to the hidden layout.. + layout.childCount = 0 + waitForRendering(layout) + layout.childCount = 1 + waitForRendering(layout) + layout.destroy() + } + } +} diff --git a/tests/auto/quick/qquicklayouts/qquicklayouts.pro b/tests/auto/quick/qquicklayouts/qquicklayouts.pro new file mode 100644 index 0000000000..9ed3e076be --- /dev/null +++ b/tests/auto/quick/qquicklayouts/qquicklayouts.pro @@ -0,0 +1,13 @@ +QT += core-private gui-private qml-private +TEMPLATE=app +TARGET=tst_qquicklayouts + +CONFIG += qmltestcase +SOURCES += tst_qquicklayouts.cpp + +TESTDATA = data/* + +OTHER_FILES += \ + data/tst_rowlayout.qml \ + data/tst_gridlayout.qml + diff --git a/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp new file mode 100644 index 0000000000..373019091f --- /dev/null +++ b/tests/auto/quick/qquicklayouts/tst_qquicklayouts.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtQuickTest/quicktest.h> +QUICK_TEST_MAIN(qquicklayouts) diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 405acad165..658ffa1f57 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -8264,99 +8264,9 @@ void tst_QQuickListView::keyNavigationEnabled() QCOMPARE(listView->currentIndex(), 1); } -static bool testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx) -{ - QHash<QQuickItem*, int> uniqueItems; - - int skip = 0; - for (int i = 0; i < priv->visibleItems.count(); ++i) { - FxViewItem *item = priv->visibleItems.at(i); - if (!item) { - *failItem = Q_NULLPTR; - return false; - } -#if 0 - qDebug() << "\t" << item->index - << item->item - << item->position() - << (!item->item || QQuickItemPrivate::get(item->item)->culled ? "hidden" : "visible"); -#endif - if (item->index == -1) { - ++skip; - } else if (item->index != priv->visibleIndex + i - skip) { - *nonUnique = false; - *failItem = item; - *expectedIdx = priv->visibleIndex + i - skip; - return false; - } else if (uniqueItems.contains(item->item)) { - *nonUnique = true; - *failItem = item; - *expectedIdx = uniqueItems.find(item->item).value(); - return false; - } - - uniqueItems.insert(item->item, item->index); - } - - return true; -} - -class QTBUG_48870_Model : public QAbstractListModel -{ - Q_OBJECT - -public: - - QTBUG_48870_Model() - : QAbstractListModel() - , m_rowCount(20) - { - QTimer *t = new QTimer(this); - t->setInterval(500); - t->start(); - - qsrand(qHash(QDateTime::currentDateTime())); - connect(t, &QTimer::timeout, this, &QTBUG_48870_Model::updateModel); - } - - int rowCount(const QModelIndex &) const - { - return m_rowCount; - } - - QVariant data(const QModelIndex &, int) const - { - return QVariant(); - } - -public Q_SLOTS: - void updateModel() - { - if (m_rowCount > 10) { - for (int i = 0; i < 10; ++i) { - int rnum = qrand() % m_rowCount; - beginRemoveRows(QModelIndex(), rnum, rnum); - m_rowCount--; - endRemoveRows(); - } - } - if (m_rowCount < 20) { - for (int i = 0; i < 10; ++i) { - int rnum = qrand() % m_rowCount; - beginInsertRows(QModelIndex(), rnum, rnum); - m_rowCount++; - endInsertRows(); - } - } - } - -private: - int m_rowCount; -}; - void tst_QQuickListView::QTBUG_48870_fastModelUpdates() { - QTBUG_48870_Model model; + StressTestModel model; QScopedPointer<QQuickView> window(createView()); QQmlContext *ctxt = window->rootContext(); diff --git a/tests/auto/quick/qquicktextedit/BLACKLIST b/tests/auto/quick/qquicktextedit/BLACKLIST index 297146195b..492d81531a 100644 --- a/tests/auto/quick/qquicktextedit/BLACKLIST +++ b/tests/auto/quick/qquicktextedit/BLACKLIST @@ -1,6 +1,2 @@ [mouseSelection] * -[undo] -* -[undo_keypressevents] -* diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 50e06e5663..47baaaece8 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -108,6 +108,7 @@ private slots: void selectionOnFocusOut(); void focusOnPress(); void selection(); + void overwriteMode(); void isRightToLeft_data(); void isRightToLeft(); void keySelection(); @@ -1466,6 +1467,74 @@ void tst_qquicktextedit::selection() QVERIFY(textEditObject->selectedText().isNull()); } +void tst_qquicktextedit::overwriteMode() +{ + QString componentStr = "import QtQuick 2.0\nTextEdit { focus: true; }"; + QQmlComponent textEditComponent(&engine); + textEditComponent.setData(componentStr.toLatin1(), QUrl()); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit*>(textEditComponent.create()); + QVERIFY(textEdit != 0); + + QSignalSpy spy(textEdit, SIGNAL(overwriteModeChanged(bool))); + + QQuickWindow window; + textEdit->setParentItem(window.contentItem()); + window.show(); + window.requestActivate(); + QTest::qWaitForWindowActive(&window); + + QVERIFY(textEdit->hasActiveFocus()); + + textEdit->setOverwriteMode(true); + QCOMPARE(spy.count(), 1); + QCOMPARE(true, textEdit->overwriteMode()); + textEdit->setOverwriteMode(false); + QCOMPARE(spy.count(), 2); + QCOMPARE(false, textEdit->overwriteMode()); + + QVERIFY(!textEdit->overwriteMode()); + QString insertString = "Some first text"; + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + + QCOMPARE(textEdit->text(), QString("Some first text")); + + textEdit->setOverwriteMode(true); + QCOMPARE(spy.count(), 3); + textEdit->setCursorPosition(5); + + insertString = "shiny"; + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + QCOMPARE(textEdit->text(), QString("Some shiny text")); + + textEdit->setCursorPosition(textEdit->text().length()); + QTest::keyClick(&window, Qt::Key_Enter); + + textEdit->setOverwriteMode(false); + QCOMPARE(spy.count(), 4); + + insertString = "Second paragraph"; + + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + QCOMPARE(textEdit->lineCount(), 2); + + textEdit->setCursorPosition(15); + + QCOMPARE(textEdit->cursorPosition(), 15); + + textEdit->setOverwriteMode(true); + QCOMPARE(spy.count(), 5); + + insertString = " blah"; + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + QCOMPARE(textEdit->lineCount(), 2); + + QCOMPARE(textEdit->text(), QString("Some shiny text blah\nSecond paragraph")); +} + void tst_qquicktextedit::isRightToLeft_data() { QTest::addColumn<QString>("text"); diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp index abb2c5b773..c899290594 100644 --- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp @@ -105,6 +105,7 @@ private slots: void wrap(); void selection(); void persistentSelection(); + void overwriteMode(); void isRightToLeft_data(); void isRightToLeft(); void moveCursorSelection_data(); @@ -777,6 +778,48 @@ void tst_qquicktextinput::persistentSelection() QCOMPARE(input->property("selected").toString(), QLatin1String("ell")); } +void tst_qquicktextinput::overwriteMode() +{ + QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; }"; + QQmlComponent textInputComponent(&engine); + textInputComponent.setData(componentStr.toLatin1(), QUrl()); + QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create()); + QVERIFY(textInput != 0); + + QSignalSpy spy(textInput, SIGNAL(overwriteModeChanged(bool))); + + QQuickWindow window; + textInput->setParentItem(window.contentItem()); + window.show(); + window.requestActivate(); + QTest::qWaitForWindowActive(&window); + + QVERIFY(textInput->hasActiveFocus()); + + textInput->setOverwriteMode(true); + QCOMPARE(spy.count(), 1); + QCOMPARE(true, textInput->overwriteMode()); + textInput->setOverwriteMode(false); + QCOMPARE(spy.count(), 2); + QCOMPARE(false, textInput->overwriteMode()); + + QVERIFY(!textInput->overwriteMode()); + QString insertString = "Some first text"; + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + + QCOMPARE(textInput->text(), QString("Some first text")); + + textInput->setOverwriteMode(true); + QCOMPARE(spy.count(), 3); + textInput->setCursorPosition(5); + + insertString = "shiny"; + for (int j = 0; j < insertString.length(); j++) + QTest::keyClick(&window, insertString.at(j).toLatin1()); + QCOMPARE(textInput->text(), QString("Some shiny text")); +} + void tst_qquicktextinput::isRightToLeft_data() { QTest::addColumn<QString>("text"); diff --git a/tests/auto/quick/qquickwindow/data/windowattached.qml b/tests/auto/quick/qquickwindow/data/windowattached.qml index a9f052d55e..9d61a02452 100644 --- a/tests/auto/quick/qquickwindow/data/windowattached.qml +++ b/tests/auto/quick/qquickwindow/data/windowattached.qml @@ -9,6 +9,7 @@ Rectangle { property Item contentItem: root.Window.contentItem property int windowWidth: root.Window.width property int windowHeight: root.Window.height + property var window: root.Window.window Text { objectName: "rectangleWindowText" anchors.centerIn: parent @@ -26,6 +27,7 @@ Rectangle { property Item contentItem: Window.contentItem property int windowWidth: Window.width property int windowHeight: Window.height + property var window: Window.window } } } diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index 5d1897ab2d..ff8c80e3ae 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -2033,6 +2033,7 @@ void tst_qquickwindow::attachedProperty() QCOMPARE(view.rootObject()->property("contentItem").value<QQuickItem*>(), view.contentItem()); QCOMPARE(view.rootObject()->property("windowWidth").toInt(), view.width()); QCOMPARE(view.rootObject()->property("windowHeight").toInt(), view.height()); + QCOMPARE(view.rootObject()->property("window").value<QQuickView*>(), &view); QQuickWindow *innerWindow = view.rootObject()->findChild<QQuickWindow*>("extraWindow"); QVERIFY(innerWindow); @@ -2045,6 +2046,13 @@ void tst_qquickwindow::attachedProperty() QCOMPARE(text->property("contentItem").value<QQuickItem*>(), innerWindow->contentItem()); QCOMPARE(text->property("windowWidth").toInt(), innerWindow->width()); QCOMPARE(text->property("windowHeight").toInt(), innerWindow->height()); + QCOMPARE(text->property("window").value<QQuickWindow*>(), innerWindow); + + text->setParentItem(0); + QVERIFY(!text->property("contentItem").value<QQuickItem*>()); + QCOMPARE(text->property("windowWidth").toInt(), 0); + QCOMPARE(text->property("windowHeight").toInt(), 0); + QVERIFY(!text->property("window").value<QQuickWindow*>()); } class RenderJob : public QRunnable diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro index f25a28d45b..13bd6d78e2 100644 --- a/tests/auto/quick/quick.pro +++ b/tests/auto/quick/quick.pro @@ -21,6 +21,7 @@ PRIVATETESTS += \ qquickfontloader_static \ qquickfontmetrics \ qquickimageprovider \ + qquicklayouts \ qquickpath \ qquicksmoothedanimation \ qquickspringanimation \ diff --git a/tests/auto/quick/shared/viewtestutil.cpp b/tests/auto/quick/shared/viewtestutil.cpp index 09c88acbe6..ab58aee648 100644 --- a/tests/auto/quick/shared/viewtestutil.cpp +++ b/tests/auto/quick/shared/viewtestutil.cpp @@ -35,6 +35,7 @@ #include <QtTest/QTest> #include <private/qquickwindow_p.h> +#include <private/qquickitemview_p_p.h> QQuickView *QQuickViewTestUtil::createView() @@ -347,6 +348,85 @@ QList<QPair<QString,QString> > QQuickViewTestUtil::ListRange::getModelDataValues return data; } +QQuickViewTestUtil::StressTestModel::StressTestModel() + : QAbstractListModel() + , m_rowCount(20) +{ + QTimer *t = new QTimer(this); + t->setInterval(500); + t->start(); + + qsrand(qHash(QDateTime::currentDateTime())); + connect(t, &QTimer::timeout, this, &StressTestModel::updateModel); +} + +int QQuickViewTestUtil::StressTestModel::rowCount(const QModelIndex &) const +{ + return m_rowCount; +} + +QVariant QQuickViewTestUtil::StressTestModel::data(const QModelIndex &, int) const +{ + return QVariant(); +} + +void QQuickViewTestUtil::StressTestModel::updateModel() +{ + if (m_rowCount > 10) { + for (int i = 0; i < 10; ++i) { + int rnum = qrand() % m_rowCount; + beginRemoveRows(QModelIndex(), rnum, rnum); + m_rowCount--; + endRemoveRows(); + } + } + if (m_rowCount < 20) { + for (int i = 0; i < 10; ++i) { + int rnum = qrand() % m_rowCount; + beginInsertRows(QModelIndex(), rnum, rnum); + m_rowCount++; + endInsertRows(); + } + } +} + +bool QQuickViewTestUtil::testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx) +{ + QHash<QQuickItem*, int> uniqueItems; + + int skip = 0; + for (int i = 0; i < priv->visibleItems.count(); ++i) { + FxViewItem *item = priv->visibleItems.at(i); + if (!item) { + *failItem = Q_NULLPTR; + return false; + } +#if 0 + qDebug() << "\t" << item->index + << item->item + << item->position() + << (!item->item || QQuickItemPrivate::get(item->item)->culled ? "hidden" : "visible"); +#endif + if (item->index == -1) { + ++skip; + } else if (item->index != priv->visibleIndex + i - skip) { + *nonUnique = false; + *failItem = item; + *expectedIdx = priv->visibleIndex + i - skip; + return false; + } else if (uniqueItems.contains(item->item)) { + *nonUnique = true; + *failItem = item; + *expectedIdx = uniqueItems.find(item->item).value(); + return false; + } + + uniqueItems.insert(item->item, item->index); + } + + return true; +} + namespace QQuickTouchUtils { /* QQuickWindow does event compression and only delivers events just diff --git a/tests/auto/quick/shared/viewtestutil.h b/tests/auto/quick/shared/viewtestutil.h index 0f50180809..b11d5e4859 100644 --- a/tests/auto/quick/shared/viewtestutil.h +++ b/tests/auto/quick/shared/viewtestutil.h @@ -34,6 +34,8 @@ #include <QtCore/QAbstractListModel> QT_FORWARD_DECLARE_CLASS(QQuickView) +QT_FORWARD_DECLARE_CLASS(QQuickItemViewPrivate) +QT_FORWARD_DECLARE_CLASS(FxViewItem) namespace QQuickViewTestUtil { @@ -154,6 +156,26 @@ namespace QQuickViewTestUtil for (; f != replaced.end(); ++f, ++t) *t = *f; } + + class StressTestModel : public QAbstractListModel + { + Q_OBJECT + + public: + + StressTestModel(); + + int rowCount(const QModelIndex &) const; + QVariant data(const QModelIndex &, int) const; + + public Q_SLOTS: + void updateModel(); + + private: + int m_rowCount; + }; + + bool testVisibleItems(const QQuickItemViewPrivate *priv, bool *nonUnique, FxViewItem **failItem, int *expectedIdx); } namespace QQuickTouchUtils { diff --git a/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_linebreaks_and_linewraps.qml b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_linebreaks_and_linewraps.qml new file mode 100644 index 0000000000..a1004d0374 --- /dev/null +++ b/tests/manual/scenegraph_lancelot/data/text/textedit_multiline_selected_linebreaks_and_linewraps.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 + +Item { + width: 200 + height: 480 + + TextEdit { + id: textEdit + anchors.centerIn: parent + font.family: "Arial" + font.pixelSize: 64 + width: 200 + textFormat: TextEdit.RichText + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + text: "ABC ABC<br>ABC" + + Component.onCompleted: { + textEdit.selectAll() + } + } + +} |