diff options
Diffstat (limited to 'tests')
91 files changed, 4831 insertions, 590 deletions
diff --git a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp index 99c90c142f..0ebf43eb6f 100644 --- a/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp +++ b/tests/auto/qml/debugger/qqmlenginedebugservice/tst_qqmlenginedebugservice.cpp @@ -524,7 +524,7 @@ void tst_QQmlEngineDebugService::watch_property() QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).value<QByteArray>(), prop.name.toUtf8()); - QCOMPARE(spy.at(0).at(1).value<QVariant>(), qVariantFromValue(origWidth*2)); + QCOMPARE(spy.at(0).at(1).value<QVariant>(), QVariant::fromValue(origWidth*2)); } void tst_QQmlEngineDebugService::watch_object() @@ -772,11 +772,11 @@ void tst_QQmlEngineDebugService::queryObject() } // test specific property values - QCOMPARE(findProperty(rect.properties, "width").value, qVariantFromValue(500)); - QCOMPARE(findProperty(rect.properties, "height").value, qVariantFromValue(600)); - QCOMPARE(findProperty(rect.properties, "color").value, qVariantFromValue(QColor("blue"))); + QCOMPARE(findProperty(rect.properties, "width").value, QVariant::fromValue(500)); + QCOMPARE(findProperty(rect.properties, "height").value, QVariant::fromValue(600)); + QCOMPARE(findProperty(rect.properties, "color").value, QVariant::fromValue(QColor("blue"))); - QCOMPARE(findProperty(text.properties, "color").value, qVariantFromValue(QColor("blue"))); + QCOMPARE(findProperty(text.properties, "color").value, QVariant::fromValue(QColor("blue"))); } else { foreach (const QQmlEngineDebugObjectReference &child, obj.children) { QVERIFY(!child.className.isEmpty()); @@ -851,11 +851,11 @@ void tst_QQmlEngineDebugService::queryObjectsForLocation() } // test specific property values - QCOMPARE(findProperty(rect.properties, "width").value, qVariantFromValue(500)); - QCOMPARE(findProperty(rect.properties, "height").value, qVariantFromValue(600)); - QCOMPARE(findProperty(rect.properties, "color").value, qVariantFromValue(QColor("blue"))); + QCOMPARE(findProperty(rect.properties, "width").value, QVariant::fromValue(500)); + QCOMPARE(findProperty(rect.properties, "height").value, QVariant::fromValue(600)); + QCOMPARE(findProperty(rect.properties, "color").value, QVariant::fromValue(QColor("blue"))); - QCOMPARE(findProperty(text.properties, "color").value, qVariantFromValue(QColor("blue"))); + QCOMPARE(findProperty(text.properties, "color").value, QVariant::fromValue(QColor("blue"))); } else { foreach (const QQmlEngineDebugObjectReference &child, obj.children) { QVERIFY(!child.className.isEmpty()); @@ -1004,15 +1004,15 @@ void tst_QQmlEngineDebugService::queryExpressionResult_data() QTest::addColumn<QString>("expr"); QTest::addColumn<QVariant>("result"); - QTest::newRow("width + 50") << "width + 50" << qVariantFromValue(60); - QTest::newRow("blueRect.width") << "blueRect.width" << qVariantFromValue(500); - QTest::newRow("bad expr") << "aeaef" << qVariantFromValue(QString("<undefined>")); - QTest::newRow("QObject*") << "varObj" << qVariantFromValue(QString("<unnamed object>")); - QTest::newRow("list of QObject*") << "varObjList" << qVariantFromValue(QVariantList() << QVariant(QString("<unnamed object>"))); + QTest::newRow("width + 50") << "width + 50" << QVariant::fromValue(60); + QTest::newRow("blueRect.width") << "blueRect.width" << QVariant::fromValue(500); + QTest::newRow("bad expr") << "aeaef" << QVariant::fromValue(QString("<undefined>")); + QTest::newRow("QObject*") << "varObj" << QVariant::fromValue(QString("<unnamed object>")); + QTest::newRow("list of QObject*") << "varObjList" << QVariant::fromValue(QVariantList() << QVariant(QString("<unnamed object>"))); QVariantMap map; map.insert(QLatin1String("rect"), QVariant(QLatin1String("<unnamed object>"))); - QTest::newRow("varObjMap") << "varObjMap" << qVariantFromValue(map); - QTest::newRow("simpleVar") << "simpleVar" << qVariantFromValue(10.05); + QTest::newRow("varObjMap") << "varObjMap" << QVariant::fromValue(map); + QTest::newRow("simpleVar") << "simpleVar" << QVariant::fromValue(10.05); } void tst_QQmlEngineDebugService::queryExpressionResultInRootContext() @@ -1052,15 +1052,15 @@ void tst_QQmlEngineDebugService::queryExpressionResultBC_data() QTest::addColumn<QString>("expr"); QTest::addColumn<QVariant>("result"); - QTest::newRow("width + 50") << "width + 50" << qVariantFromValue(60); - QTest::newRow("blueRect.width") << "blueRect.width" << qVariantFromValue(500); - QTest::newRow("bad expr") << "aeaef" << qVariantFromValue(QString("<undefined>")); - QTest::newRow("QObject*") << "varObj" << qVariantFromValue(QString("<unnamed object>")); - QTest::newRow("list of QObject*") << "varObjList" << qVariantFromValue(QVariantList() << QVariant(QString("<unnamed object>"))); + QTest::newRow("width + 50") << "width + 50" << QVariant::fromValue(60); + QTest::newRow("blueRect.width") << "blueRect.width" << QVariant::fromValue(500); + QTest::newRow("bad expr") << "aeaef" << QVariant::fromValue(QString("<undefined>")); + QTest::newRow("QObject*") << "varObj" << QVariant::fromValue(QString("<unnamed object>")); + QTest::newRow("list of QObject*") << "varObjList" << QVariant::fromValue(QVariantList() << QVariant(QString("<unnamed object>"))); QVariantMap map; map.insert(QLatin1String("rect"), QVariant(QLatin1String("<unnamed object>"))); - QTest::newRow("varObjMap") << "varObjMap" << qVariantFromValue(map); - QTest::newRow("simpleVar") << "simpleVar" << qVariantFromValue(10.05); + QTest::newRow("varObjMap") << "varObjMap" << QVariant::fromValue(map); + QTest::newRow("simpleVar") << "simpleVar" << QVariant::fromValue(10.05); } void tst_QQmlEngineDebugService::setBindingForObject() diff --git a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp index 497c721f50..84f5eebd10 100644 --- a/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp +++ b/tests/auto/qml/debugger/qv4debugger/tst_qv4debugger.cpp @@ -36,7 +36,6 @@ #include <QQmlComponent> #include <private/qv4engine_p.h> #include <private/qv4debugging_p.h> -#include <private/qv8engine_p.h> #include <private/qv4objectiterator_p.h> #include <private/qv4string_p.h> #include <private/qqmlbuiltinfunctions_p.h> diff --git a/tests/auto/qml/ecmascripttests/TestExpectations b/tests/auto/qml/ecmascripttests/TestExpectations index cf7bf3d8ba..0e0d70845b 100644 --- a/tests/auto/qml/ecmascripttests/TestExpectations +++ b/tests/auto/qml/ecmascripttests/TestExpectations @@ -211,7 +211,6 @@ built-ins/Promise/prototype/then/ctor-throws.js fails built-ins/Promise/race/ctx-ctor.js fails built-ins/Proxy/ownKeys/return-duplicate-entries-throws.js fails built-ins/Proxy/ownKeys/return-duplicate-symbol-entries-throws.js fails -built-ins/RegExp/S15.10.2.12_A2_T1.js fails built-ins/RegExp/prototype/Symbol.match/builtin-success-u-return-val-groups.js fails built-ins/RegExp/prototype/Symbol.split/species-ctor.js fails built-ins/RegExp/prototype/exec/S15.10.6.2_A5_T3.js fails @@ -219,7 +218,6 @@ built-ins/RegExp/prototype/exec/failure-lastindex-access.js fails built-ins/RegExp/prototype/exec/success-lastindex-access.js fails built-ins/RegExp/prototype/source/value-line-terminator.js fails built-ins/RegExp/prototype/test/S15.10.6.3_A1_T22.js fails -built-ins/RegExp/u180e.js fails built-ins/RegExp/unicode_restricted_brackets.js fails built-ins/RegExp/unicode_restricted_character_class_escape.js fails built-ins/RegExp/unicode_restricted_identity_escape.js fails diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index 75fa2216f7..6ca2663f30 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -66,6 +66,10 @@ private slots: void newArray(); void newArray_HooliganTask218092(); void newArray_HooliganTask233836(); + void toScriptValue_data(); + void toScriptValue(); + void toScriptValuenotroundtripped_data(); + void toScriptValuenotroundtripped(); void newVariant(); void newVariant_valueOfToString(); void newVariant_valueOfEnum(); @@ -94,6 +98,7 @@ private slots: void valueConversion_basic2(); void valueConversion_dateTime(); void valueConversion_regExp(); + void valueConversion_RegularExpression(); void castWithMultipleInheritance(); void collectGarbage(); void gcWithNestedDataStructure(); @@ -135,6 +140,8 @@ private slots: void qRegExpInport_data(); void qRegExpInport(); + void qRegularExpressionImport_data(); + void qRegularExpressionImport(); void dateRoundtripJSQtJS(); void dateRoundtripQtJSQt(); void dateConversionJSQt(); @@ -238,6 +245,9 @@ private slots: void equality(); void aggressiveGc(); + void interrupt_data(); + void interrupt(); + public: Q_INVOKABLE QJSValue throwingCppMethod1(); Q_INVOKABLE void throwingCppMethod2(); @@ -481,17 +491,97 @@ void tst_QJSEngine::newArray_HooliganTask233836() } } +void tst_QJSEngine::toScriptValue_data() +{ + QTest::addColumn<QVariant>("input"); + + QTest::newRow("UnknownType") << QVariant(int(QMetaType::UnknownType), nullptr); + QTest::newRow("Nullptr") << QVariant(int(QMetaType::Nullptr), nullptr); + QTest::newRow("true") << QVariant(true); + QTest::newRow("false") << QVariant(false); + QTest::newRow("int") << QVariant(int(42)); + QTest::newRow("uint") << QVariant(uint(42)); + QTest::newRow("longlong") << QVariant(qlonglong(4242)); + QTest::newRow("ulonglong") << QVariant(qulonglong(4242)); + QTest::newRow("double") << QVariant(double(42.42)); + QTest::newRow("float") << QVariant(float(42.42)); + QTest::newRow("qstring") << QVariant(QString::fromLatin1("hello")); + QTest::newRow("qbytearray") << QVariant(QByteArray("hello")); + QTest::newRow("short") << QVariant(short('r')); + QTest::newRow("ushort") << QVariant(short('b')); + QTest::newRow("char") << QVariant(char('r')); + QTest::newRow("uchar") << QVariant(uchar('b')); + QTest::newRow("qchar") << QVariant(QString::fromUtf8("å").at(0)); + QTest::newRow("qdate") << QVariant(QDate(1925, 5, 8)); + QTest::newRow("qtime") << QVariant(QTime(4, 5, 6)); + QTest::newRow("qregularexpression") << QVariant(QRegularExpression(".*")); + QTest::newRow("qpointf") << QVariant(QPointF(42, 24)); + QTest::newRow("qvariantlist") << QVariant(QVariantList() << 42.24 << 5 << "hello"); + QTest::newRow("qvariantlist_point") << QVariant(QVariantList() << 42.24 << QPointF(42.24, 24.42) << QPointF(24.42, 42.24)); + QVariantMap vm; vm.insert("test", 55); vm.insert("abc", 42.42);; + QTest::newRow("qvariantmap") << QVariant(vm); + vm.clear(); vm.insert("point1", QPointF(42.24, 24.42)); vm.insert("point2", QPointF(42.24, 24.42)); + QTest::newRow("qvariantmap_point") << QVariant(vm); + QTest::newRow("qvariant") << QVariant(QVariant(42)); + QTest::newRow("QList<int>") << QVariant::fromValue(QList<int>() << 1 << 2 << 3 << 4); + QTest::newRow("QVector<int>") << QVariant::fromValue(QVector<int>() << 1 << 2 << 3 << 4); + QTest::newRow("QList<QString>") << QVariant::fromValue(QVector<QString>() << "1" << "2" << "3" << "4"); + QTest::newRow("QStringList") << QVariant::fromValue(QStringList() << "1" << "2" << "3" << "4"); + QTest::newRow("QMap<QString, QString>") << QVariant::fromValue(QMap<QString, QString>{{ "1", "2" }, { "3", "4" }}); + QTest::newRow("QHash<QString, QString>") << QVariant::fromValue(QHash<QString, QString>{{ "1", "2" }, { "3", "4" }}); + QTest::newRow("QMap<QString, QPointF>") << QVariant::fromValue(QMap<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }}); + QTest::newRow("QHash<QString, QPointF>") << QVariant::fromValue(QHash<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }}); +} + +void tst_QJSEngine::toScriptValue() +{ + QFETCH(QVariant, input); + + QJSEngine engine; + QJSValue outputJS = engine.toScriptValue(input); + QVariant output = engine.fromScriptValue<QVariant>(outputJS); + + QCOMPARE(input, output); +} + +void tst_QJSEngine::toScriptValuenotroundtripped_data() +{ + QTest::addColumn<QVariant>("input"); + QTest::addColumn<QVariant>("output"); + + QTest::newRow("QList<QObject*>") << QVariant::fromValue(QList<QObject*>() << this) << QVariant(QVariantList() << QVariant::fromValue(this)); + QTest::newRow("QObjectList") << QVariant::fromValue(QObjectList() << this) << QVariant(QVariantList() << QVariant::fromValue(this)); + QTest::newRow("QList<QPoint>") << QVariant::fromValue(QList<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); + QTest::newRow("QVector<QPoint>") << QVariant::fromValue(QVector<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); + QTest::newRow("VoidStar") << QVariant(int(QMetaType::VoidStar), nullptr) << QVariant(int(QMetaType::Nullptr), nullptr); + QTest::newRow("qregex") << QVariant(QRegExp(".*", Qt::CaseSensitive, QRegExp::RegExp2)) << QVariant(QRegularExpression(".*")); +} + +// This is almost the same as toScriptValue, but the inputs don't roundtrip to +// exactly the same value. +void tst_QJSEngine::toScriptValuenotroundtripped() +{ + QFETCH(QVariant, input); + QFETCH(QVariant, output); + + QJSEngine engine; + QJSValue outputJS = engine.toScriptValue(input); + QVariant actualOutput = engine.fromScriptValue<QVariant>(outputJS); + + QCOMPARE(actualOutput, output); +} + void tst_QJSEngine::newVariant() { QJSEngine eng; { QJSValue opaque = eng.toScriptValue(QVariant(QPoint(1, 2))); QVERIFY(!opaque.isUndefined()); - QCOMPARE(opaque.isVariant(), true); + QCOMPARE(opaque.isVariant(), false); QVERIFY(!opaque.isCallable()); QCOMPARE(opaque.isObject(), true); QVERIFY(!opaque.prototype().isUndefined()); - QCOMPARE(opaque.prototype().isVariant(), true); + QCOMPARE(opaque.prototype().isVariant(), false); QVERIFY(opaque.property("valueOf").callWithInstance(opaque).equals(opaque)); } } @@ -505,7 +595,7 @@ void tst_QJSEngine::newVariant_valueOfToString() QJSValue value = object.property("valueOf").callWithInstance(object); QVERIFY(value.isObject()); QVERIFY(value.strictlyEquals(object)); - QCOMPARE(object.toString(), QString::fromLatin1("QVariant(QPoint, QPoint(10,20))")); + QCOMPARE(object.toString(), QString::fromLatin1("QPoint(10, 20)")); } } @@ -523,22 +613,27 @@ void tst_QJSEngine::newVariant_valueOfEnum() void tst_QJSEngine::newRegExp() { QJSEngine eng; - QJSValue rexp = eng.toScriptValue(QRegExp("foo")); - QVERIFY(!rexp.isUndefined()); - QCOMPARE(rexp.isRegExp(), true); - QCOMPARE(rexp.isObject(), true); - QCOMPARE(rexp.isCallable(), false); - // prototype should be RegExp.prototype - QVERIFY(!rexp.prototype().isUndefined()); - QCOMPARE(rexp.prototype().isObject(), true); - // Get [[Class]] internal property of RegExp Prototype Object. - // See ECMA-262 Section 8.6.2, "Object Internal Properties and Methods". - // See ECMA-262 Section 15.10.6, "Properties of the RegExp Prototype Object". - QJSValue r = eng.evaluate("Object.prototype.toString.call(RegExp.prototype)"); - QCOMPARE(r.toString(), QString::fromLatin1("[object Object]")); - QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true); + QJSValue rexps[] = { + eng.toScriptValue(QRegularExpression("foo")), + eng.toScriptValue(QRegExp("foo")) + }; + for (const auto &rexp : rexps) { + QVERIFY(!rexp.isUndefined()); + QCOMPARE(rexp.isRegExp(), true); + QCOMPARE(rexp.isObject(), true); + QCOMPARE(rexp.isCallable(), false); + // prototype should be RegExp.prototype + QVERIFY(!rexp.prototype().isUndefined()); + QCOMPARE(rexp.prototype().isObject(), true); + // Get [[Class]] internal property of RegExp Prototype Object. + // See ECMA-262 Section 8.6.2, "Object Internal Properties and Methods". + // See ECMA-262 Section 15.10.6, "Properties of the RegExp Prototype Object". + QJSValue r = eng.evaluate("Object.prototype.toString.call(RegExp.prototype)"); + QCOMPARE(r.toString(), QString::fromLatin1("[object Object]")); + QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true); - QCOMPARE(qjsvalue_cast<QRegExp>(rexp).pattern(), QRegExp("foo").pattern()); + QCOMPARE(qjsvalue_cast<QRegExp>(rexp).pattern(), QRegExp("foo").pattern()); + } } void tst_QJSEngine::jsRegExp() @@ -1490,15 +1585,15 @@ void tst_QJSEngine::valueConversion_QVariant() QCOMPARE(val.toString(), str); } { - QJSValue val = eng.toScriptValue(qVariantFromValue((QObject*)this)); + QJSValue val = eng.toScriptValue(QVariant::fromValue((QObject*)this)); QVERIFY(!val.isVariant()); QVERIFY(val.isQObject()); QCOMPARE(val.toQObject(), (QObject*)this); } { - QVariant var = qVariantFromValue(QPoint(123, 456)); + QVariant var = QVariant::fromValue(QPoint(123, 456)); QJSValue val = eng.toScriptValue(var); - QVERIFY(val.isVariant()); + QVERIFY(!val.isVariant()); QCOMPARE(val.toVariant(), var); } @@ -1604,6 +1699,28 @@ void tst_QJSEngine::valueConversion_regExp() } } +void tst_QJSEngine::valueConversion_RegularExpression() +{ + QJSEngine eng; + { + QRegularExpression in = QRegularExpression("foo"); + QJSValue val = eng.toScriptValue(in); + QVERIFY(val.isRegExp()); + QRegularExpression out = qjsvalue_cast<QRegularExpression>(val); + QCOMPARE(out.pattern(), in.pattern()); + QCOMPARE(out.patternOptions(), in.patternOptions()); + } + { + QRegularExpression in = QRegularExpression("foo", + QRegularExpression::CaseInsensitiveOption); + QJSValue val = eng.toScriptValue(in); + QVERIFY(val.isRegExp()); + QCOMPARE(qjsvalue_cast<QRegularExpression>(val), in); + QRegularExpression out = qjsvalue_cast<QRegularExpression>(val); + QCOMPARE(out.patternOptions(), in.patternOptions()); + } +} + Q_DECLARE_METATYPE(QGradient) Q_DECLARE_METATYPE(QGradient*) Q_DECLARE_METATYPE(QLinearGradient) @@ -2953,6 +3070,8 @@ void tst_QJSEngine::reentrancy_objectCreation() QJSValue r2 = eng2.evaluate("new RegExp('foo', 'gim')"); QCOMPARE(qjsvalue_cast<QRegExp>(r1), qjsvalue_cast<QRegExp>(r2)); QCOMPARE(qjsvalue_cast<QRegExp>(r2), qjsvalue_cast<QRegExp>(r1)); + QCOMPARE(qjsvalue_cast<QRegularExpression>(r1), qjsvalue_cast<QRegularExpression>(r2)); + QCOMPARE(qjsvalue_cast<QRegularExpression>(r2), qjsvalue_cast<QRegularExpression>(r1)); } { QJSValue o1 = eng1.newQObject(temp); @@ -3148,6 +3267,56 @@ void tst_QJSEngine::qRegExpInport() } } +void tst_QJSEngine::qRegularExpressionImport_data() +{ + QTest::addColumn<QRegularExpression>("rx"); + QTest::addColumn<QString>("string"); + QTest::addColumn<QString>("matched"); + + QTest::newRow("normal") << QRegularExpression("(test|foo)") << "test _ foo _ test _ Foo"; + QTest::newRow("normal2") << QRegularExpression("(Test|Foo)") << "test _ foo _ test _ Foo"; + QTest::newRow("case insensitive") << QRegularExpression("(test|foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo"; + QTest::newRow("case insensitive2") << QRegularExpression("(Test|Foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo"; + QTest::newRow("b(a*)(b*)") << QRegularExpression("b(a*)(b*)", QRegularExpression::CaseInsensitiveOption) << "aaabbBbaAabaAaababaaabbaaab"; + QTest::newRow("greedy") << QRegularExpression("a*(a*)", QRegularExpression::CaseInsensitiveOption) << "aaaabaaba"; + QTest::newRow("wildcard") << QRegularExpression(".*\\.txt") << "file.txt"; + QTest::newRow("wildcard 2") << QRegularExpression("a.b\\.txt") << "ab.txt abb.rtc acb.txt"; + QTest::newRow("slash") << QRegularExpression("g/.*/s", QRegularExpression::CaseInsensitiveOption) << "string/string/string"; + QTest::newRow("slash2") << QRegularExpression("g / .* / s", QRegularExpression::CaseInsensitiveOption) << "string / string / string"; + QTest::newRow("fixed") << QRegularExpression("a\\*aa\\.a\\(ba\\)\\*a\\\\ba", QRegularExpression::CaseInsensitiveOption) << "aa*aa.a(ba)*a\\ba"; + QTest::newRow("fixed insensitive") << QRegularExpression("A\\*A", QRegularExpression::CaseInsensitiveOption) << "a*A A*a A*A a*a"; + QTest::newRow("fixed sensitive") << QRegularExpression("A\\*A") << "a*A A*a A*A a*a"; + QTest::newRow("html") << QRegularExpression("<b>(.*)</b>") << "<b>bold</b><i>italic</i><b>bold</b>"; + QTest::newRow("html minimal") << QRegularExpression("^<b>(.*)</b>$") << "<b>bold</b><i>italic</i><b>bold</b>"; + QTest::newRow("aaa") << QRegularExpression("a{2,5}") << "aAaAaaaaaAa"; + QTest::newRow("aaa minimal") << QRegularExpression("^a{2,5}$") << "aAaAaaaaaAa"; + QTest::newRow("minimal") << QRegularExpression("^.*\\} [*8]$") << "}?} ?} *"; + QTest::newRow(".? minimal") << QRegularExpression("^.?$") << ".?"; + QTest::newRow(".+ minimal") << QRegularExpression("^.+$") << ".+"; + QTest::newRow("[.?] minimal") << QRegularExpression("^[.?]$") << ".?"; + QTest::newRow("[.+] minimal") << QRegularExpression("^[.+]$") << ".+"; +} + +void tst_QJSEngine::qRegularExpressionImport() +{ + QFETCH(QRegularExpression, rx); + QFETCH(QString, string); + + QJSEngine eng; + QJSValue rexp; + rexp = eng.toScriptValue(rx); + + QCOMPARE(rexp.isRegExp(), true); + QCOMPARE(rexp.isCallable(), false); + + QJSValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })"); + QJSValue result = func.call(QJSValueList() << string << rexp); + + const QRegularExpressionMatch match = rx.match(string); + for (int i = 0; i <= match.lastCapturedIndex(); i++) + QCOMPARE(result.property(i).toString(), match.captured(i)); +} + // QScriptValue::toDateTime() returns a local time, whereas JS dates // are always stored as UTC. Qt Script must respect the current time // zone, and correctly adjust for daylight saving time that may be in @@ -4673,6 +4842,82 @@ void tst_QJSEngine::aggressiveGc() qputenv("QV4_MM_AGGRESSIVE_GC", origAggressiveGc); } +void tst_QJSEngine::interrupt_data() +{ + QTest::addColumn<int>("jitThreshold"); + QTest::addColumn<QString>("code"); + + const int big = (1 << 24); + for (int i = 0; i <= big; i += big) { + const char *mode = i ? "interpret" : "jit"; + QTest::addRow("for with content / %s", mode) << i << "var a = 0; for (;;) { a += 2; }"; + QTest::addRow("for empty / %s", mode) << i << "for (;;) {}"; + QTest::addRow("for continue / %s", mode) << i << "for (;;) { continue; }"; + QTest::addRow("while with content / %s", mode) << i << "var a = 0; while (true) { a += 2; }"; + QTest::addRow("while empty / %s", mode) << i << "while (true) {}"; + QTest::addRow("while continue / %s", mode) << i << "while (true) { continue; }"; + QTest::addRow("do with content / %s", mode) << i << "var a = 0; do { a += 2; } while (true);"; + QTest::addRow("do empty / %s", mode) << i << "do {} while (true);"; + QTest::addRow("do continue / %s", mode) << i << "do { continue; } while (true);"; + QTest::addRow("nested loops / %s", mode) << i << "while (true) { for (;;) {} }"; + QTest::addRow("labeled continue / %s", mode) << i << "a: while (true) { for (;;) { continue a; } }"; + QTest::addRow("labeled break / %s", mode) << i << "while (true) { a: for (;;) { break a; } }"; + QTest::addRow("tail call / %s", mode) << i << "'use strict';\nfunction x() { return x(); }; x();"; + } +} + +class TemporaryJitThreshold +{ + Q_DISABLE_COPY_MOVE(TemporaryJitThreshold) +public: + TemporaryJitThreshold(int threshold) { + m_wasSet = qEnvironmentVariableIsSet(m_envVar); + m_value = qgetenv(m_envVar); + qputenv(m_envVar, QByteArray::number(threshold)); + } + + ~TemporaryJitThreshold() + { + if (m_wasSet) + qputenv(m_envVar, m_value); + else + qunsetenv(m_envVar); + } + +private: + const char *m_envVar = "QV4_JIT_CALL_THRESHOLD"; + bool m_wasSet = false; + QByteArray m_value; +}; + +void tst_QJSEngine::interrupt() +{ + QFETCH(int, jitThreshold); + QFETCH(QString, code); + + TemporaryJitThreshold threshold(jitThreshold); + Q_UNUSED(threshold); + + QJSEngine *engineInThread = nullptr; + QScopedPointer<QThread> worker(QThread::create([&engineInThread, &code, jitThreshold](){ + QJSEngine jsEngine; + engineInThread = &jsEngine; + QJSValue result = jsEngine.evaluate(code); + QVERIFY(jsEngine.isInterrupted()); + QVERIFY(result.isError()); + QCOMPARE(result.toString(), QString::fromLatin1("Error: Interrupted")); + engineInThread = nullptr; + })); + worker->start(); + + QTRY_VERIFY(engineInThread); + + engineInThread->setInterrupted(true); + + QVERIFY(worker->wait()); + QVERIFY(!engineInThread); +} + QTEST_MAIN(tst_QJSEngine) #include "tst_qjsengine.moc" diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp index b58cd98d1e..a57cd3113c 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.cpp @@ -404,8 +404,8 @@ void tst_QJSValue::toString() // variant should use internal valueOf(), then fall back to QVariant::toString(), // then fall back to "QVariant(typename)" QJSValue variant = eng.toScriptValue(QPoint(10, 20)); - QVERIFY(variant.isVariant()); - QCOMPARE(variant.toString(), QString::fromLatin1("QVariant(QPoint, QPoint(10,20))")); + QVERIFY(!variant.isVariant()); + QCOMPARE(variant.toString(), QString::fromLatin1("QPoint(10, 20)")); variant = eng.toScriptValue(QUrl()); QVERIFY(variant.isVariant()); QVERIFY(variant.toString().isEmpty()); @@ -1027,6 +1027,20 @@ void tst_QJSValue::toVariant() QJSValue rxObject = eng.toScriptValue(rx); QVERIFY(rxObject.isRegExp()); QVariant var = rxObject.toVariant(); + + // We can't roundtrip a QRegExp this way, as toVariant() has no information on whether we + // want QRegExp or QRegularExpression. It will always create a QRegularExpression. + QCOMPARE(var.type(), QMetaType::QRegularExpression); + QRegularExpression result = var.toRegularExpression(); + QCOMPARE(result.pattern(), rx.pattern()); + QCOMPARE(result.patternOptions() & QRegularExpression::CaseInsensitiveOption, 0); + } + + { + QRegularExpression rx = QRegularExpression("[0-9a-z]+"); + QJSValue rxObject = eng.toScriptValue(rx); + QVERIFY(rxObject.isRegExp()); + QVariant var = rxObject.toVariant(); QCOMPARE(var, QVariant(rx)); } @@ -1107,7 +1121,7 @@ void tst_QJSValue::toQObject_nonQObject_data() QTest::newRow("array") << engine->newArray(); QTest::newRow("date") << engine->evaluate("new Date(124)"); QTest::newRow("variant(12345)") << engine->toScriptValue(QVariant(12345)); - QTest::newRow("variant((QObject*)0)") << engine->toScriptValue(qVariantFromValue((QObject*)nullptr)); + QTest::newRow("variant((QObject*)0)") << engine->toScriptValue(QVariant::fromValue((QObject*)nullptr)); QTest::newRow("newQObject(0)") << engine->newQObject(nullptr); } @@ -1194,6 +1208,32 @@ void tst_QJSValue::toRegExp() QVERIFY(qjsvalue_cast<QRegExp>(eng.toScriptValue(QVariant())).isEmpty()); } +void tst_QJSValue::toRegularExpression() +{ + QJSEngine eng; + { + QRegularExpression rx = qjsvalue_cast<QRegularExpression>(eng.evaluate("/foo/")); + QVERIFY(rx.isValid()); + QCOMPARE(rx.pattern(), QString::fromLatin1("foo")); + QVERIFY(!(rx.patternOptions() & QRegularExpression::CaseInsensitiveOption)); + } + { + QRegularExpression rx = qjsvalue_cast<QRegularExpression>(eng.evaluate("/bar/gi")); + QVERIFY(rx.isValid()); + QCOMPARE(rx.pattern(), QString::fromLatin1("bar")); + QVERIFY(rx.patternOptions() & QRegularExpression::CaseInsensitiveOption); + } + + QVERIFY(qjsvalue_cast<QRegularExpression>(eng.evaluate("[]")).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(eng.evaluate("{}")).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(eng.globalObject()).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(QJSValue()).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(QJSValue(123)).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(QJSValue(false)).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(eng.evaluate("null")).pattern().isEmpty()); + QVERIFY(qjsvalue_cast<QRegularExpression>(eng.toScriptValue(QVariant())).pattern().isEmpty()); +} + void tst_QJSValue::isArray_data() { newEngine(); @@ -2248,8 +2288,8 @@ void tst_QJSValue::strictlyEquals() { QJSValue var1 = eng.toScriptValue(QVariant(QStringList() << "a")); QJSValue var2 = eng.toScriptValue(QVariant(QStringList() << "a")); - QVERIFY(var1.isArray()); - QVERIFY(var2.isArray()); + QVERIFY(!var1.isArray()); + QVERIFY(!var2.isArray()); QVERIFY(!var1.strictlyEquals(var2)); } { @@ -2287,7 +2327,7 @@ void tst_QJSValue::castToPointer() QBrush *bp = qjsvalue_cast<QBrush*>(v); QVERIFY(!bp); - QJSValue v2 = eng.toScriptValue(qVariantFromValue(cp)); + QJSValue v2 = eng.toScriptValue(QVariant::fromValue(cp)); QCOMPARE(qjsvalue_cast<QColor*>(v2), cp); } } diff --git a/tests/auto/qml/qjsvalue/tst_qjsvalue.h b/tests/auto/qml/qjsvalue/tst_qjsvalue.h index b8b9f4403c..9532b1f10e 100644 --- a/tests/auto/qml/qjsvalue/tst_qjsvalue.h +++ b/tests/auto/qml/qjsvalue/tst_qjsvalue.h @@ -76,6 +76,7 @@ private slots: void toQObject(); void toDateTime(); void toRegExp(); + void toRegularExpression(); void isArray_data(); void isArray(); void isDate(); diff --git a/tests/auto/qml/qml.pro b/tests/auto/qml/qml.pro index 5448088ee5..db9bb52010 100644 --- a/tests/auto/qml/qml.pro +++ b/tests/auto/qml/qml.pro @@ -70,6 +70,7 @@ PRIVATETESTS += \ qqmltranslation \ qqmlimport \ qqmlobjectmodel \ + qqmltablemodel \ qv4assembler \ qv4mm \ qv4identifiertable \ @@ -104,7 +105,3 @@ qtConfig(private_tests): \ qtNomakeTools( \ qmlplugindump \ ) - -QtConfig(qml_tracing) { - PRIVATETESTS += v4traced -} diff --git a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp index 70a5a73e0f..e86b7bf5fe 100644 --- a/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp +++ b/tests/auto/qml/qmldiskcache/tst_qmldiskcache.cpp @@ -30,7 +30,6 @@ #include <private/qv4compileddata_p.h> #include <private/qv4compiler_p.h> -#include <private/qv8engine_p.h> #include <private/qv4engine_p.h> #include <private/qv4codegen_p.h> #include <private/qqmlcomponent_p.h> diff --git a/tests/auto/qml/qqmlchangeset/qqmlchangeset.pro b/tests/auto/qml/qqmlchangeset/qqmlchangeset.pro index 9f854f1fa2..cdac5c0ff9 100644 --- a/tests/auto/qml/qqmlchangeset/qqmlchangeset.pro +++ b/tests/auto/qml/qqmlchangeset/qqmlchangeset.pro @@ -4,4 +4,4 @@ macx:CONFIG -= app_bundle SOURCES += tst_qqmlchangeset.cpp -QT += core-private gui-private qml-private testlib +QT += core-private gui-private qml-private testlib qmlmodels-private diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 7c7c7d3bd0..98ae86d248 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -35,7 +35,6 @@ #include <QtQuick> #include <QtQuick/private/qquickrectangle_p.h> #include <QtQuick/private/qquickmousearea_p.h> -#include <private/qv8engine_p.h> #include <private/qqmlcontext_p.h> #include <private/qv4qmlcontext_p.h> #include <private/qv4scopedvalue_p.h> diff --git a/tests/auto/qml/qqmlecmascript/data/regularExpression.2.qml b/tests/auto/qml/qqmlecmascript/data/regularExpression.2.qml new file mode 100644 index 0000000000..b22f8ab71e --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/regularExpression.2.qml @@ -0,0 +1,7 @@ +import Qt.test 1.0 + +MyQmlObject{ + id: obj + objectName: "obj" + regularExpression: "[a-zA-z]" +} diff --git a/tests/auto/qml/qqmlecmascript/data/regularExpression.qml b/tests/auto/qml/qqmlecmascript/data/regularExpression.qml new file mode 100644 index 0000000000..6f31ffd305 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/regularExpression.qml @@ -0,0 +1,7 @@ +import Qt.test 1.0 + +MyQmlObject{ + id: obj + objectName: "obj" + regularExpression: /[a-zA-z]/ +} diff --git a/tests/auto/qml/qqmlecmascript/testtypes.h b/tests/auto/qml/qqmlecmascript/testtypes.h index 4547a74470..730dc7cab8 100644 --- a/tests/auto/qml/qqmlecmascript/testtypes.h +++ b/tests/auto/qml/qqmlecmascript/testtypes.h @@ -33,6 +33,7 @@ #include <QtQml/qqmlexpression.h> #include <QtCore/qpoint.h> #include <QtCore/qsize.h> +#include <QtCore/qregularexpression.h> #include <QtQml/qqmllist.h> #include <QtCore/qrect.h> #include <QtGui/qmatrix.h> @@ -49,7 +50,6 @@ #include <QtQml/qqmlcomponent.h> #include <private/qqmlengine_p.h> -#include <private/qv8engine_p.h> #include <private/qv4qobjectwrapper_p.h> class MyQmlAttachedObject : public QObject @@ -101,6 +101,7 @@ class MyQmlObject : public QObject Q_PROPERTY(QQmlListProperty<QObject> objectListProperty READ objectListProperty CONSTANT) Q_PROPERTY(int resettableProperty READ resettableProperty WRITE setResettableProperty RESET resetProperty) Q_PROPERTY(QRegExp regExp READ regExp WRITE setRegExp) + Q_PROPERTY(QRegularExpression regularExpression READ regularExpression WRITE setRegularExpression) Q_PROPERTY(int nonscriptable READ nonscriptable WRITE setNonscriptable SCRIPTABLE false) Q_PROPERTY(int intProperty READ intProperty WRITE setIntProperty NOTIFY intChanged) Q_PROPERTY(QJSValue qjsvalue READ qjsvalue WRITE setQJSValue NOTIFY qjsvalueChanged) @@ -170,6 +171,12 @@ public: QRegExp regExp() { return m_regExp; } void setRegExp(const QRegExp ®Exp) { m_regExp = regExp; } + QRegularExpression regularExpression() { return m_regularExpression; } + void setRegularExpression(const QRegularExpression ®ularExpression) + { + m_regularExpression = regularExpression; + } + int console() const { return 11; } int nonscriptable() const { return 0; } @@ -270,6 +277,7 @@ private: int m_value; int m_resetProperty; QRegExp m_regExp; + QRegularExpression m_regularExpression; QVariant m_variant; QJSValue m_qjsvalue; int m_intProperty; @@ -788,11 +796,11 @@ public: Q_INVOKABLE void method_real(qreal a) { invoke(10); m_actuals << a; } Q_INVOKABLE void method_QString(QString a) { invoke(11); m_actuals << a; } Q_INVOKABLE void method_QPointF(QPointF a) { invoke(12); m_actuals << a; } - Q_INVOKABLE void method_QObject(QObject *a) { invoke(13); m_actuals << qVariantFromValue(a); } - Q_INVOKABLE void method_QScriptValue(QJSValue a) { invoke(14); m_actuals << qVariantFromValue(a); } - Q_INVOKABLE void method_intQScriptValue(int a, QJSValue b) { invoke(15); m_actuals << a << qVariantFromValue(b); } + Q_INVOKABLE void method_QObject(QObject *a) { invoke(13); m_actuals << QVariant::fromValue(a); } + Q_INVOKABLE void method_QScriptValue(QJSValue a) { invoke(14); m_actuals << QVariant::fromValue(a); } + Q_INVOKABLE void method_intQScriptValue(int a, QJSValue b) { invoke(15); m_actuals << a << QVariant::fromValue(b); } Q_INVOKABLE void method_QByteArray(QByteArray value) { invoke(29); m_actuals << value; } - Q_INVOKABLE QJSValue method_intQJSValue(int a, QJSValue b) { invoke(30); m_actuals << a << qVariantFromValue(b); return b.call(); } + Q_INVOKABLE QJSValue method_intQJSValue(int a, QJSValue b) { invoke(30); m_actuals << a << QVariant::fromValue(b); return b.call(); } Q_INVOKABLE QJSValue method_intQJSValue(int a, int b) { m_actuals << a << b; return QJSValue();} // Should never be called. Q_INVOKABLE void method_overload(int a) { invoke(16); m_actuals << a; } diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index 09b271c5f1..72a524ece5 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -2423,6 +2423,13 @@ void tst_qqmlecmascript::regExpBug() delete object; } + { + QQmlComponent component(&engine, testFileUrl("regularExpression.qml")); + QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create())); + QVERIFY(!object.isNull()); + QCOMPARE(object->regularExpression().pattern(), QLatin1String("[a-zA-z]")); + } + //QTBUG-23068 { QString err = QString(QLatin1String("%1:6 Invalid property assignment: regular expression expected; use /pattern/ syntax\n")).arg(testFileUrl("regExp.2.qml").toString()); @@ -2432,6 +2439,18 @@ void tst_qqmlecmascript::regExpBug() QVERIFY(!object); QCOMPARE(component.errorString(), err); } + + { + const QString err = QString::fromLatin1("%1:6 Invalid property assignment: " + "regular expression expected; " + "use /pattern/ syntax\n") + .arg(testFileUrl("regularExpression.2.qml").toString()); + QQmlComponent component(&engine, testFileUrl("regularExpression.2.qml")); + QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); + MyQmlObject *object = qobject_cast<MyQmlObject*>(component.create()); + QVERIFY(!object); + QCOMPARE(component.errorString(), err); + } } static inline bool evaluate_error(QV4::ExecutionEngine *v4, const QV4::Value &o, const char *source) @@ -2486,7 +2505,7 @@ static inline bool evaluate_value(QV4::ExecutionEngine *v4, const QV4::Value &o, scope.engine->catchException(); return false; } - return QV4::Runtime::method_strictEqual(value, result); + return QV4::Runtime::StrictEqual::call(value, result); } static inline QV4::ReturnedValue evaluate(QV4::ExecutionEngine *v4, const QV4::Value &o, @@ -2816,35 +2835,35 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), 13); QCOMPARE(o->actuals().count(), 1); - QCOMPARE(o->actuals().at(0), qVariantFromValue((QObject *)nullptr)); + QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(\"Hello world\")", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), 13); QCOMPARE(o->actuals().count(), 1); - QCOMPARE(o->actuals().at(0), qVariantFromValue((QObject *)nullptr)); + QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(null)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), 13); QCOMPARE(o->actuals().count(), 1); - QCOMPARE(o->actuals().at(0), qVariantFromValue((QObject *)nullptr)); + QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), 13); QCOMPARE(o->actuals().count(), 1); - QCOMPARE(o->actuals().at(0), qVariantFromValue((QObject *)nullptr)); + QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(object)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), 13); QCOMPARE(o->actuals().count(), 1); - QCOMPARE(o->actuals().at(0), qVariantFromValue((QObject *)o)); + QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)o)); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(null)", QV4::Primitive::undefinedValue())); @@ -3120,7 +3139,7 @@ void tst_qqmlecmascript::resolveClashingProperties() QString key = name->toQStringNoThrow(); if (key == QLatin1String("clashes")) { value = v; - QV4::ScopedValue typeString(scope, QV4::Runtime::method_typeofValue(engine, value)); + QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(engine, value)); QString type = typeString->toQStringNoThrow(); if (type == QLatin1String("boolean")) { QVERIFY(!seenProperty); diff --git a/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro b/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro index 542ec44736..e8ed8e91b1 100644 --- a/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro +++ b/tests/auto/qml/qqmlinstantiator/qqmlinstantiator.pro @@ -9,4 +9,4 @@ include (../../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private testlib +QT += core-private gui-private qml-private testlib qmlmodels-private diff --git a/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp b/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp index a66f13e6bb..9c5e09c77c 100644 --- a/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp +++ b/tests/auto/qml/qqmlinstantiator/tst_qqmlinstantiator.cpp @@ -31,7 +31,7 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> -#include <QtQml/private/qqmlinstantiator_p.h> +#include <QtQmlModels/private/qqmlinstantiator_p.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlincubator.h> #include "../../shared/util.h" diff --git a/tests/auto/qml/qqmllanguage/data/bareQmlImport.errors.txt b/tests/auto/qml/qqmllanguage/data/bareQmlImport.errors.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/bareQmlImport.errors.txt diff --git a/tests/auto/qml/qqmllanguage/data/bareQmlImport.qml b/tests/auto/qml/qqmllanguage/data/bareQmlImport.qml new file mode 100644 index 0000000000..3567cdb5a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/bareQmlImport.qml @@ -0,0 +1,6 @@ +import QML 1.0 +QtObject { + property Component component: Component { + QtObject {} + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 63fc55ad0f..08dab99e0f 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -620,6 +620,8 @@ void tst_qqmllanguage::errors_data() QTest::newRow("fuzzed.1") << "fuzzed.1.qml" << "fuzzed.1.errors.txt" << false; QTest::newRow("fuzzed.2") << "fuzzed.2.qml" << "fuzzed.2.errors.txt" << false; + + QTest::newRow("bareQmlImport") << "bareQmlImport.qml" << "bareQmlImport.errors.txt" << false; } @@ -1458,8 +1460,8 @@ void tst_qqmllanguage::dynamicObjectProperties() QScopedPointer<QObject> object(component.create()); QVERIFY(object != nullptr); - QCOMPARE(object->property("objectProperty"), qVariantFromValue((QObject*)nullptr)); - QVERIFY(object->property("objectProperty2") != qVariantFromValue((QObject*)nullptr)); + QCOMPARE(object->property("objectProperty"), QVariant::fromValue((QObject*)nullptr)); + QVERIFY(object->property("objectProperty2") != QVariant::fromValue((QObject*)nullptr)); } { QQmlComponent component(&engine, testFileUrl("dynamicObjectProperties.2.qml")); @@ -1467,7 +1469,7 @@ void tst_qqmllanguage::dynamicObjectProperties() QScopedPointer<QObject> object(component.create()); QVERIFY(object != nullptr); - QVERIFY(object->property("objectProperty") != qVariantFromValue((QObject*)nullptr)); + QVERIFY(object->property("objectProperty") != QVariant::fromValue((QObject*)nullptr)); } } @@ -1736,7 +1738,7 @@ void tst_qqmllanguage::aliasProperties() // Write through alias MyQmlObject *v2 = new MyQmlObject(); v2->setParent(object.data()); - object->setProperty("aliasObject", qVariantFromValue(v2)); + object->setProperty("aliasObject", QVariant::fromValue(v2)); MyQmlObject *v3 = qvariant_cast<MyQmlObject *>(object->property("aliasObject")); QVERIFY(v3 != nullptr); @@ -3812,7 +3814,7 @@ void tst_qqmllanguage::scopedEnumsWithNameClash() { auto typeId = qmlRegisterUncreatableType<ScopedEnumsWithNameClash>("ScopedEnumsWithNameClashTest", 1, 0, "ScopedEnum", "Dummy reason"); auto registryGuard = qScopeGuard([typeId]() { - qmlUnregisterType(typeId); + QQmlMetaType::unregisterType(typeId); }); QQmlEngine engine; @@ -3831,7 +3833,7 @@ void tst_qqmllanguage::scopedEnumsWithResolvedNameClash() { auto typeId = qmlRegisterUncreatableType<ScopedEnumsWithResolvedNameClash>("ScopedEnumsWithResolvedNameClashTest", 1, 0, "ScopedEnum", "Dummy reason"); auto registryGuard = qScopeGuard([typeId]() { - qmlUnregisterType(typeId); + QQmlMetaType::unregisterType(typeId); }); QQmlEngine engine; diff --git a/tests/auto/qml/qqmllistcompositor/qqmllistcompositor.pro b/tests/auto/qml/qqmllistcompositor/qqmllistcompositor.pro index 4ada590a2a..62ad85547e 100644 --- a/tests/auto/qml/qqmllistcompositor/qqmllistcompositor.pro +++ b/tests/auto/qml/qqmllistcompositor/qqmllistcompositor.pro @@ -4,4 +4,4 @@ macx:CONFIG -= app_bundle SOURCES += tst_qqmllistcompositor.cpp -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private diff --git a/tests/auto/qml/qqmllistmodel/qqmllistmodel.pro b/tests/auto/qml/qqmllistmodel/qqmllistmodel.pro index 8e3aed0baf..4d44d6b22b 100644 --- a/tests/auto/qml/qqmllistmodel/qqmllistmodel.pro +++ b/tests/auto/qml/qqmllistmodel/qqmllistmodel.pro @@ -8,4 +8,4 @@ include (../../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private diff --git a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp index 771f3e5c4e..77ab0ecbc0 100644 --- a/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp +++ b/tests/auto/qml/qqmllistmodel/tst_qqmllistmodel.cpp @@ -30,7 +30,7 @@ #include <QtQuick/private/qquicktext_p.h> #include <QtQuick/private/qquickanimation_p.h> #include <QtQml/private/qqmlengine_p.h> -#include <QtQml/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQml/private/qqmlexpression_p.h> #include <QQmlComponent> @@ -760,11 +760,11 @@ void tst_qqmllistmodel::set() RUNEXPR("model.set(0, {test:true})"); QCOMPARE(RUNEXPR("model.get(0).test").toBool(), true); // triggers creation of model cache - QCOMPARE(model.data(0, 0), qVariantFromValue(true)); + QCOMPARE(model.data(0, 0), QVariant::fromValue(true)); RUNEXPR("model.set(0, {test:false})"); QCOMPARE(RUNEXPR("model.get(0).test").toBool(), false); // tests model cache is updated - QCOMPARE(model.data(0, 0), qVariantFromValue(false)); + QCOMPARE(model.data(0, 0), QVariant::fromValue(false)); QString warning = QString::fromLatin1("<Unknown File>: Can't create role for unsupported data type"); if (isValidErrorMessage(warning, dynamicRoles)) diff --git a/tests/auto/qml/qqmllistmodelworkerscript/qqmllistmodelworkerscript.pro b/tests/auto/qml/qqmllistmodelworkerscript/qqmllistmodelworkerscript.pro index 9e1cea9867..de58c0c075 100644 --- a/tests/auto/qml/qqmllistmodelworkerscript/qqmllistmodelworkerscript.pro +++ b/tests/auto/qml/qqmllistmodelworkerscript/qqmllistmodelworkerscript.pro @@ -8,4 +8,4 @@ include (../../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private diff --git a/tests/auto/qml/qqmllistmodelworkerscript/tst_qqmllistmodelworkerscript.cpp b/tests/auto/qml/qqmllistmodelworkerscript/tst_qqmllistmodelworkerscript.cpp index 21b0508e4d..b5e8800d0e 100644 --- a/tests/auto/qml/qqmllistmodelworkerscript/tst_qqmllistmodelworkerscript.cpp +++ b/tests/auto/qml/qqmllistmodelworkerscript/tst_qqmllistmodelworkerscript.cpp @@ -29,7 +29,7 @@ #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquicktext_p.h> #include <QtQml/private/qqmlengine_p.h> -#include <QtQml/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQml/private/qqmlexpression_p.h> #include <QQmlComponent> @@ -162,7 +162,7 @@ QQuickItem *tst_qqmllistmodelworkerscript::createWorkerTest(QQmlEngine *eng, QQm QQuickItem *item = qobject_cast<QQuickItem*>(component->create()); QQmlEngine::setContextForObject(model, eng->rootContext()); if (item) - item->setProperty("model", qVariantFromValue(model)); + item->setProperty("model", QVariant::fromValue(model)); return item; } diff --git a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp index a7805922a5..76185a97e0 100644 --- a/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp +++ b/tests/auto/qml/qqmlmetatype/tst_qqmlmetatype.cpp @@ -36,7 +36,6 @@ #include <private/qqmlmetatype_p.h> #include <private/qqmlpropertyvalueinterceptor_p.h> #include <private/qqmlengine_p.h> -#include <private/qhashedstring_p.h> #include "../../shared/util.h" class tst_qqmlmetatype : public QQmlDataTest @@ -401,7 +400,7 @@ void tst_qqmlmetatype::unregisterCustomType() QCOMPARE(enumVal.type(), QVariant::Int); QCOMPARE(enumVal.toInt(), 1); } - qmlUnregisterType(controllerId); + QQmlMetaType::unregisterType(controllerId); { QQmlEngine engine; QQmlType type = QQmlMetaType::qmlType(QString("Controller"), QString("mytypes"), 1, 0); @@ -424,7 +423,7 @@ void tst_qqmlmetatype::unregisterCustomType() QCOMPARE(enumVal.type(), QVariant::Int); QCOMPARE(enumVal.toInt(), 111); } - qmlUnregisterType(controllerId); + QQmlMetaType::unregisterType(controllerId); { QQmlEngine engine; QQmlType type = QQmlMetaType::qmlType(QString("Controller"), QString("mytypes"), 1, 0); @@ -493,7 +492,7 @@ void tst_qqmlmetatype::unregisterCustomSingletonType() QCOMPARE(stringVal.type(), QVariant::String); QCOMPARE(stringVal.toString(), QStringLiteral("StaticProvider #1")); } - qmlUnregisterType(staticProviderId); + QQmlMetaType::unregisterType(staticProviderId); { QQmlEngine engine; staticProviderId = qmlRegisterSingletonType<StaticProvider2>("mytypes", 1, 0, "StaticProvider", createStaticProvider2); @@ -509,7 +508,7 @@ void tst_qqmlmetatype::unregisterCustomSingletonType() QCOMPARE(stringVal.type(), QVariant::String); QCOMPARE(stringVal.toString(), QStringLiteral("StaticProvider #2")); } - qmlUnregisterType(staticProviderId); + QQmlMetaType::unregisterType(staticProviderId); { QQmlEngine engine; staticProviderId = qmlRegisterSingletonType<StaticProvider1>("mytypes", 1, 0, "StaticProvider", createStaticProvider1); @@ -535,7 +534,7 @@ void tst_qqmlmetatype::normalizeUrls() QVERIFY(QQmlMetaType::qmlType(url, /*includeNonFileImports=*/true).isValid()); QUrl normalizedURL("qrc:/tstqqmlmetatype/data/CompositeType.qml"); QVERIFY(QQmlMetaType::qmlType(normalizedURL, /*includeNonFileImports=*/true).isValid()); - qmlUnregisterType(registrationId); + QQmlMetaType::unregisterType(registrationId); QVERIFY(!QQmlMetaType::qmlType(url, /*includeNonFileImports=*/true).isValid()); } diff --git a/tests/auto/qml/qqmlobjectmodel/qqmlobjectmodel.pro b/tests/auto/qml/qqmlobjectmodel/qqmlobjectmodel.pro index 88bb630e29..5746ff754a 100644 --- a/tests/auto/qml/qqmlobjectmodel/qqmlobjectmodel.pro +++ b/tests/auto/qml/qqmlobjectmodel/qqmlobjectmodel.pro @@ -5,4 +5,4 @@ osx:CONFIG -= app_bundle SOURCES += tst_qqmlobjectmodel.cpp QT += qml testlib -QT += core-private qml-private +QT += core-private qml-private qmlmodels-private diff --git a/tests/auto/qml/qqmlobjectmodel/tst_qqmlobjectmodel.cpp b/tests/auto/qml/qqmlobjectmodel/tst_qqmlobjectmodel.cpp index fb63d811a8..6691fa43a0 100644 --- a/tests/auto/qml/qqmlobjectmodel/tst_qqmlobjectmodel.cpp +++ b/tests/auto/qml/qqmlobjectmodel/tst_qqmlobjectmodel.cpp @@ -25,8 +25,8 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ -#include <QtQml/private/qqmlobjectmodel_p.h> -#include <QtQml/private/qqmlchangeset_p.h> +#include <QtQmlModels/private/qqmlobjectmodel_p.h> +#include <QtQmlModels/private/qqmlchangeset_p.h> #include <QtTest/qsignalspy.h> #include <QtTest/qtest.h> diff --git a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp index 27e06c6f67..04c61ec11b 100644 --- a/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp +++ b/tests/auto/qml/qqmlproperty/tst_qqmlproperty.cpp @@ -1624,7 +1624,7 @@ void tst_qqmlproperty::writeObjectToList() MyQmlObject *object = new MyQmlObject; QQmlProperty prop(container, "children"); - prop.write(qVariantFromValue(object)); + prop.write(QVariant::fromValue(object)); QCOMPARE(list.count(), 1); QCOMPARE(list.at(0), qobject_cast<QObject*>(object)); } @@ -1641,13 +1641,13 @@ void tst_qqmlproperty::writeListToList() QList<QObject*> objList; objList << new MyQmlObject() << new MyQmlObject() << new MyQmlObject() << new MyQmlObject(); QQmlProperty prop(container, "children"); - prop.write(qVariantFromValue(objList)); + prop.write(QVariant::fromValue(objList)); QCOMPARE(list.count(), 4); //XXX need to try this with read/write prop (for read-only it correctly doesn't write) /*QList<MyQmlObject*> typedObjList; typedObjList << new MyQmlObject(); - prop.write(qVariantFromValue(&typedObjList)); + prop.write(QVariant::fromValue(&typedObjList)); QCOMPARE(container->children()->size(), 1);*/ } diff --git a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp index 02b5302a45..9a1e4667dd 100644 --- a/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp +++ b/tests/auto/qml/qqmlpropertycache/tst_qqmlpropertycache.cpp @@ -31,7 +31,6 @@ #include <QtQml/qqmlengine.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlcomponent.h> -#include <private/qv8engine_p.h> #include <private/qmetaobjectbuilder_p.h> #include <QCryptographicHash> #include "../../shared/util.h" diff --git a/tests/auto/qml/qqmltablemodel/data/TestModel.qml b/tests/auto/qml/qqmltablemodel/data/TestModel.qml new file mode 100644 index 0000000000..00e1fa65a7 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/TestModel.qml @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import Qt.labs.qmlmodels 1.0 + +import "TestUtils.js" as TestUtils + +TableModel { + id: testModel + objectName: "testModel" + + TableModelColumn { display: "name" } + TableModelColumn { display: "age" } + + rows: [ + { + name: "John", + age: 22 + }, + { + name: "Oliver", + age: 33 + } + ] +} diff --git a/tests/auto/qml/qqmltablemodel/data/TestUtils.js b/tests/auto/qml/qqmltablemodel/data/TestUtils.js new file mode 100644 index 0000000000..0b92a377bb --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/TestUtils.js @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +function testModelRoleDataProvider(index, role, cellData) { + switch (role) { + case "display": + switch (index.column) { + case 0: + return cellData.name + case 1: + return cellData.age + } + break + case "name": + return cellData.name + case "age": + return cellData.age + } + return cellData +} diff --git a/tests/auto/qml/qqmltablemodel/data/common.qml b/tests/auto/qml/qqmltablemodel/data/common.qml new file mode 100644 index 0000000000..2f8b0c072b --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/common.qml @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.13 +import Qt.labs.qmlmodels 1.0 + +Item { + id: root + width: 200 + height: 200 + + property alias testModel: testModel + property alias tableView: tableView + + function appendRow(personName, personAge) { + testModel.appendRow({ + name: personName, + age: personAge + }) + } + + function appendRowExtraData() { + testModel.appendRow({ + name: "Foo", + age: 99, + nonExistentRole: 123 + }) + } + + function appendRowInvalid1() { + testModel.appendRow(123) + } + + function appendRowInvalid2() { + testModel.appendRow({ + name: "Foo", + age: [] + }) + } + + function appendRowInvalid3() { + testModel.appendRow([ + { name: "Bar" }, + { age: "111" } + ]) + } + + function insertRow(personName, personAge, rowIndex) { + testModel.insertRow(rowIndex, { + name: personName, + age: personAge + }) + } + + function insertRowExtraData() { + testModel.insertRow(0, { + name: "Foo", + age: 99, + nonExistentRole: 123 + }) + } + + function insertRowInvalid1() { + testModel.insertRow(0, 123) + } + + function insertRowInvalid2() { + testModel.insertRow(0, { + name: "Foo", + age: [] + }) + } + + function insertRowInvalid3() { + testModel.insertRow(0, [ + { name: "Bar" }, + { age: "111" } + ]) + } + + function setRow(rowIndex, personName, personAge) { + testModel.setRow(rowIndex, { + name: personName, + age: personAge + }) + } + + function setRowExtraData() { + testModel.setRow(0, { + name: "Foo", + age: 99, + nonExistentRole: 123 + }) + } + + function setRowInvalid1() { + testModel.setRow(0, 123) + } + + function setRowInvalid2() { + testModel.setRow(0, { + name: "Foo", + age: [] + }) + } + + function setRowInvalid3() { + testModel.setRow(0, [ + { name: "Bar" }, + { age: "111" } + ]) + } + + TableView { + id: tableView + anchors.fill: parent + model: TestModel { + id: testModel + } + delegate: Text { + text: model.display + } + } +} diff --git a/tests/auto/qml/qqmltablemodel/data/complex.qml b/tests/auto/qml/qqmltablemodel/data/complex.qml new file mode 100644 index 0000000000..dbf53bac7e --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/complex.qml @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.13 +import Qt.labs.qmlmodels 1.0 + +TableView { + width: 100 + height: 100 + delegate: Item { + implicitWidth: 50 + implicitHeight: 50 + + Text { + text: model.display + anchors.centerIn: parent + } + } + model: TableModel { + id: testModel + objectName: "testModel" + + TableModelColumn { + display: function(modelIndex) { return testModel.rows[modelIndex.row][0].name } + setDisplay: function(modelIndex, cellData) { testModel.rows[modelIndex.row][0].name = cellData } + } + TableModelColumn { + display: function(modelIndex) { return testModel.rows[modelIndex.row][1].age } + setDisplay: function(modelIndex, cellData) { testModel.rows[modelIndex.row][1].age = cellData } + } + + rows: [ + [ + { name: "John" }, + { age: 22 } + ], + [ + { name: "Oliver" }, + { age: 33 } + ] + ] + } +} diff --git a/tests/auto/qml/qqmltablemodel/data/dataAndSetData.qml b/tests/auto/qml/qqmltablemodel/data/dataAndSetData.qml new file mode 100644 index 0000000000..d3f726bfa1 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/dataAndSetData.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.12 +import Qt.labs.qmlmodels 1.0 + +TableView { + width: 200; height: 200 + model: TestModel { + id: testModel + + // This is silly: in real life, store the birthdate instead of the age, + // and let the delegate calculate the age, so it won't need updating + function happyBirthday(dude) { + var row = -1; + for (var r = 0; row < 0 && r < testModel.rowCount; ++r) + if (testModel.data(testModel.index(r, 0), "display") === dude) + row = r; + var index = testModel.index(row, 1) + testModel.setData(index, "display", testModel.data(index, "display") + 1) + } + } + delegate: Text { + id: textItem + text: model.display + TapHandler { + onTapped: testModel.happyBirthday(testModel.data(testModel.index(row, 0), "display")) + } + } +} diff --git a/tests/auto/qml/qqmltablemodel/data/empty.qml b/tests/auto/qml/qqmltablemodel/data/empty.qml new file mode 100644 index 0000000000..f5afbd1d27 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/empty.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.12 +import Qt.labs.qmlmodels 1.0 + +Item { + id: root + width: 200 + height: 200 + + property alias testModel: testModel + property alias tableView: tableView + + function setRows() { + testModel.rows = [ + { + name: "John", + age: 22 + }, + { + name: "Oliver", + age: 33 + } + ] + } + + function appendJohn() { + testModel.appendRow({ + name: "John", + age: 22 + }) + } + + function appendOliver() { + testModel.appendRow({ + name: "Oliver", + age: 33 + }) + } + + TableModel { + id: testModel + objectName: "testModel" + + TableModelColumn { display: "name" } + TableModelColumn { display: "age" } + } + TableView { + id: tableView + anchors.fill: parent + model: testModel + delegate: Text { + text: model.display + } + } +} diff --git a/tests/auto/qml/qqmltablemodel/data/omitTableModelColumnIndex.qml b/tests/auto/qml/qqmltablemodel/data/omitTableModelColumnIndex.qml new file mode 100644 index 0000000000..86bcb08fa2 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/omitTableModelColumnIndex.qml @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import Qt.labs.qmlmodels 1.0 + +TableModel { + objectName: "testModel" + + TableModelColumn { display: "name" } + TableModelColumn { display: "age" } + + rows: [ + { + name: "John", + age: 22 + }, + { + name: "Oliver", + age: 33 + } + ] +} diff --git a/tests/auto/qml/qqmltablemodel/data/setDataThroughDelegate.qml b/tests/auto/qml/qqmltablemodel/data/setDataThroughDelegate.qml new file mode 100644 index 0000000000..ebfe4ed930 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/setDataThroughDelegate.qml @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.12 +import Qt.labs.qmlmodels 1.0 + +Item { + id: root + width: 200 + height: 200 + + property alias testModel: testModel + + signal shouldModify() + signal shouldModifyInvalidRole() + signal shouldModifyInvalidType() + + function modify() { + shouldModify() + } + + function modifyInvalidRole() { + shouldModifyInvalidRole() + } + + function modifyInvalidType() { + shouldModifyInvalidType() + } + + TableView { + anchors.fill: parent + model: TestModel { + id: testModel + } + + delegate: Text { + id: textItem + text: model.display + + Connections { + target: root + enabled: column === 1 + onShouldModify: model.display = 18 + } + + Connections { + target: root + enabled: column === 0 + // Invalid: should be "display". + onShouldModifyInvalidRole: model.age = 100 + } + + Connections { + target: root + enabled: column === 1 + // Invalid: should be string. + onShouldModifyInvalidType: model.display = "Whoops" + } + } + } +} diff --git a/tests/auto/qml/qqmltablemodel/data/setRowsMultipleTimes.qml b/tests/auto/qml/qqmltablemodel/data/setRowsMultipleTimes.qml new file mode 100644 index 0000000000..01ec40270c --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/data/setRowsMultipleTimes.qml @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.12 +import Qt.labs.qmlmodels 1.0 + +Item { + id: root + width: 200 + height: 200 + + property alias testModel: testModel + property alias tableView: tableView + + function setRowsValid() { + testModel.rows = [ + { + name: "Max", + age: 20 + }, + { + name: "Imum", + age: 41 + }, + { + name: "Power", + age: 89 + } + ] + } + + function setRowsInvalid() { + testModel.rows = [ + { + nope: "Nope", + age: 20 + }, + { + nope: "Nah", + age: 41 + }, + { + nope: "No", + age: 89 + } + ] + } + + TableView { + id: tableView + anchors.fill: parent + model: TestModel { + id: testModel + } + delegate: Text { + text: model.display + } + } +} diff --git a/tests/auto/qml/qqmltablemodel/qqmltablemodel.pro b/tests/auto/qml/qqmltablemodel/qqmltablemodel.pro new file mode 100644 index 0000000000..9d298dfdf2 --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/qqmltablemodel.pro @@ -0,0 +1,10 @@ +CONFIG += testcase +TARGET = tst_qqmltablemodel + +SOURCES += tst_qqmltablemodel.cpp + +include (../../shared/util.pri) + +TESTDATA = data/* + +QT += core gui qml-private qml quick-private quick testlib qmlmodels-private diff --git a/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp new file mode 100644 index 0000000000..d913bcdf9a --- /dev/null +++ b/tests/auto/qml/qqmltablemodel/tst_qqmltablemodel.cpp @@ -0,0 +1,980 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QtTest/qtest.h> +#include <QtTest/qsignalspy.h> +#include <QtCore/qregularexpression.h> +#include <QtQml/private/qqmlengine_p.h> +#include <QtQmlModels/private/qqmltablemodel_p.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuick/qquickitem.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/private/qquicktableview_p.h> + +#include "../../shared/util.h" + +class tst_QQmlTableModel : public QQmlDataTest +{ + Q_OBJECT + +public: + tst_QQmlTableModel() {} + +private slots: + void appendRemoveRow(); + void appendRowToEmptyModel(); + void clear(); + void getRow(); + void insertRow(); + void moveRow(); + void setRow(); + void setDataThroughDelegate(); + void setRowsImperatively(); + void setRowsMultipleTimes(); + void dataAndEditing(); + void omitTableModelColumnIndex(); + void complexRow(); +}; + +void tst_QQmlTableModel::appendRemoveRow() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + int rowCountSignalEmissions = 0; + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(roleNames.size(), 1); + QVERIFY(roleNames.values().contains("display")); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + + // Call remove() with a negative rowIndex. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*removeRow\\(\\): \"rowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, -1))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call remove() with an rowIndex that is too large. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*removeRow\\(\\): \"rowIndex\" 2 is greater than or equal to rowCount\\(\\) of 2")); + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 2))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call remove() with a valid rowIndex but negative rows. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*removeRow\\(\\): \"rows\" is less than or equal to zero")); + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, -1))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call remove() with a valid rowIndex but excessive rows. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*removeRow\\(\\): \"rows\" 3 exceeds available rowCount\\(\\) of 2 when removing from \"rowIndex\" 0")); + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, 3))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call remove() without specifying the number of rows to remove; it should remove one row. + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0))); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + + // Call append() with a row that has an unexpected role; the row should be added and the extra data ignored. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowExtraData")); + // Nothing should change. + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + + // Call append() with a row that is an int. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*appendRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid1")); + // Nothing should change. + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call append() with a row with a role of the wrong type. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*appendRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid2")); + // Nothing should change. + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call append() with a row that is an array instead of a simple object. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*appendRow\\(\\): row manipulation functions do not support complex rows \\(row index: -1\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRowInvalid3")); + // Nothing should change. + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Call append() to insert one row. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40))); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + + // Call remove() and specify rowIndex and rows, removing all remaining rows. + QVERIFY(QMetaObject::invokeMethod(model, "removeRow", Q_ARG(int, 0), Q_ARG(int, 3))); + QCOMPARE(model->rowCount(), 0); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")), QVariant()); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant()); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); +} + +void tst_QQmlTableModel::appendRowToEmptyModel() +{ + QQuickView view(testFileUrl("empty.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 0); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 0); + QCOMPARE(tableView->columns(), 2); + + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendJohn")); + QCOMPARE(model->rowCount(), 1); + QCOMPARE(model->columnCount(), 2); + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 1); + QTRY_COMPARE(tableView->rows(), 1); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::clear() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QVERIFY(roleNames.values().contains("display")); + QCOMPARE(roleNames.size(), 1); + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + QVERIFY(QMetaObject::invokeMethod(model, "clear")); + QCOMPARE(model->rowCount(), 0); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")), QVariant()); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")), QVariant()); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 1); + // Wait until updatePolish() gets called, which is where the size is recalculated. + QTRY_COMPARE(tableView->rows(), 0); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::getRow() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + // Call get() with a negative row index. + QVariant returnValue; + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*getRow\\(\\): \"rowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, -1))); + QVERIFY(!returnValue.isValid()); + + // Call get() with a row index that is too large. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*getRow\\(\\): \"rowIndex\" 2 is greater than or equal to rowCount\\(\\) of 2")); + QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, 2))); + QVERIFY(!returnValue.isValid()); + + // Call get() with a valid row index. + QVERIFY(QMetaObject::invokeMethod(model, "getRow", Q_RETURN_ARG(QVariant, returnValue), Q_ARG(int, 0))); + const QVariantMap rowAsVariantMap = returnValue.toMap(); + QCOMPARE(rowAsVariantMap.value(QLatin1String("name")).toString(), QLatin1String("John")); + QCOMPARE(rowAsVariantMap.value(QLatin1String("age")).toInt(), 22); +} + +void tst_QQmlTableModel::insertRow() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + int rowCountSignalEmissions = 0; + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert with a negative index. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*insertRow\\(\\): \"rowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow", + Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, -1))); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->rowCount(), 2); + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert past the last allowed index. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*insertRow\\(\\): \"rowIndex\" 3 is greater than rowCount\\(\\) of 2")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow", + Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, 3))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Call insert() with a row that is an int. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*insertRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid1")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row with a role of the wrong type. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*insertRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid2")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row that is an array instead of a simple object. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*insertRow\\(\\): row manipulation functions do not support complex rows \\(row index: 0\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowInvalid3")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row has an unexpected role; the row should be added and the extra data ignored. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRowExtraData")); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); + + // Insert a row at the bottom of the table. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow", + Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40), Q_ARG(QVariant, 3))); + QCOMPARE(model->rowCount(), 4); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 4); + QCOMPARE(tableView->columns(), 2); + + // Insert a row in the middle of the table. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "insertRow", + Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30), Q_ARG(QVariant, 2))); + QCOMPARE(model->rowCount(), 5); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 5); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::moveRow() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->rowCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + int rowCountSignalEmissions = 0; + + const QHash<int, QByteArray> roleNames = model->roleNames(); + + // Append some rows. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40))); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30))); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "appendRow", Q_ARG(QVariant, QLatin1String("Trev")), Q_ARG(QVariant, 48))); + QCOMPARE(model->rowCount(), 5); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + rowCountSignalEmissions = 3; + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Try to move with a fromRowIndex that is negative. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*moveRow\\(\\): \"fromRowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, -1), Q_ARG(int, 1))); + // Shouldn't have changed. + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Try to move with a fromRowIndex that is too large. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*moveRow\\(\\): \"fromRowIndex\" 5 is greater than or equal to rowCount\\(\\)")); + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 5), Q_ARG(int, 1))); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Try to move with a toRowIndex that is negative. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*moveRow\\(\\): \"toRowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, -1))); + // Shouldn't have changed. + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Try to move with a toRowIndex that is too large. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*moveRow\\(\\): \"toRowIndex\" 5 is greater than or equal to rowCount\\(\\)")); + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 5))); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Move the first row to the end. + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 4))); + // The counts shouldn't have changed. + QCOMPARE(model->rowCount(), 5); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Move it back again. + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 4), Q_ARG(int, 0))); + QCOMPARE(model->rowCount(), 5); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + + // Move the first row down one by one row. + QVERIFY(QMetaObject::invokeMethod(model, "moveRow", Q_ARG(int, 0), Q_ARG(int, 1))); + QCOMPARE(model->rowCount(), 5); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(3, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(3, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(4, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Trev")); + QCOMPARE(model->data(model->index(4, 1, QModelIndex()), roleNames.key("display")).toInt(), 48); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); +} + +void tst_QQmlTableModel::setRow() +{ + QQuickView view(testFileUrl("common.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->rowCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + int rowCountSignalEmissions = 0; + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to set with a negative index. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRow\\(\\): \"rowIndex\" cannot be negative")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow", + Q_ARG(QVariant, -1), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to set at an index past the last allowed index. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRow\\(\\): \"rowIndex\" 3 is greater than rowCount\\(\\) of 2")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow", + Q_ARG(QVariant, 3), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to set a row that has an unexpected role; the row should be set and the extra data ignored. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowExtraData")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row that is not an array. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRow\\(\\): expected \"row\" argument to be a QJSValue, but got int instead:\nQVariant\\(int, 123\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid1")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row with a role that is of the wrong type. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRow\\(\\): expected the property named \"age\" to be of type \"int\", but got \"QVariantList\" instead")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid2")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Try to insert a row that is an array instead of a simple object. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRow\\(\\): row manipulation functions do not support complex rows \\(row index: 0\\)")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowInvalid3")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Foo")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Set the first row. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow", + Q_ARG(QVariant, 0), Q_ARG(QVariant, QLatin1String("Max")), Q_ARG(QVariant, 40))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Set the last row. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow", + Q_ARG(QVariant, 1), Q_ARG(QVariant, QLatin1String("Daisy")), Q_ARG(QVariant, 30))); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), rowCountSignalEmissions); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Append a row by passing an index that is equal to rowCount(). + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRow", + Q_ARG(QVariant, 2), Q_ARG(QVariant, QLatin1String("Wot")), Q_ARG(QVariant, 99))); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 40); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Daisy")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 30); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Wot")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 99); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), ++rowCountSignalEmissions); + QTRY_COMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::setDataThroughDelegate() +{ + QQuickView view(testFileUrl("setDataThroughDelegate.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(roleNames.size(), 1); + QVERIFY(roleNames.values().contains("display")); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 0); + + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modify")); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 0); + + // Test setting a role that doesn't exist for a certain column. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modifyInvalidRole")); + // Should be unchanged. + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 0); + + // Test setting a role with a value of the wrong type. + // There are two rows, so two delegates respond to the signal, which means we need to ignore two warnings. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*setData\\(\\): failed converting value QVariant\\(QString, \"Whoops\"\\) " \ + "set at row 0 column 1 with role \"display\" to \"int\"")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*setData\\(\\): failed converting value QVariant\\(QString, \"Whoops\"\\) " \ + "set at row 1 column 1 with role \"display\" to \"int\"")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "modifyInvalidType")); + // Should be unchanged. + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 18); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 0); +} + +// Start off with empty rows and then set them to test rowCountChanged(). +void tst_QQmlTableModel::setRowsImperatively() +{ + QQuickView view(testFileUrl("empty.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 0); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 0); + QCOMPARE(tableView->columns(), 2); + + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRows")); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 1); + QTRY_COMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::setRowsMultipleTimes() +{ + QQuickView view(testFileUrl("setRowsMultipleTimes.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("testModel").value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + QSignalSpy columnCountSpy(model, SIGNAL(columnCountChanged())); + QVERIFY(columnCountSpy.isValid()); + + QSignalSpy rowCountSpy(model, SIGNAL(rowCountChanged())); + QVERIFY(rowCountSpy.isValid()); + + QQuickTableView *tableView = view.rootObject()->property("tableView").value<QQuickTableView*>(); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + // Set valid rows after they've already been declared. + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowsValid")); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 20); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Imum")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 41); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Power")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 89); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 1); + QTRY_COMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); + + // Set invalid rows; we should get a warning and nothing should change. + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + ".*setRows\\(\\): expected a property named \"name\" in row at index 0, but couldn't find one")); + QVERIFY(QMetaObject::invokeMethod(view.rootObject(), "setRowsInvalid")); + QCOMPARE(model->rowCount(), 3); + QCOMPARE(model->columnCount(), 2); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Max")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 20); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Imum")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 41); + QCOMPARE(model->data(model->index(2, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Power")); + QCOMPARE(model->data(model->index(2, 1, QModelIndex()), roleNames.key("display")).toInt(), 89); + QCOMPARE(columnCountSpy.count(), 0); + QCOMPARE(rowCountSpy.count(), 1); + QCOMPARE(tableView->rows(), 3); + QCOMPARE(tableView->columns(), 2); +} + +void tst_QQmlTableModel::dataAndEditing() +{ + QQuickView view(testFileUrl("dataAndSetData.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQmlTableModel *model = view.rootObject()->property("model").value<QQmlTableModel*>(); + QVERIFY(model); + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QVERIFY(roleNames.values().contains("display")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); + QVERIFY(QMetaObject::invokeMethod(model, "happyBirthday", Q_ARG(QVariant, QLatin1String("Oliver")))); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 34); +} + +void tst_QQmlTableModel::omitTableModelColumnIndex() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("omitTableModelColumnIndex.qml")); + QCOMPARE(component.status(), QQmlComponent::Ready); + + QScopedPointer<QQmlTableModel> model(qobject_cast<QQmlTableModel*>(component.create())); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); +} + +void tst_QQmlTableModel::complexRow() +{ + QQuickView view(testFileUrl("complex.qml")); + QCOMPARE(view.status(), QQuickView::Ready); + view.show(); + QVERIFY(QTest::qWaitForWindowActive(&view)); + + QQuickTableView *tableView = qobject_cast<QQuickTableView*>(view.rootObject()); + QVERIFY(tableView); + QCOMPARE(tableView->rows(), 2); + QCOMPARE(tableView->columns(), 2); + + QQmlTableModel *model = tableView->model().value<QQmlTableModel*>(); + QVERIFY(model); + QCOMPARE(model->rowCount(), 2); + QCOMPARE(model->columnCount(), 2); + + const QHash<int, QByteArray> roleNames = model->roleNames(); + QCOMPARE(model->data(model->index(0, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("John")); + QCOMPARE(model->data(model->index(0, 1, QModelIndex()), roleNames.key("display")).toInt(), 22); + QCOMPARE(model->data(model->index(1, 0, QModelIndex()), roleNames.key("display")).toString(), QLatin1String("Oliver")); + QCOMPARE(model->data(model->index(1, 1, QModelIndex()), roleNames.key("display")).toInt(), 33); +} + +QTEST_MAIN(tst_QQmlTableModel) + +#include "tst_qqmltablemodel.moc" diff --git a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp index 15bd031ce3..83a37df797 100644 --- a/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp +++ b/tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp @@ -1713,7 +1713,7 @@ void tst_qqmlvaluetypes::sequences() QJSValue value = engine.toScriptValue(qcharVector); QCOMPARE(value.property("length").toInt(), qcharVector.length()); for (int i = 0; i < qcharVector.length(); ++i) - QCOMPARE(value.property(i).toInt(), qcharVector.at(i)); + QCOMPARE(value.property(i).toString(), qcharVector.at(i)); } { MyTypeObject a, b, c; diff --git a/tests/auto/qml/qquickworkerscript/qquickworkerscript.pro b/tests/auto/qml/qquickworkerscript/qquickworkerscript.pro index be8b9089a2..f58fd4543f 100644 --- a/tests/auto/qml/qquickworkerscript/qquickworkerscript.pro +++ b/tests/auto/qml/qquickworkerscript/qquickworkerscript.pro @@ -8,4 +8,4 @@ include (../../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private testlib +QT += core-private gui-private qml-private testlib qmlworkerscript-private diff --git a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp index 4ad58ba56c..bea9978f0b 100644 --- a/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp +++ b/tests/auto/qml/qquickworkerscript/tst_qquickworkerscript.cpp @@ -30,6 +30,7 @@ #include <QtCore/qtimer.h> #include <QtCore/qdir.h> #include <QtCore/qfileinfo.h> +#include <QtCore/qregularexpression.h> #include <QtQml/qjsengine.h> #include <QtQml/qqmlcomponent.h> @@ -91,14 +92,14 @@ void tst_QQuickWorkerScript::source() QCOMPARE(worker->source(), source); QVERIFY(QMetaObject::invokeMethod(worker.data(), "testSend", Q_ARG(QVariant, value))); waitForEchoMessage(worker.data()); - QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), qVariantFromValue(QString("Hello_World"))); + QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), QVariant::fromValue(QString("Hello_World"))); source = testFileUrl("script_module.mjs"); worker->setSource(source); QCOMPARE(worker->source(), source); QVERIFY(QMetaObject::invokeMethod(worker.data(), "testSend", Q_ARG(QVariant, value))); waitForEchoMessage(worker.data()); - QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), qVariantFromValue(QString("Hello from the module"))); + QCOMPARE(mo->property(mo->indexOfProperty("response")).read(worker.data()).value<QVariant>(), QVariant::fromValue(QString("Hello from the module"))); qApp->processEvents(); } @@ -118,7 +119,18 @@ void tst_QQuickWorkerScript::messaging() QVariant response = mo->property(mo->indexOfProperty("response")).read(worker).value<QVariant>(); if (response.userType() == qMetaTypeId<QJSValue>()) response = response.value<QJSValue>().toVariant(); - QCOMPARE(response, value); + + if (value.type() == QMetaType::QRegExp && response.type() == QMetaType::QRegularExpression) { + // toVariant() doesn't know if we want QRegExp or QRegularExpression. It always creates + // a QRegularExpression from a JavaScript regular expression. + const QRegularExpression responseRegExp = response.toRegularExpression(); + const QRegExp valueRegExp = value.toRegExp(); + QCOMPARE(responseRegExp.pattern(), valueRegExp.pattern()); + QCOMPARE(bool(responseRegExp.patternOptions() & QRegularExpression::CaseInsensitiveOption), + bool(valueRegExp.caseSensitivity() == Qt::CaseInsensitive)); + } else { + QCOMPARE(response, value); + } qApp->processEvents(); delete worker; @@ -129,16 +141,16 @@ void tst_QQuickWorkerScript::messaging_data() QTest::addColumn<QVariant>("value"); QTest::newRow("invalid") << QVariant(); - QTest::newRow("bool") << qVariantFromValue(true); - QTest::newRow("int") << qVariantFromValue(1001); - QTest::newRow("real") << qVariantFromValue(10334.375); - QTest::newRow("string") << qVariantFromValue(QString("More cheeeese, Gromit!")); - QTest::newRow("variant list") << qVariantFromValue((QVariantList() << "a" << "b" << "c")); - QTest::newRow("date time") << qVariantFromValue(QDateTime::currentDateTime()); -#ifndef QT_NO_REGEXP - // Qt Script's QScriptValue -> QRegExp uses RegExp2 pattern syntax - QTest::newRow("regexp") << qVariantFromValue(QRegExp("^\\d\\d?$", Qt::CaseInsensitive, QRegExp::RegExp2)); -#endif + QTest::newRow("bool") << QVariant::fromValue(true); + QTest::newRow("int") << QVariant::fromValue(1001); + QTest::newRow("real") << QVariant::fromValue(10334.375); + QTest::newRow("string") << QVariant::fromValue(QString("More cheeeese, Gromit!")); + QTest::newRow("variant list") << QVariant::fromValue((QVariantList() << "a" << "b" << "c")); + QTest::newRow("date time") << QVariant::fromValue(QDateTime::currentDateTime()); + QTest::newRow("regexp") << QVariant::fromValue(QRegExp("^\\d\\d?$", Qt::CaseInsensitive, + QRegExp::RegExp2)); + QTest::newRow("regularexpression") << QVariant::fromValue(QRegularExpression( + "^\\d\\d?$", QRegularExpression::CaseInsensitiveOption)); } void tst_QQuickWorkerScript::messaging_sendQObjectList() @@ -153,9 +165,9 @@ void tst_QQuickWorkerScript::messaging_sendQObjectList() QVariantList objects; for (int i=0; i<3; i++) - objects << qVariantFromValue(new QObject(this)); + objects << QVariant::fromValue(new QObject(this)); - QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, qVariantFromValue(objects)))); + QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, QVariant::fromValue(objects)))); waitForEchoMessage(worker); const QMetaObject *mo = worker->metaObject(); @@ -181,10 +193,10 @@ void tst_QQuickWorkerScript::messaging_sendJsObject() map.insert("name", "zyz"); map.insert("spell power", 3101); - QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, qVariantFromValue(map)))); + QVERIFY(QMetaObject::invokeMethod(worker, "testSend", Q_ARG(QVariant, QVariant::fromValue(map)))); waitForEchoMessage(worker); - QVariant result = qVariantFromValue(false); + QVariant result = QVariant::fromValue(false); QVERIFY(QMetaObject::invokeMethod(worker, "compareLiteralResponse", Qt::DirectConnection, Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, jsObject))); QVERIFY(result.toBool()); diff --git a/tests/auto/qml/qv4assembler/tst_qv4assembler.cpp b/tests/auto/qml/qv4assembler/tst_qv4assembler.cpp index fd50ff5020..392ce16880 100644 --- a/tests/auto/qml/qv4assembler/tst_qv4assembler.cpp +++ b/tests/auto/qml/qv4assembler/tst_qv4assembler.cpp @@ -140,23 +140,17 @@ void tst_QV4Assembler::functionTable() #endif } -#ifdef V4_ENABLE_JIT -#define JIT_ENABLED 1 -#else -#define JIT_ENABLED 0 -#endif - void tst_QV4Assembler::jitEnabled() { #if defined(Q_OS_IOS) || defined(Q_OS_TVOS) /* JIT should be disabled on iOS and tvOS. */ - QCOMPARE(JIT_ENABLED, 0); + QVERIFY(!QT_CONFIG(qml_jit)); #elif defined(Q_OS_WIN) && defined(Q_PROCESSOR_ARM) /* JIT should be disabled Windows on ARM/ARM64 for now. */ - QCOMPARE(JIT_ENABLED, 0); + QVERIFY(!QT_CONFIG(qml_jit)); #else /* JIT should be enabled on all other architectures/OSes tested in CI. */ - QCOMPARE(JIT_ENABLED, 1); + QVERIFY(QT_CONFIG(qml_jit)); #endif } diff --git a/tests/auto/qml/v4traced/tst_v4traced.cpp b/tests/auto/qml/v4traced/tst_v4traced.cpp deleted file mode 100644 index f82cc0ed5e..0000000000 --- a/tests/auto/qml/v4traced/tst_v4traced.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/**************************************************************************** -** -** 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 <QtTest/QtTest> -#include <QJSEngine> -#include <private/qjsvalue_p.h> -#include <private/qv4scopedvalue_p.h> -#include <private/qv4script_p.h> - -class EnvVarSaver -{ -public: - EnvVarSaver(const char *name, const QByteArray &newValue) - : _name(name) - { - _wasSet = qEnvironmentVariableIsSet(name); - if (_wasSet) - _oldValue = qgetenv(name); - qputenv(name, newValue); - } - - ~EnvVarSaver() - { - if (_wasSet) - qputenv(_name, _oldValue); - else - qunsetenv(_name); - } - -private: - const char *_name; - bool _wasSet; - QByteArray _oldValue; -}; - -class tst_v4traced : public QObject -{ - Q_OBJECT - -private slots: - void collectTraces_data(); - void collectTraces(); - - void binopI32deopt_data(); - void binopI32deopt(); - - void calls_data(); - void calls(); - - void setLookup(); - void construct(); -}; - -void tst_v4traced::collectTraces_data() -{ - QTest::addColumn<QString>("code"); - QTest::addColumn<int>("tracePointCount"); - QTest::addColumn<int>("interestingTracePoint"); - QTest::addColumn<quint8>("expectedBits"); - - QTest::newRow("int+") << "var a = 4; a + 2" << 2 << 1 << quint8(QV4::ObservedTraceValues::Integer); - QTest::newRow("double+") << "var a = 4.1; a + 1.9" << 2 << 1 << quint8(QV4::ObservedTraceValues::Double); - QTest::newRow("object+") << "var a = '4'; a + '2'" << 2 << 1 << quint8(QV4::ObservedTraceValues::Other); -} - -void tst_v4traced::collectTraces() -{ - QFETCH(QString, code); - QFETCH(int, tracePointCount); - QFETCH(int, interestingTracePoint); - QFETCH(quint8, expectedBits); - - EnvVarSaver forceInterpreter("QV4_FORCE_INTERPRETER", "1"); - EnvVarSaver forceTracing("QV4_FORCE_TRACING", "1"); - - QV4::ExecutionEngine vm; - QV4::Scope scope(&vm); - QV4::ScopedContext ctx(scope, vm.rootContext()); - QV4::ScopedValue result(scope); - QScopedPointer<QV4::Script> script; - script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, "collectTraces")); - script->parseAsBinding = false; - - QVERIFY(!scope.engine->hasException); - script->parse(); - QVERIFY(!scope.engine->hasException); - - QVERIFY(script->function()->tracingEnabled()); - result = script->run(); - QVERIFY(!scope.engine->hasException); - - QCOMPARE(int(script->function()->compiledFunction->nTraceInfos), tracePointCount); - QCOMPARE(*script->function()->traceInfo(interestingTracePoint), expectedBits); -} - -void tst_v4traced::binopI32deopt_data() -{ - QTest::addColumn<QString>("operand"); - QTest::addColumn<int>("int_arg1"); - QTest::addColumn<int>("int_arg2"); - QTest::addColumn<int>("int_result"); - QTest::addColumn<QString>("other_arg1"); - QTest::addColumn<QString>("other_arg2"); - QTest::addColumn<double>("other_result"); - - QTest::newRow("+") << "+" << 1 << 2 << 3 << "1.1" << "1.9" << 3.0; - QTest::newRow("-") << "-" << 3 << 2 << 1 << "3.1" << "2.1" << 1.0; - QTest::newRow("*") << "*" << 2 << 3 << 6 << "2.1" << "1.9" << 3.99; - QTest::newRow("/") << "/" << 6 << 3 << 2 << "6.6" << "3.3" << 2.0; - - QTest::newRow("&") << "&" << 6 << 3 << 2 << "'6'" << "'3'" << 2.0; - QTest::newRow("|") << "|" << 6 << 3 << 7 << "'6'" << "'3'" << 7.0; - QTest::newRow("^") << "^" << 6 << 3 << 5 << "'6'" << "'3'" << 5.0; - - QTest::newRow("<<") << "<<" << 5 << 1 << 10 << "'5'" << "'1'" << 10.0; - QTest::newRow(">>") << ">>" << -1 << 1 << -1 << "'-1'" << "'1'" << -1.0; - QTest::newRow(">>>") << ">>>" << -1 << 1 << 0x7FFFFFFF << "'-1'" << "'1'" << 2147483647.0; - - QTest::newRow("==") << "==" << 2 << 1 << 0 << "'2'" << "'1'" << 0.0; - QTest::newRow("!=") << "!=" << 2 << 1 << 1 << "'2'" << "'1'" << 1.0; - QTest::newRow("<" ) << "<" << 2 << 1 << 0 << "'2'" << "'1'" << 0.0; - QTest::newRow("<=") << "<=" << 2 << 1 << 0 << "'2'" << "'1'" << 0.0; - QTest::newRow(">" ) << ">" << 2 << 1 << 1 << "'2'" << "'1'" << 1.0; - QTest::newRow(">=") << ">=" << 2 << 1 << 1 << "'2'" << "'1'" << 1.0; -} - -void tst_v4traced::binopI32deopt() -{ - QFETCH(QString, operand); - QFETCH(int, int_arg1); - QFETCH(int, int_arg2); - QFETCH(int, int_result); - QFETCH(QString, other_arg1); - QFETCH(QString, other_arg2); - QFETCH(double, other_result); - - QString func = QStringLiteral("function binopI32(a, b) { return a %1 b }").arg(operand); - QString intCall = QStringLiteral("binopI32(%1, %2)").arg(int_arg1).arg(int_arg2); - QString otherCall = QStringLiteral("binopI32(%1, %2)").arg(other_arg1).arg(other_arg2); - - QJSEngine engine; - engine.evaluate(func); - - QCOMPARE(engine.evaluate(intCall).toInt(), int_result); // interpret + trace - QCOMPARE(engine.evaluate(intCall).toInt(), int_result); // jit - QCOMPARE(engine.evaluate(otherCall).toNumber(), other_result); // deopt - QCOMPARE(engine.evaluate(otherCall).toNumber(), other_result); // retrace - QCOMPARE(engine.evaluate(otherCall).toNumber(), other_result); // rejit -} - -void tst_v4traced::calls_data() -{ - QTest::addColumn<QString>("call"); - - QTest::newRow("callGlobalLookup") << "globalLookup"; - QTest::newRow("callPropertyLookup") << "obj.propertyLookup"; -} - -class Calls -{ -public: - static int callCount; - - static QV4::ReturnedValue doSomething(const QV4::FunctionObject */*o*/, - const QV4::Value */*thiz*/, - const QV4::Value *argv, int argc) - { - ++callCount; - - if (argc == 0) - return QV4::Encode(42); - - int prod = 1; - for (int i = 0; i < argc; ++i) { - Q_ASSERT(argv[i].isInteger()); - prod *= argv[i].int_32(); - } - return QV4::Encode(prod); - } -}; - -int Calls::callCount = 0; - -void tst_v4traced::calls() -{ - QFETCH(QString, call); - - EnvVarSaver forceTracing("QV4_FORCE_TRACING", "1"); - EnvVarSaver jitCallThreshold("QV4_JIT_CALL_THRESHOLD", "1"); - - QV4::ExecutionEngine vm; - QV4::Scope scope(&vm); - QV4::ScopedContext ctx(scope, vm.rootContext()); - vm.globalObject->defineDefaultProperty(QLatin1String("globalLookup"), - Calls::doSomething); - QV4::ScopedObject obj(scope, vm.newObject()); - vm.globalObject->defineDefaultProperty(QLatin1String("obj"), obj); - obj->defineDefaultProperty("propertyLookup", Calls::doSomething); - - QString code = QStringLiteral( - "function doCalls() {\n" - " if (%1() != 42) return false\n" - " if (%1(21, 2) != 42) return false\n" - " if (%1(2, 3, 7) != 42) return false\n" - " return true\n" - "}\n" - "var result = true\n" - "for (var i = 0; i < 10; ++i) {\n" - " if (!doCalls()) { result = false; break }" - "}\n" - "result\n").arg(call); - QScopedPointer<QV4::Script> script; - script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, "call")); - script->parseAsBinding = false; - - QVERIFY(!scope.engine->hasException); - script->parse(); - QVERIFY(!scope.engine->hasException); - - Calls::callCount = 0; - QV4::ScopedValue result(scope, script->run()); - QVERIFY(!scope.engine->hasException); - - QVERIFY(result->isBoolean()); - QVERIFY(result->booleanValue()); - QCOMPARE(Calls::callCount, 30); -} - -void tst_v4traced::setLookup() -{ - EnvVarSaver forceTracing("QV4_FORCE_TRACING", "1"); - EnvVarSaver jitCallThreshold("QV4_JIT_CALL_THRESHOLD", "1"); - - QV4::ExecutionEngine vm; - QV4::Scope scope(&vm); - QV4::ScopedContext ctx(scope, vm.rootContext()); - QV4::ScopedObject obj(scope, vm.newObject()); - vm.globalObject->defineDefaultProperty(QLatin1String("oracle"), obj); - obj->defineDefaultProperty("answer", QV4::Primitive::fromInt32(32)); - - QString code = QStringLiteral( - "function doit() {\n" - " ++oracle.answer\n" - "}\n" - "for (var i = 0; i < 10; ++i) doit()\n" - "oracle.answer\n"); - QScopedPointer<QV4::Script> script; - script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, "setLookup")); - script->parseAsBinding = false; - - QVERIFY(!scope.engine->hasException); - script->parse(); - QVERIFY(!scope.engine->hasException); - - QV4::ScopedValue result(scope, script->run()); - QVERIFY(!scope.engine->hasException); - - QVERIFY(result->isInteger()); - QCOMPARE(result->int_32(), 42); -} - -void tst_v4traced::construct() -{ - EnvVarSaver forceTracing("QV4_FORCE_TRACING", "1"); - EnvVarSaver jitCallThreshold("QV4_JIT_CALL_THRESHOLD", "1"); - - QV4::ExecutionEngine vm; - QV4::Scope scope(&vm); - QV4::ScopedContext ctx(scope, vm.rootContext()); - QV4::ScopedObject obj(scope, vm.newObject()); - vm.globalObject->defineDefaultProperty(QLatin1String("oracle"), obj); - obj->defineDefaultProperty("answer", QV4::Primitive::fromInt32(32)); - - QString code = QStringLiteral( - "function doit() {\n" - " this.arr = new Array()\n" - " this.arr[0] = 0\n" - " this.arr[1] = 1\n" - " this.arr[2] = 2\n" - "}\n" - "var o\n" - "for (var i = 0; i < 10; ++i) o = new doit()\n" - "o.arr\n"); - QScopedPointer<QV4::Script> script; - script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, "setLookup")); - script->parseAsBinding = false; - - QVERIFY(!scope.engine->hasException); - script->parse(); - QVERIFY(!scope.engine->hasException); - - QV4::ScopedValue result(scope, script->run()); - QVERIFY(!scope.engine->hasException); - - QVERIFY(result->as<QV4::ArrayObject>()); -} - -QTEST_MAIN(tst_v4traced) - -#include "tst_v4traced.moc" diff --git a/tests/auto/qml/v4traced/v4traced.pro b/tests/auto/qml/v4traced/v4traced.pro deleted file mode 100644 index cbc8f38046..0000000000 --- a/tests/auto/qml/v4traced/v4traced.pro +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG += testcase -TARGET = tst_v4traced -macos:CONFIG -= app_bundle -QT += core-private qml-private testlib -SOURCES += tst_v4traced.cpp diff --git a/tests/auto/quick/examples/tst_examples.cpp b/tests/auto/quick/examples/tst_examples.cpp index 9b3fa8fd2c..fdefa855e4 100644 --- a/tests/auto/quick/examples/tst_examples.cpp +++ b/tests/auto/quick/examples/tst_examples.cpp @@ -74,6 +74,7 @@ tst_examples::tst_examples() { // Add files to exclude here excludedFiles << "snippets/qml/listmodel/listmodel.qml"; //Just a ListModel, no root QQuickItem + excludedFiles << "snippets/qml/tablemodel/fruit-example-delegatechooser.qml"; // Requires QtQuick.Controls import. // Add directories you want excluded here excludedDirs << "shared"; //Not an example diff --git a/tests/auto/quick/pointerhandlers/pointerhandlers.pro b/tests/auto/quick/pointerhandlers/pointerhandlers.pro index 950d6835eb..4d6311bdb2 100644 --- a/tests/auto/quick/pointerhandlers/pointerhandlers.pro +++ b/tests/auto/quick/pointerhandlers/pointerhandlers.pro @@ -10,4 +10,5 @@ qtConfig(private_tests) { qquickpointerhandler \ qquickpointhandler \ qquicktaphandler \ + qquickwheelhandler \ } diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml new file mode 100644 index 0000000000..49e44f2b1f --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/nested.qml @@ -0,0 +1,55 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 320; height: 240 + color: "lightsteelblue"; antialiasing: true + border.color: outerWheelHandler.active ? "red" : "white" + + WheelHandler { + id: outerWheelHandler + objectName: "outerWheelHandler" + property: "x" + } + + Rectangle { + width: 120; height: 120; x: 100; y: 60 + color: "beige"; antialiasing: true + border.color: innerWheelHandler.active ? "red" : "white" + + WheelHandler { + id: innerWheelHandler + objectName: "innerWheelHandler" + // TODO should ideally deactivate because events go to the outer handler, not because of timeout + activeTimeout: 0.5 + property: "x" + } + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml new file mode 100644 index 0000000000..d4875d5313 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/data/rectWheel.qml @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +import QtQuick 2.14 + +Rectangle { + width: 320; height: 240 + color: "green"; antialiasing: true + + Rectangle { + width: 100; height: 2; anchors.centerIn: parent + Rectangle { + width: 2; height: 100; anchors.centerIn: parent + } + } + + WheelHandler { + activeTimeout: 0.5 + } +} diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro b/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro new file mode 100644 index 0000000000..7509e38dd3 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/qquickwheelhandler.pro @@ -0,0 +1,14 @@ +CONFIG += testcase +TARGET = tst_qquickwheelhandler +macos:CONFIG -= app_bundle + +SOURCES += tst_qquickwheelhandler.cpp +OTHER_FILES = \ + data/rectWheel.qml \ + +include (../../../shared/util.pri) +include (../../shared/util.pri) + +TESTDATA = data/* + +QT += core-private gui-private qml-private quick-private testlib diff --git a/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp b/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp new file mode 100644 index 0000000000..2abf2ea8c3 --- /dev/null +++ b/tests/auto/quick/pointerhandlers/qquickwheelhandler/tst_qquickwheelhandler.cpp @@ -0,0 +1,344 @@ +/**************************************************************************** +** +** 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 <QtTest/QtTest> +#include <QtTest/QSignalSpy> +#include <QtGui/QStyleHints> +#include <qpa/qwindowsysteminterface.h> +#include <private/qquickwheelhandler_p.h> +#include <QtQuick/private/qquickrectangle_p.h> +#include <QtQuick/qquickview.h> +#include <QtQml/qqmlcontext.h> +#include "../../../shared/util.h" +#include "../../shared/viewtestutil.h" + +Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") + +class tst_QQuickWheelHandler: public QQmlDataTest +{ + Q_OBJECT +public: + tst_QQuickWheelHandler() { } + +private slots: + void singleHandler_data(); + void singleHandler(); + void nestedHandler_data(); + void nestedHandler(); + +private: + void sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta, + QPoint pixelDelta = QPoint(), Qt::KeyboardModifiers modifiers = Qt::NoModifier, + Qt::ScrollPhase phase = Qt::NoScrollPhase, bool inverted = false); +}; + +void tst_QQuickWheelHandler::sendWheelEvent(QQuickView &window, QPoint pos, QPoint angleDelta, + QPoint pixelDelta, Qt::KeyboardModifiers modifiers, Qt::ScrollPhase phase, bool inverted) +{ + QWheelEvent wheelEvent(pos, window.mapToGlobal(pos), pixelDelta, angleDelta, + Qt::NoButton, modifiers, phase, inverted); + QGuiApplication::sendEvent(&window, &wheelEvent); + qApp->processEvents(); +} + +void tst_QQuickWheelHandler::singleHandler_data() +{ + // handler properties + QTest::addColumn<Qt::Orientation>("orientation"); + QTest::addColumn<bool>("invertible"); + QTest::addColumn<int>("rotationScale"); + QTest::addColumn<QString>("property"); + QTest::addColumn<qreal>("targetScaleMultiplier"); + QTest::addColumn<bool>("targetTransformAroundCursor"); + // event + QTest::addColumn<QPoint>("eventPos"); + QTest::addColumn<QPoint>("eventAngleDelta"); + QTest::addColumn<QPoint>("eventPixelDelta"); + QTest::addColumn<Qt::KeyboardModifiers>("eventModifiers"); + QTest::addColumn<bool>("eventPhases"); + QTest::addColumn<bool>("eventInverted"); + // result + QTest::addColumn<QPoint>("expectedPosition"); + QTest::addColumn<qreal>("expectedScale"); + QTest::addColumn<int>("expectedRotation"); + + // move the item + QTest::newRow("vertical wheel angle delta to adjust x") + << Qt::Vertical << false << 1 << "x" << 1.5 << true + << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(15, 0) << 1.0 << 0; + QTest::newRow("horizontal wheel angle delta to adjust y") + << Qt::Horizontal << false << 1 << "y" << 1.5 << false + << QPoint(160, 120) << QPoint(-360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(0, -45) << 1.0 << 0; + QTest::newRow("vertical wheel angle delta to adjust y, amplified and inverted") + << Qt::Vertical << true << 4 << "y" << 1.5 << true + << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << true + << QPoint(0, 30) << 1.0 << 0; + QTest::newRow("horizontal wheel angle delta to adjust x, amplified and reversed") + << Qt::Horizontal << false << -4 << "x" << 1.5 << false + << QPoint(160, 120) << QPoint(60, 60) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(-30, 0) << 1.0 << 0; + QTest::newRow("vertical wheel pixel delta to adjust x") + << Qt::Vertical << false << 1 << "x" << 1.5 << true + << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false + << QPoint(20, 0) << 1.0 << 0; + QTest::newRow("horizontal wheel pixel delta to adjust y") + << Qt::Horizontal << false << 1 << "y" << 1.5 << false + << QPoint(160, 120) << QPoint(-360, 120) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false + << QPoint(0, 20) << 1.0 << 0; + QTest::newRow("vertical wheel pixel delta to adjust y, amplified and inverted") + << Qt::Vertical << true << 4 << "y" << 1.5 << true + << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << true + << QPoint(0, 80) << 1.0 << 0; + QTest::newRow("horizontal wheel pixel delta to adjust x, amplified and reversed") + << Qt::Horizontal << false << -4 << "x" << 1.5 << false + << QPoint(160, 120) << QPoint(60, 60) << QPoint(20, 20) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false + << QPoint(-80, 0) << 1.0 << 0; + + // scale the item + QTest::newRow("vertical wheel angle delta to adjust scale") + << Qt::Vertical << false << 1 << "scale" << 1.5 << true + << QPoint(50, 32) << QPoint(360, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(55, 44) << 1.5 << 0; + QTest::newRow("horizontal wheel angle delta to adjust scale, amplified and reversed, don't adjust position") + << Qt::Horizontal << false << -2 << "scale" << 1.5 << false + << QPoint(50, 32) << QPoint(-240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(0, 0) << 5.0625 << 0; + + // rotate the item + QTest::newRow("vertical wheel angle delta to adjust rotation") + << Qt::Vertical << false << 1 << "rotation" << 1.5 << true + << QPoint(50, 32) << QPoint(360, -120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(19, -31) << 1.0 << -15; + QTest::newRow("horizontal wheel angle delta to adjust rotation, amplified and reversed, don't adjust position") + << Qt::Horizontal << false << -2 << "rotation" << 1.5 << false + << QPoint(80, 80) << QPoint(240, 360) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false + << QPoint(0, 0) << 1.0 << -60; +} + +void tst_QQuickWheelHandler::singleHandler() +{ + // handler properties + QFETCH(Qt::Orientation, orientation); + QFETCH(bool, invertible); + QFETCH(int, rotationScale); + QFETCH(QString, property); + QFETCH(qreal, targetScaleMultiplier); + QFETCH(bool, targetTransformAroundCursor); + // event + QFETCH(QPoint, eventPos); + QFETCH(QPoint, eventAngleDelta); + QFETCH(QPoint, eventPixelDelta); + QFETCH(Qt::KeyboardModifiers, eventModifiers); + QFETCH(bool, eventPhases); + QFETCH(bool, eventInverted); + // result + QFETCH(QPoint, expectedPosition); + QFETCH(qreal, expectedScale); + QFETCH(int, expectedRotation); + + QQuickView window; + QByteArray errorMessage; + QVERIFY2(QQuickTest::initView(window, testFileUrl("rectWheel.qml"), true, &errorMessage), errorMessage.constData()); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + QQuickItem *rect = window.rootObject(); + QVERIFY(rect != nullptr); + QQuickWheelHandler *handler = rect->findChild<QQuickWheelHandler*>(); + QVERIFY(handler != nullptr); + handler->setOrientation(orientation); + handler->setInvertible(invertible); + handler->setRotationScale(rotationScale); + handler->setProperty(property); + handler->setTargetScaleMultiplier(targetScaleMultiplier); + handler->setTargetTransformAroundCursor(targetTransformAroundCursor); + QSignalSpy activeChangedSpy(handler, SIGNAL(activeChanged())); + + if (eventPhases) { + sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollBegin, eventInverted); + sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers, Qt::ScrollUpdate, eventInverted); + } else { + sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers, Qt::NoScrollPhase, eventInverted); + } + QCOMPARE(rect->position().toPoint(), expectedPosition); + QCOMPARE(activeChangedSpy.count(), 1); + QCOMPARE(handler->active(), true); + QCOMPARE(rect->scale(), expectedScale); + QCOMPARE(rect->rotation(), expectedRotation); + if (!eventPhases) { + QTRY_COMPARE(handler->active(), false); + QCOMPARE(activeChangedSpy.count(), 2); + } + + // restore by rotating backwards + if (eventPhases) { + sendWheelEvent(window, eventPos, eventAngleDelta * -1, eventPixelDelta * -1, eventModifiers, Qt::ScrollUpdate, eventInverted); + sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollEnd, eventInverted); + } else { + sendWheelEvent(window, eventPos, eventAngleDelta * -1, eventPixelDelta * -1, eventModifiers, Qt::NoScrollPhase, eventInverted); + } + QCOMPARE(activeChangedSpy.count(), eventPhases ? 2 : 3); + QCOMPARE(handler->active(), !eventPhases); + QCOMPARE(rect->position().toPoint(), QPoint(0, 0)); + QCOMPARE(rect->scale(), 1); + QCOMPARE(rect->rotation(), 0); +} + +void tst_QQuickWheelHandler::nestedHandler_data() +{ + // handler properties + QTest::addColumn<Qt::Orientation>("orientation"); + QTest::addColumn<bool>("invertible"); + QTest::addColumn<int>("rotationScale"); + QTest::addColumn<QString>("property"); + QTest::addColumn<qreal>("targetScaleMultiplier"); + QTest::addColumn<bool>("targetTransformAroundCursor"); + // event + QTest::addColumn<QPoint>("eventPos"); + QTest::addColumn<QPoint>("eventAngleDelta"); + QTest::addColumn<QPoint>("eventPixelDelta"); + QTest::addColumn<Qt::KeyboardModifiers>("eventModifiers"); + QTest::addColumn<bool>("eventPhases"); + QTest::addColumn<bool>("eventInverted"); + QTest::addColumn<int>("eventCount"); + // result: inner handler + QTest::addColumn<QPoint>("innerPosition"); + QTest::addColumn<qreal>("innerScale"); + QTest::addColumn<int>("innerRotation"); + // result: outer handler + QTest::addColumn<QPoint>("outerPosition"); + QTest::addColumn<qreal>("outerScale"); + QTest::addColumn<int>("outerRotation"); + + // move the item + QTest::newRow("vertical wheel angle delta to adjust x") + << Qt::Vertical << false << 1 << "x" << 1.5 << true + << QPoint(160, 120) << QPoint(120, 120) << QPoint() << Qt::KeyboardModifiers(Qt::NoModifier) << false << false << 10 + << QPoint(175,60) << 1.0 << 0 + << QPoint(75, 0) << 1.0 << 0; + QTest::newRow("horizontal wheel pixel delta to adjust y") + << Qt::Horizontal << false << 1 << "y" << 1.5 << false + << QPoint(160, 120) << QPoint(120, 120) << QPoint(50, 50) << Qt::KeyboardModifiers(Qt::NoModifier) << true << false << 4 + << QPoint(100, 160) << 1.0 << 0 + << QPoint(0, 100) << 1.0 << 0; +} + +void tst_QQuickWheelHandler::nestedHandler() +{ + // handler properties + QFETCH(Qt::Orientation, orientation); + QFETCH(bool, invertible); + QFETCH(int, rotationScale); + QFETCH(QString, property); + QFETCH(qreal, targetScaleMultiplier); + QFETCH(bool, targetTransformAroundCursor); + // event + QFETCH(QPoint, eventPos); + QFETCH(QPoint, eventAngleDelta); + QFETCH(QPoint, eventPixelDelta); + QFETCH(Qt::KeyboardModifiers, eventModifiers); + QFETCH(bool, eventPhases); + QFETCH(bool, eventInverted); + QFETCH(int, eventCount); + // result: inner handler + QFETCH(QPoint, innerPosition); + QFETCH(qreal, innerScale); + QFETCH(int, innerRotation); + // result: outer handler + QFETCH(QPoint, outerPosition); + QFETCH(qreal, outerScale); + QFETCH(int, outerRotation); + + QQuickView window; + QByteArray errorMessage; + QVERIFY2(QQuickTest::initView(window, testFileUrl("nested.qml"), true, &errorMessage), errorMessage.constData()); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + QQuickItem *outerRect = window.rootObject(); + QVERIFY(outerRect != nullptr); + QQuickWheelHandler *outerHandler = outerRect->findChild<QQuickWheelHandler*>("outerWheelHandler"); + QVERIFY(outerHandler != nullptr); + QQuickWheelHandler *innerHandler = outerRect->findChild<QQuickWheelHandler*>("innerWheelHandler"); + QVERIFY(innerHandler != nullptr); + QQuickItem *innerRect = innerHandler->parentItem(); + QVERIFY(innerRect != nullptr); + innerHandler->setOrientation(orientation); + innerHandler->setInvertible(invertible); + innerHandler->setRotationScale(rotationScale); + innerHandler->setProperty(property); + innerHandler->setTargetScaleMultiplier(targetScaleMultiplier); + innerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor); + outerHandler->setOrientation(orientation); + outerHandler->setInvertible(invertible); + outerHandler->setRotationScale(rotationScale); + outerHandler->setProperty(property); + outerHandler->setTargetScaleMultiplier(targetScaleMultiplier); + outerHandler->setTargetTransformAroundCursor(targetTransformAroundCursor); + QSignalSpy innerActiveChangedSpy(innerHandler, SIGNAL(activeChanged())); + QSignalSpy outerActiveChangedSpy(outerHandler, SIGNAL(activeChanged())); + + if (eventPhases) + sendWheelEvent(window, eventPos, QPoint(), QPoint(), eventModifiers, Qt::ScrollBegin, eventInverted); + for (int i = 0; i < eventCount; ++i) + sendWheelEvent(window, eventPos, eventAngleDelta, eventPixelDelta, eventModifiers, + (eventPhases ? Qt::ScrollUpdate : Qt::NoScrollPhase), eventInverted); + QCOMPARE(innerRect->position().toPoint(), innerPosition); + + /* + If outer is activated, maybe inner should be deactivated? But the event + doesn't get delivered to inner anymore, so it doesn't find out that + it's no longer getting events. It will get deactivated after the + timeout, just as if the user stopped scrolling. + + This situation is similar to QTBUG-50199, but it's questionable whether + that was really so important. So far in Qt Quick, if you move the mouse + while wheel momentum continues, or if the item moves out from under the + mouse, a different item starts getting the events immediately. In + non-Qt applications on most OSes, that's quite normal. + */ + // QCOMPARE(innerActiveChangedSpy.count(), 2); + // QCOMPARE(innerHandler->active(), false); + QCOMPARE(innerRect->scale(), innerScale); + QCOMPARE(innerRect->rotation(), innerRotation); + QCOMPARE(outerRect->position().toPoint(), outerPosition); + QCOMPARE(outerActiveChangedSpy.count(), 1); + QCOMPARE(outerHandler->active(), true); + QCOMPARE(outerRect->scale(), outerScale); + QCOMPARE(outerRect->rotation(), outerRotation); + if (!eventPhases) { + QTRY_COMPARE(outerHandler->active(), false); + QCOMPARE(outerActiveChangedSpy.count(), 2); + } +} + +QTEST_MAIN(tst_QQuickWheelHandler) + +#include "tst_qquickwheelhandler.moc" diff --git a/tests/auto/quick/propertyrequirements/tst_propertyrequirements.cpp b/tests/auto/quick/propertyrequirements/tst_propertyrequirements.cpp index 34be4d98b4..5781a007b6 100644 --- a/tests/auto/quick/propertyrequirements/tst_propertyrequirements.cpp +++ b/tests/auto/quick/propertyrequirements/tst_propertyrequirements.cpp @@ -28,7 +28,6 @@ #include <qtest.h> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlengine.h> -#include <QtQml/private/qhashedstring_p.h> #include <QtQml/private/qqmlmetatype_p.h> #include <QtCore/QDebug> #include <QtCore/QHash> diff --git a/tests/auto/quick/qquickanchors/tst_qquickanchors.cpp b/tests/auto/quick/qquickanchors/tst_qquickanchors.cpp index 77fa1292c4..128a154492 100644 --- a/tests/auto/quick/qquickanchors/tst_qquickanchors.cpp +++ b/tests/auto/quick/qquickanchors/tst_qquickanchors.cpp @@ -364,7 +364,7 @@ void tst_qquickanchors::reset() const QMetaObject *meta = itemPrivate->anchors()->metaObject(); QMetaProperty p = meta->property(meta->indexOfProperty(side.toUtf8().constData())); - QVERIFY(p.write(itemPrivate->anchors(), qVariantFromValue(anchorLine))); + QVERIFY(p.write(itemPrivate->anchors(), QVariant::fromValue(anchorLine))); QCOMPARE(itemPrivate->anchors()->usedAnchors().testFlag(anchor), true); QVERIFY(p.reset(itemPrivate->anchors())); @@ -423,7 +423,7 @@ void tst_qquickanchors::nullItem() QMetaProperty p = meta->property(meta->indexOfProperty(side.toUtf8().constData())); QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML Item: Cannot anchor to a null item."); - QVERIFY(p.write(itemPrivate->anchors(), qVariantFromValue(anchor))); + QVERIFY(p.write(itemPrivate->anchors(), QVariant::fromValue(anchor))); delete item; } diff --git a/tests/auto/quick/qquickanimations/qquickanimations.pro b/tests/auto/quick/qquickanimations/qquickanimations.pro index 8bb1f47af5..cd0a0fbb15 100644 --- a/tests/auto/quick/qquickanimations/qquickanimations.pro +++ b/tests/auto/quick/qquickanimations/qquickanimations.pro @@ -8,7 +8,7 @@ macx:CONFIG -= app_bundle TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 OTHER_FILES += \ diff --git a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp index 961506372a..b75dc8bc6b 100644 --- a/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp +++ b/tests/auto/quick/qquickanimations/tst_qquickanimations.cpp @@ -30,7 +30,7 @@ #include <QtQml/qqmlcomponent.h> #include <QtQuick/qquickview.h> #include <QtQml/private/qqmltimer_p.h> -#include <QtQml/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQml/private/qanimationgroupjob_p.h> #include <QtQuick/private/qquickrectangle_p.h> #include <QtQuick/private/qquickitemanimation_p.h> diff --git a/tests/auto/quick/qquickboundaryrule/data/dragHandler.qml b/tests/auto/quick/qquickboundaryrule/data/dragHandler.qml new file mode 100644 index 0000000000..c66fd76ff1 --- /dev/null +++ b/tests/auto/quick/qquickboundaryrule/data/dragHandler.qml @@ -0,0 +1,23 @@ +import QtQuick 2.14 +import Qt.labs.animation 1.0 + +Rectangle { + id: root + width: 240; height: 120 + color: "green" + + DragHandler { + id: dragHandler + yAxis.minimum: -1000 + xAxis.minimum: -1000 + onActiveChanged: if (!active) xbr.returnToBounds(); + } + + BoundaryRule on x { + id: xbr + minimum: -50 + maximum: 100 + minimumOvershoot: 40 + maximumOvershoot: 40 + } +} diff --git a/tests/auto/quick/qquickboundaryrule/qquickboundaryrule.pro b/tests/auto/quick/qquickboundaryrule/qquickboundaryrule.pro new file mode 100644 index 0000000000..ef43f4526a --- /dev/null +++ b/tests/auto/quick/qquickboundaryrule/qquickboundaryrule.pro @@ -0,0 +1,12 @@ +CONFIG += testcase +TARGET = tst_qquickboundaryrule +macx:CONFIG -= app_bundle + +SOURCES += tst_qquickboundaryrule.cpp + +include (../../shared/util.pri) +include (../shared/util.pri) + +TESTDATA = data/* + +QT += core-private gui-private qml-private quick-private testlib diff --git a/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp b/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp new file mode 100644 index 0000000000..44f1c9a2f9 --- /dev/null +++ b/tests/auto/quick/qquickboundaryrule/tst_qquickboundaryrule.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QtTest/QtTest> +#include <qsignalspy.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuick/qquickview.h> +#include <QtQuick/private/qquickboundaryrule_p.h> +#include <QtQuick/private/qquickdraghandler_p.h> +#include "../../shared/util.h" +#include "../shared/viewtestutil.h" + +class tst_qquickboundaryrule : public QQmlDataTest +{ + Q_OBJECT +public: + tst_qquickboundaryrule() {} + +private slots: + void init() { qApp->processEvents(); } //work around animation timer bug (QTBUG-22865) + void dragHandler(); +}; + +void tst_qquickboundaryrule::dragHandler() +{ + QQuickView window; + QByteArray errorMessage; + QVERIFY2(QQuickTest::initView(window, testFileUrl("dragHandler.qml"), true, &errorMessage), errorMessage.constData()); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + QQuickItem *target = window.rootObject(); + QVERIFY(target); + QQuickDragHandler *dragHandler = target->findChild<QQuickDragHandler*>(); + QVERIFY(dragHandler); + QQuickBoundaryRule *boundaryRule = target->findChild<QQuickBoundaryRule*>(); + QVERIFY(boundaryRule); + QSignalSpy overshootChangedSpy(boundaryRule, SIGNAL(currentOvershootChanged())); + + QPoint p1(10, 10); + QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, p1); + // unrestricted drag + p1 += QPoint(100, 0); + QTest::mouseMove(&window, p1); + QTRY_VERIFY(dragHandler->active()); + QCOMPARE(target->position().x(), 100); + QCOMPARE(boundaryRule->currentOvershoot(), 0); + QCOMPARE(boundaryRule->peakOvershoot(), 0); + QCOMPARE(overshootChangedSpy.count(), 0); + // restricted drag: halfway into overshoot + p1 += QPoint(20, 0); + QTest::mouseMove(&window, p1); + QCOMPARE(target->position().x(), 117.5); + QCOMPARE(boundaryRule->currentOvershoot(), 20); + QCOMPARE(boundaryRule->peakOvershoot(), 20); + QCOMPARE(overshootChangedSpy.count(), 1); + // restricted drag: maximum overshoot + p1 += QPoint(80, 0); + QTest::mouseMove(&window, p1); + QCOMPARE(target->position().x(), 140); + QCOMPARE(boundaryRule->currentOvershoot(), 100); + QCOMPARE(boundaryRule->peakOvershoot(), 100); + QCOMPARE(overshootChangedSpy.count(), 2); + // release and let it return to bounds + QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, p1); + QTRY_COMPARE(dragHandler->active(), false); + QTRY_COMPARE(overshootChangedSpy.count(), 3); + QCOMPARE(boundaryRule->currentOvershoot(), 0); + QCOMPARE(boundaryRule->peakOvershoot(), 0); + QCOMPARE(target->position().x(), 100); +} + +QTEST_MAIN(tst_qquickboundaryrule) + +#include "tst_qquickboundaryrule.moc" diff --git a/tests/auto/quick/qquickgridview/qquickgridview.pro b/tests/auto/quick/qquickgridview/qquickgridview.pro index 5051f8bc62..0390637058 100644 --- a/tests/auto/quick/qquickgridview/qquickgridview.pro +++ b/tests/auto/quick/qquickgridview/qquickgridview.pro @@ -10,5 +10,5 @@ include (../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib qmltest +QT += core-private gui-private qml-private quick-private testlib qmltest qmlmodels-private diff --git a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp index 448096720c..b65b8770b1 100644 --- a/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp +++ b/tests/auto/quick/qquickgridview/tst_qquickgridview.cpp @@ -40,7 +40,7 @@ #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> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include "../../shared/util.h" #include "../shared/viewtestutil.h" #include "../shared/visualtestutil.h" diff --git a/tests/auto/quick/qquicklistview/qquicklistview.pro b/tests/auto/quick/qquicklistview/qquicklistview.pro index fd96c269a2..b08fca2b1d 100644 --- a/tests/auto/quick/qquicklistview/qquicklistview.pro +++ b/tests/auto/quick/qquicklistview/qquicklistview.pro @@ -18,5 +18,5 @@ include (../shared/util.pri) TESTDATA = data/* DISTFILES += data/* -QT += core-private gui-private qml-private quick-private testlib qmltest +QT += core-private gui-private qml-private quick-private testlib qmltest qmlmodels-private diff --git a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp index 2ea8a477a8..894a5ee16e 100644 --- a/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp +++ b/tests/auto/quick/qquicklistview/tst_qquicklistview.cpp @@ -40,9 +40,9 @@ #include <QtQuick/private/qquicklistview_p.h> #include <QtQuick/private/qquickmousearea_p.h> #include <QtQuick/private/qquicktext_p.h> -#include <QtQml/private/qqmlobjectmodel_p.h> -#include <QtQml/private/qqmllistmodel_p.h> -#include <QtQml/private/qqmldelegatemodel_p.h> +#include <QtQmlModels/private/qqmlobjectmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmldelegatemodel_p.h> #include "../../shared/util.h" #include "../shared/viewtestutil.h" #include "../shared/visualtestutil.h" diff --git a/tests/auto/quick/qquickpathview/qquickpathview.pro b/tests/auto/quick/qquickpathview/qquickpathview.pro index f21fb64fa4..5eb24b89bd 100644 --- a/tests/auto/quick/qquickpathview/qquickpathview.pro +++ b/tests/auto/quick/qquickpathview/qquickpathview.pro @@ -9,5 +9,5 @@ include (../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib qmltest +QT += core-private gui-private qml-private quick-private testlib qmltest qmlmodels-private qtHaveModule(widgets): QT += widgets diff --git a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp index 1a5ce39318..a1b2b64ae2 100644 --- a/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp +++ b/tests/auto/quick/qquickpathview/tst_qquickpathview.cpp @@ -39,7 +39,7 @@ #include <QtQuick/private/qquicktext_p.h> #include <QtQuick/private/qquickrectangle_p.h> #include <QtQuickTest/QtQuickTest> -#include <QtQml/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include <QtQml/private/qqmlvaluetype_p.h> #include <QtGui/qstandarditemmodel.h> #include <QStringListModel> diff --git a/tests/auto/quick/qquickrepeater/qquickrepeater.pro b/tests/auto/quick/qquickrepeater/qquickrepeater.pro index 5554342943..aed5702266 100644 --- a/tests/auto/quick/qquickrepeater/qquickrepeater.pro +++ b/tests/auto/quick/qquickrepeater/qquickrepeater.pro @@ -9,4 +9,4 @@ include (../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private diff --git a/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp b/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp index e4b427f6ec..65e7d29595 100644 --- a/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp +++ b/tests/auto/quick/qquickrepeater/tst_qquickrepeater.cpp @@ -35,8 +35,8 @@ #include <QtQml/qqmlincubator.h> #include <private/qquickrepeater_p.h> #include <QtQuick/private/qquicktext_p.h> -#include <QtQml/private/qqmllistmodel_p.h> -#include <QtQml/private/qqmlobjectmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmlobjectmodel_p.h> #include <QtGui/qstandarditemmodel.h> #include "../../shared/util.h" @@ -899,15 +899,15 @@ void tst_QQuickRepeater::destroyCount() QQuickRepeater *repeater = findItem<QQuickRepeater>(rootObject, "repeater"); QVERIFY(repeater); - repeater->setProperty("model", qVariantFromValue<int>(3)); + repeater->setProperty("model", QVariant::fromValue<int>(3)); QCOMPARE(repeater->property("componentCount").toInt(), 3); - repeater->setProperty("model", qVariantFromValue<int>(0)); + repeater->setProperty("model", QVariant::fromValue<int>(0)); QCOMPARE(repeater->property("componentCount").toInt(), 0); - repeater->setProperty("model", qVariantFromValue<int>(4)); + repeater->setProperty("model", QVariant::fromValue<int>(4)); QCOMPARE(repeater->property("componentCount").toInt(), 4); QStringListModel model; - repeater->setProperty("model", qVariantFromValue<QStringListModel *>(&model)); + repeater->setProperty("model", QVariant::fromValue<QStringListModel *>(&model)); QCOMPARE(repeater->property("componentCount").toInt(), 0); QStringList list; list << "1" << "2" << "3" << "4"; @@ -915,7 +915,7 @@ void tst_QQuickRepeater::destroyCount() QCOMPARE(repeater->property("componentCount").toInt(), 4); model.insertRows(2,1); QModelIndex index = model.index(2); - model.setData(index, qVariantFromValue<QString>(QStringLiteral("foobar"))); + model.setData(index, QVariant::fromValue<QString>(QStringLiteral("foobar"))); QCOMPARE(repeater->property("componentCount").toInt(), 5); model.removeRows(2,1); diff --git a/tests/auto/quick/qquicktableview/data/syncviewsimple.qml b/tests/auto/quick/qquicktableview/data/syncviewsimple.qml new file mode 100644 index 0000000000..012cceaa9d --- /dev/null +++ b/tests/auto/quick/qquicktableview/data/syncviewsimple.qml @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.14 +import QtQuick.Window 2.3 + +Item { + width: 640 + height: 450 + + property alias tableView: mainTableView + property alias tableViewH: tableViewH + property alias tableViewV: tableViewV + property alias tableViewHV: tableViewHV + + Column { + spacing: 10 + TableView { + id: tableViewH + objectName: "Hor" + width: 600 + height: 100 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + syncView: mainTableView + syncDirection: Qt.Horizontal + } + + TableView { + id: tableViewV + objectName: "Ver" + width: 600 + height: 100 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + syncView: mainTableView + syncDirection: Qt.Vertical + } + + TableView { + id: tableViewHV + objectName: "HorVer" + width: 600 + height: 100 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + syncView: mainTableView + } + + TableView { + id: mainTableView + objectName: "root" + width: 600 + height: 100 + anchors.margins: 1 + clip: true + delegate: tableViewDelegate + columnSpacing: 1 + rowSpacing: 1 + + columnWidthProvider: function(c) { return 50 + c } + rowHeightProvider: function(r) { return 25 + r } + } + } + + Component { + id: tableViewDelegate + Rectangle { + objectName: "tableViewDelegate" + color: "lightgray" + border.width: 1 + implicitWidth: 30 + implicitHeight: 60 + + Text { + anchors.centerIn: parent + font.pixelSize: 10 + text: parent.TableView.view.objectName + "\n" + column + ", " + row + } + } + } + +} diff --git a/tests/auto/quick/qquicktableview/qquicktableview.pro b/tests/auto/quick/qquicktableview/qquicktableview.pro index cf831ed5b5..da0c0b01d0 100644 --- a/tests/auto/quick/qquicktableview/qquicktableview.pro +++ b/tests/auto/quick/qquicktableview/qquicktableview.pro @@ -11,5 +11,5 @@ include (../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private diff --git a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp index 60d48bb59f..d03f08ec6a 100644 --- a/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp +++ b/tests/auto/quick/qquicktableview/tst_qquicktableview.cpp @@ -38,8 +38,8 @@ #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlexpression.h> #include <QtQml/qqmlincubator.h> -#include <QtQml/private/qqmlobjectmodel_p.h> -#include <QtQml/private/qqmllistmodel_p.h> +#include <QtQmlModels/private/qqmlobjectmodel_p.h> +#include <QtQmlModels/private/qqmllistmodel_p.h> #include "testmodel.h" @@ -57,17 +57,17 @@ static const char *kModelDataBindingProp = "modelDataBinding"; Q_DECLARE_METATYPE(QMarginsF); -#define DECLARE_TABLEVIEW_VARIABLES \ - auto tableView = view->rootObject()->property(kTableViewPropName).value<QQuickTableView *>(); \ - QVERIFY(tableView); \ - auto tableViewPrivate = QQuickTableViewPrivate::get(tableView); \ - Q_UNUSED(tableViewPrivate) +#define GET_QML_TABLEVIEW(PROPNAME) \ + auto PROPNAME = view->rootObject()->property(#PROPNAME).value<QQuickTableView *>(); \ + QVERIFY(PROPNAME); \ + auto PROPNAME ## Private = QQuickTableViewPrivate::get(PROPNAME); \ + Q_UNUSED(PROPNAME ## Private) #define LOAD_TABLEVIEW(fileName) \ view->setSource(testFileUrl(fileName)); \ view->show(); \ QVERIFY(QTest::qWaitForWindowActive(view)); \ - DECLARE_TABLEVIEW_VARIABLES + GET_QML_TABLEVIEW(tableView) #define LOAD_TABLEVIEW_ASYNC(fileName) \ view->setSource(testFileUrl("asyncloader.qml")); \ @@ -77,11 +77,12 @@ Q_DECLARE_METATYPE(QMarginsF); loader->setSource(QUrl::fromLocalFile("data/" fileName)); \ QTRY_VERIFY(loader->item()); \ QCOMPARE(loader->status(), QQuickLoader::Status::Ready); \ - DECLARE_TABLEVIEW_VARIABLES + GET_QML_TABLEVIEW(tableView) -#define WAIT_UNTIL_POLISHED \ - QVERIFY(QQuickTest::qIsPolishScheduled(tableView)); \ - QVERIFY(QQuickTest::qWaitForItemPolished(tableView)) +#define WAIT_UNTIL_POLISHED_ARG(item) \ + QVERIFY(QQuickTest::qIsPolishScheduled(item)); \ + QVERIFY(QQuickTest::qWaitForItemPolished(item)) +#define WAIT_UNTIL_POLISHED WAIT_UNTIL_POLISHED_ARG(tableView) class tst_QQuickTableView : public QQmlDataTest { @@ -122,6 +123,9 @@ private slots: void checkContentWidthAndHeight(); void checkPageFlicking(); void checkExplicitContentWidthAndHeight(); + void checkExtents_origin(); + void checkExtents_endExtent(); + void checkExtents_moveTableToEdge(); void checkContentXY(); void noDelegate(); void changeDelegateDuringUpdate(); @@ -162,6 +166,13 @@ private slots: void hideRowsAndColumns_data(); void hideRowsAndColumns(); void checkThatRevisionedPropertiesCannotBeUsedInOldImports(); + void checkSyncView_rootView_data(); + void checkSyncView_rootView(); + void checkSyncView_childViews_data(); + void checkSyncView_childViews(); + void checkSyncView_differentSizedModels(); + void checkSyncView_connect_late_data(); + void checkSyncView_connect_late(); }; tst_QQuickTableView::tst_QQuickTableView() @@ -601,8 +612,7 @@ void tst_QQuickTableView::checkContentWidthAndHeight() // Since we move the viewport more than a page, tableview // will jump to the new position and do a rebuild. - QVERIFY(tableViewPrivate->polishScheduled); - QVERIFY(tableViewPrivate->rebuildScheduled); + QVERIFY(tableViewPrivate->scheduledRebuildOptions); WAIT_UNTIL_POLISHED; // Check that the average cell size is now matching the @@ -654,7 +664,7 @@ void tst_QQuickTableView::checkContentWidthAndHeight() // Since we move the viewport more than a page, tableview // will jump to the new position and do a rebuild. QVERIFY(tableViewPrivate->polishScheduled); - QVERIFY(tableViewPrivate->rebuildScheduled); + QVERIFY(tableViewPrivate->scheduledRebuildOptions); WAIT_UNTIL_POLISHED; // We should now have the same content width/height as when we started @@ -689,7 +699,6 @@ void tst_QQuickTableView::checkPageFlicking() QCOMPARE(tableViewPrivate->averageEdgeSize.width(), cellWidth); QCOMPARE(tableViewPrivate->averageEdgeSize.height(), cellHeight); - QVERIFY(!tableViewPrivate->rebuildScheduled); QCOMPARE(tableViewPrivate->scheduledRebuildOptions, QQuickTableViewPrivate::RebuildOption::None); // Flick 5000 columns to the right, and check that this triggers a @@ -699,7 +708,6 @@ void tst_QQuickTableView::checkPageFlicking() const qreal flickToColumnInPixels = ((cellWidth + columnSpacing) * flickToColumn) - columnSpacing; tableView->setContentX(flickToColumnInPixels); - QVERIFY(tableViewPrivate->rebuildScheduled); QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly); QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn); QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow)); @@ -721,7 +729,6 @@ void tst_QQuickTableView::checkPageFlicking() const qreal flickToRowInPixels = ((cellHeight + rowSpacing) * flickToRow) - rowSpacing; tableView->setContentY(flickToRowInPixels); - QVERIFY(tableViewPrivate->rebuildScheduled); QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::ViewportOnly); QVERIFY(!(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftColumn)); QVERIFY(tableViewPrivate->scheduledRebuildOptions & QQuickTableViewPrivate::RebuildOption::CalculateNewTopLeftRow); @@ -756,6 +763,164 @@ void tst_QQuickTableView::checkExplicitContentWidthAndHeight() QCOMPARE(tableView->contentHeight(), 1000); } +void tst_QQuickTableView::checkExtents_origin() +{ + // Check that if the beginning of the content view doesn't match the + // actual size of the table, origin will be adjusted to make it fit. + LOAD_TABLEVIEW("contentwidthheight.qml"); + + const int rows = 10; + const int columns = rows; + const qreal columnWidth = 100; + const qreal rowHeight = 100; + const qreal actualTableSize = columns * columnWidth; + + // Set a content size that is far too large + // compared to the size of the table. + tableView->setContentWidth(actualTableSize * 2); + tableView->setContentHeight(actualTableSize * 2); + tableView->setRowSpacing(0); + tableView->setColumnSpacing(0); + tableView->setLeftMargin(0); + tableView->setRightMargin(0); + tableView->setTopMargin(0); + tableView->setBottomMargin(0); + + auto model = TestModelAsVariant(rows, columns); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Flick slowly to column 5 (to avoid rebuilds). Flick two columns at a + // time to ensure that we create a gap before TableView gets a chance to + // adjust endExtent first. This gap on the right side will make TableView + // move the table to move to the edge. Because of this, the table will not + // be aligned at the start of the content view when we next flick back again. + // And this will cause origin to move. + for (int x = 0; x <= 6; x += 2) { + tableView->setContentX(x * columnWidth); + tableView->setContentY(x * rowHeight); + } + + // Check that the table has now been moved one column to the right + // (One column because that's how far outside the table we ended up flicking above). + QCOMPARE(tableViewPrivate->loadedTableOuterRect.right(), actualTableSize + columnWidth); + + // Flick back one column at a time so that TableView detects that the first + // column is not at the origin before the "table move" logic kicks in. This + // will make TableView adjust the origin. + for (int x = 6; x >= 0; x -= 1) { + tableView->setContentX(x * columnWidth); + tableView->setContentY(x * rowHeight); + } + + // The origin will be moved with the same offset that the table was + // moved on the right side earlier, which is one column length. + QCOMPARE(tableViewPrivate->origin.x(), columnWidth); + QCOMPARE(tableViewPrivate->origin.y(), rowHeight); +} + +void tst_QQuickTableView::checkExtents_endExtent() +{ + // Check that if we the content view size doesn't match the actual size + // of the table, endExtent will be adjusted to make it fit (so that + // e.g the the flicking will bounce to a stop at the edge of the table). + LOAD_TABLEVIEW("contentwidthheight.qml"); + + const int rows = 10; + const int columns = rows; + const qreal columnWidth = 100; + const qreal rowHeight = 100; + const qreal actualTableSize = columns * columnWidth; + + // Set a content size that is far too large + // compared to the size of the table. + tableView->setContentWidth(actualTableSize * 2); + tableView->setContentHeight(actualTableSize * 2); + tableView->setRowSpacing(0); + tableView->setColumnSpacing(0); + tableView->setLeftMargin(0); + tableView->setRightMargin(0); + tableView->setTopMargin(0); + tableView->setBottomMargin(0); + + auto model = TestModelAsVariant(rows, columns); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Flick slowly to column 5 (to avoid rebuilds). This will flick the table to + // the last column in the model. But since there still is a lot space left in + // the content view, endExtent will be set accordingly to compensate. + for (int x = 1; x <= 5; x++) + tableView->setContentX(x * columnWidth); + QCOMPARE(tableViewPrivate->rightColumn(), columns - 1); + qreal expectedEndExtentWidth = actualTableSize - tableView->contentWidth(); + QCOMPARE(tableViewPrivate->endExtent.width(), expectedEndExtentWidth); + + for (int y = 1; y <= 5; y++) + tableView->setContentY(y * rowHeight); + QCOMPARE(tableViewPrivate->bottomRow(), rows - 1); + qreal expectedEndExtentHeight = actualTableSize - tableView->contentHeight(); + QCOMPARE(tableViewPrivate->endExtent.height(), expectedEndExtentHeight); +} + +void tst_QQuickTableView::checkExtents_moveTableToEdge() +{ + // Check that if we the content view size doesn't match the actual + // size of the table, and we fast-flick the viewport to outside + // the table, we end up moving the table back into the viewport to + // avoid any visual glitches. + LOAD_TABLEVIEW("contentwidthheight.qml"); + + const int rows = 10; + const int columns = rows; + const qreal columnWidth = 100; + const qreal rowHeight = 100; + const qreal actualTableSize = columns * columnWidth; + + // Set a content size that is far to large + // compared to the size of the table. + tableView->setContentWidth(actualTableSize * 2); + tableView->setContentHeight(actualTableSize * 2); + tableView->setRowSpacing(0); + tableView->setColumnSpacing(0); + tableView->setLeftMargin(0); + tableView->setRightMargin(0); + tableView->setTopMargin(0); + tableView->setBottomMargin(0); + + auto model = TestModelAsVariant(rows, columns); + tableView->setModel(model); + + WAIT_UNTIL_POLISHED; + + // Flick slowly to column 5 (to avoid rebuilds). Flick two columns at a + // time to ensure that we create a gap before TableView gets a chance to + // adjust endExtent first. This gap on the right side will make TableView + // move the table to the edge (in addition to adjusting the extents, but that + // will happen in a subsequent polish, and is not for this test verify). + for (int x = 0; x <= 6; x += 2) + tableView->setContentX(x * columnWidth); + QCOMPARE(tableViewPrivate->rightColumn(), columns - 1); + QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect); + + for (int y = 0; y <= 6; y += 2) + tableView->setContentY(y * rowHeight); + QCOMPARE(tableViewPrivate->bottomRow(), rows - 1); + QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect); + + for (int x = 6; x >= 0; x -= 2) + tableView->setContentX(x * columnWidth); + QCOMPARE(tableViewPrivate->leftColumn(), 0); + QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect); + + for (int y = 6; y >= 0; y -= 2) + tableView->setContentY(y * rowHeight); + QCOMPARE(tableViewPrivate->topRow(), 0); + QCOMPARE(tableViewPrivate->loadedTableOuterRect, tableViewPrivate->viewportRect); +} + void tst_QQuickTableView::checkContentXY() { // Check that you can bind contentX and contentY to @@ -1942,7 +2107,7 @@ void tst_QQuickTableView::checkChangingModelFromDelegate() // And since the QML code tried to add another row as well, we // expect rebuildScheduled to be true, and a polish event to be pending. - QCOMPARE(tableViewPrivate->rebuildScheduled, true); + QVERIFY(tableViewPrivate->scheduledRebuildOptions); QCOMPARE(tableViewPrivate->polishScheduled, true); WAIT_UNTIL_POLISHED; @@ -2025,7 +2190,7 @@ void tst_QQuickTableView::checkTableviewInsideAsyncLoader() QCOMPARE(loader->status(), QQuickLoader::Ready); // Check that TableView has finished building - QCOMPARE(tableViewPrivate->rebuildScheduled, false); + QVERIFY(!tableViewPrivate->scheduledRebuildOptions); QCOMPARE(tableViewPrivate->rebuildState, QQuickTableViewPrivate::RebuildState::Done); // Check that all expected delegate items have been loaded @@ -2152,6 +2317,337 @@ void tst_QQuickTableView::checkThatRevisionedPropertiesCannotBeUsedInOldImports( QCOMPARE(resolvedColumn, 42); } +void tst_QQuickTableView::checkSyncView_rootView_data() +{ + QTest::addColumn<qreal>("flickToPos"); + + QTest::newRow("pos:110") << 110.; + QTest::newRow("pos:2010") << 2010.; +} + +void tst_QQuickTableView::checkSyncView_rootView() +{ + // Check that if you flick on the root tableview (the view that has + // no other view as syncView), all the other tableviews will sync + // their content view position according to their syncDirection flag. + QFETCH(qreal, flickToPos); + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV}; + + auto model = TestModelAsVariant(100, 100); + + tableView->setModel(model); + for (auto view : views) + view->setModel(model); + + tableView->setContentX(flickToPos); + tableView->setContentY(flickToPos); + + WAIT_UNTIL_POLISHED; + + // Check that geometry properties are mirrored + QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing()); + QCOMPARE(tableViewH->rowSpacing(), 0); + QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth()); + QCOMPARE(tableViewV->columnSpacing(), 0); + QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing()); + QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight()); + + // Check that viewport is in sync after the flick + QCOMPARE(tableView->contentX(), flickToPos); + QCOMPARE(tableView->contentY(), flickToPos); + QCOMPARE(tableViewH->contentX(), tableView->contentX()); + QCOMPARE(tableViewH->contentY(), 0); + QCOMPARE(tableViewV->contentX(), 0); + QCOMPARE(tableViewV->contentY(), tableView->contentY()); + QCOMPARE(tableViewHV->contentX(), tableView->contentX()); + QCOMPARE(tableViewHV->contentY(), tableView->contentY()); + + // Check that topLeft cell is in sync after the flick + QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn()); + QCOMPARE(tableViewHPrivate->topRow(), 0); + QCOMPARE(tableViewVPrivate->leftColumn(), 0); + QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow()); + QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow()); + + // Check that the geometry of the tables are in sync after the flick + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0); + + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0); + + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect); +} + +void tst_QQuickTableView::checkSyncView_childViews_data() +{ + QTest::addColumn<int>("viewIndexToFlick"); + QTest::addColumn<qreal>("flickToPos"); + + QTest::newRow("tableViewH, pos:100") << 0 << 100.; + QTest::newRow("tableViewV, pos:100") << 1 << 100.; + QTest::newRow("tableViewHV, pos:100") << 2 << 100.; + QTest::newRow("tableViewH, pos:2000") << 0 << 2000.; + QTest::newRow("tableViewV, pos:2000") << 1 << 2000.; + QTest::newRow("tableViewHV, pos:2000") << 2 << 2000.; +} + +void tst_QQuickTableView::checkSyncView_childViews() +{ + // Check that if you flick on a tableview that has a syncView, the + // syncView will move to the new position as well, which will also + // recursivly move all other connected child views of the syncView. + QFETCH(int, viewIndexToFlick); + QFETCH(qreal, flickToPos); + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV}; + QQuickTableView *viewToFlick = views[viewIndexToFlick]; + QQuickTableViewPrivate *viewToFlickPrivate = QQuickTableViewPrivate::get(viewToFlick); + + auto model = TestModelAsVariant(100, 100); + + tableView->setModel(model); + for (auto view : views) + view->setModel(model); + + viewToFlick->setContentX(flickToPos); + viewToFlick->setContentY(flickToPos); + + WAIT_UNTIL_POLISHED; + + // The view the user flicks on can always be flicked in both directions + // (unless is has a flickingDirection set, which is not the case here). + QCOMPARE(viewToFlick->contentX(), flickToPos); + QCOMPARE(viewToFlick->contentY(), flickToPos); + + // The root view (tableView) will move in sync according + // to the syncDirection of the view being flicked. + if (viewToFlick->syncDirection() & Qt::Horizontal) { + QCOMPARE(tableView->contentX(), flickToPos); + QCOMPARE(tableViewPrivate->leftColumn(), viewToFlickPrivate->leftColumn()); + QCOMPARE(tableViewPrivate->rightColumn(), viewToFlickPrivate->rightColumn()); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), viewToFlickPrivate->loadedTableOuterRect.left()); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.right(), viewToFlickPrivate->loadedTableOuterRect.right()); + } else { + QCOMPARE(tableView->contentX(), 0); + QCOMPARE(tableViewPrivate->leftColumn(), 0); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.left(), 0); + } + + if (viewToFlick->syncDirection() & Qt::Vertical) { + QCOMPARE(tableView->contentY(), flickToPos); + QCOMPARE(tableViewPrivate->topRow(), viewToFlickPrivate->topRow()); + QCOMPARE(tableViewPrivate->bottomRow(), viewToFlickPrivate->bottomRow()); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), viewToFlickPrivate->loadedTableOuterRect.top()); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.bottom(), viewToFlickPrivate->loadedTableOuterRect.bottom()); + } else { + QCOMPARE(tableView->contentY(), 0); + QCOMPARE(tableViewPrivate->topRow(), 0); + QCOMPARE(tableViewPrivate->loadedTableOuterRect.top(), 0); + } + + // The other views should continue to stay in sync with + // the root view, unless it was the view being flicked. + if (viewToFlick != tableViewH) { + QCOMPARE(tableViewH->contentX(), tableView->contentX()); + QCOMPARE(tableViewH->contentY(), 0); + QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right()); + QCOMPARE(tableViewHPrivate->topRow(), 0); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0); + } + + if (viewToFlick != tableViewV) { + QCOMPARE(tableViewV->contentX(), 0); + QCOMPARE(tableViewV->contentY(), tableView->contentY()); + QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow()); + QCOMPARE(tableViewVPrivate->bottomRow(), tableViewPrivate->bottomRow()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom()); + QCOMPARE(tableViewVPrivate->leftColumn(), 0); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0); + } + + if (viewToFlick != tableViewHV) { + QCOMPARE(tableViewHV->contentX(), tableView->contentX()); + QCOMPARE(tableViewHV->contentY(), tableView->contentY()); + QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHVPrivate->rightColumn(), tableViewPrivate->rightColumn()); + QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow()); + QCOMPARE(tableViewHVPrivate->bottomRow(), tableViewPrivate->bottomRow()); + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect); + } +} + +void tst_QQuickTableView::checkSyncView_differentSizedModels() +{ + // Check that you can have two tables in a syncView relation, where + // the sync "child" has fewer rows/columns than the syncView. In that + // case, it will be possible to flick the syncView further out than + // the child have rows/columns to follow. This causes some extra + // challenges for TableView to ensure that they are still kept in + // sync once you later flick the syncView back to a point where both + // tables ends up visible. This test will check this sitiation. + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + + auto tableViewModel = TestModelAsVariant(100, 100); + auto tableViewHModel = TestModelAsVariant(100, 50); + auto tableViewVModel = TestModelAsVariant(50, 100); + auto tableViewHVModel = TestModelAsVariant(5, 5); + + tableView->setModel(tableViewModel); + tableViewH->setModel(tableViewHModel); + tableViewV->setModel(tableViewVModel); + tableViewHV->setModel(tableViewHVModel); + + WAIT_UNTIL_POLISHED; + + // Flick far out, beyond the smaller tables, which will + // also force a rebuild (and as such, cause layout properties + // like average cell width to be temporarily out of sync). + tableView->setContentX(5000); + QVERIFY(tableViewPrivate->scheduledRebuildOptions); + + WAIT_UNTIL_POLISHED; + + // Check that the smaller tables are now flicked out of view + qreal leftEdge = tableViewPrivate->loadedTableOuterRect.left(); + QVERIFY(tableViewHPrivate->loadedTableOuterRect.right() < leftEdge); + QVERIFY(tableViewHVPrivate->loadedTableOuterRect.right() < leftEdge); + + // Flick slowly back so that we don't trigger a rebuild (since + // we want to check that we stay in sync also when not rebuilding). + while (tableView->contentX() > 200) { + tableView->setContentX(tableView->contentX() - 200); + QVERIFY(!tableViewPrivate->rebuildOptions); + QVERIFY(!tableViewPrivate->polishScheduled); + } + + leftEdge = tableViewPrivate->loadedTableOuterRect.left(); + const int leftColumn = tableViewPrivate->leftColumn(); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), leftEdge); + QCOMPARE(tableViewHPrivate->leftColumn(), leftColumn); + + // Because the tableView was fast flicked and then slowly flicked back, the + // left column is now 49, which is actually far too high, since we're almost + // at the beginning of the content view. But this "miscalculation" is expected + // when the column widths are increasing for each column, like they do in this + // test. In that case, the algorithm that predicts where each column should end + // up gets slightly confused. Anyway, check that tableViewHV, that has only + // 5 columns, is not showing any columns, since it should always stay in sync with + // syncView regardless of the content view position. + QVERIFY(tableViewHVPrivate->loadedColumns.isEmpty()); +} + +void tst_QQuickTableView::checkSyncView_connect_late_data() +{ + QTest::addColumn<qreal>("flickToPos"); + + QTest::newRow("pos:110") << 110.; + QTest::newRow("pos:2010") << 2010.; +} + +void tst_QQuickTableView::checkSyncView_connect_late() +{ + // Check that if you assign a syncView to a TableView late, and + // after the views have been flicked around, they will still end up in sync. + QFETCH(qreal, flickToPos); + LOAD_TABLEVIEW("syncviewsimple.qml"); + GET_QML_TABLEVIEW(tableViewH); + GET_QML_TABLEVIEW(tableViewV); + GET_QML_TABLEVIEW(tableViewHV); + QQuickTableView *views[] = {tableViewH, tableViewV, tableViewHV}; + + auto model = TestModelAsVariant(100, 100); + + tableView->setModel(model); + + // Start with no syncView connections + for (auto view : views) { + view->setSyncView(nullptr); + view->setModel(model); + } + + WAIT_UNTIL_POLISHED; + + tableView->setContentX(flickToPos); + tableView->setContentY(flickToPos); + + WAIT_UNTIL_POLISHED; + + // Check that viewport is not in sync after the flick + QCOMPARE(tableView->contentX(), flickToPos); + QCOMPARE(tableView->contentY(), flickToPos); + QCOMPARE(tableViewH->contentX(), 0); + QCOMPARE(tableViewH->contentY(), 0); + QCOMPARE(tableViewV->contentX(), 0); + QCOMPARE(tableViewV->contentY(), 0); + QCOMPARE(tableViewHV->contentX(), 0); + QCOMPARE(tableViewHV->contentY(), 0); + + // Assign the syncView late. This should make + // all the views end up in sync. + for (auto view : views) { + view->setSyncView(tableView); + WAIT_UNTIL_POLISHED_ARG(view); + } + + // Check that geometry properties are mirrored + QCOMPARE(tableViewH->columnSpacing(), tableView->columnSpacing()); + QCOMPARE(tableViewH->rowSpacing(), 0); + QCOMPARE(tableViewH->contentWidth(), tableView->contentWidth()); + QCOMPARE(tableViewV->columnSpacing(), 0); + QCOMPARE(tableViewV->rowSpacing(), tableView->rowSpacing()); + QCOMPARE(tableViewV->contentHeight(), tableView->contentHeight()); + + // Check that viewport is in sync after the flick + QCOMPARE(tableView->contentX(), flickToPos); + QCOMPARE(tableView->contentY(), flickToPos); + QCOMPARE(tableViewH->contentX(), tableView->contentX()); + QCOMPARE(tableViewH->contentY(), 0); + QCOMPARE(tableViewV->contentX(), 0); + QCOMPARE(tableViewV->contentY(), tableView->contentY()); + QCOMPARE(tableViewHV->contentX(), tableView->contentX()); + QCOMPARE(tableViewHV->contentY(), tableView->contentY()); + + // Check that topLeft cell is in sync after the flick + QCOMPARE(tableViewHPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHPrivate->rightColumn(), tableViewPrivate->rightColumn()); + QCOMPARE(tableViewHPrivate->topRow(), 0); + QCOMPARE(tableViewVPrivate->leftColumn(), 0); + QCOMPARE(tableViewVPrivate->topRow(), tableViewPrivate->topRow()); + QCOMPARE(tableViewHVPrivate->leftColumn(), tableViewPrivate->leftColumn()); + QCOMPARE(tableViewHVPrivate->topRow(), tableViewPrivate->topRow()); + + // Check that the geometry of the tables are in sync after the flick + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.left(), tableViewPrivate->loadedTableOuterRect.left()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.right(), tableViewPrivate->loadedTableOuterRect.right()); + QCOMPARE(tableViewHPrivate->loadedTableOuterRect.top(), 0); + + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.top(), tableViewPrivate->loadedTableOuterRect.top()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.bottom(), tableViewPrivate->loadedTableOuterRect.bottom()); + QCOMPARE(tableViewVPrivate->loadedTableOuterRect.left(), 0); + + QCOMPARE(tableViewHVPrivate->loadedTableOuterRect, tableViewPrivate->loadedTableOuterRect); + +} + QTEST_MAIN(tst_QQuickTableView) #include "tst_qquicktableview.moc" diff --git a/tests/auto/quick/qquickvisualdatamodel/qquickvisualdatamodel.pro b/tests/auto/quick/qquickvisualdatamodel/qquickvisualdatamodel.pro index 9222e39477..5445f6768d 100644 --- a/tests/auto/quick/qquickvisualdatamodel/qquickvisualdatamodel.pro +++ b/tests/auto/quick/qquickvisualdatamodel/qquickvisualdatamodel.pro @@ -9,5 +9,5 @@ include (../shared/util.pri) TESTDATA = data/* -QT += core-private gui-private qml-private quick-private testlib +QT += core-private gui-private qml-private quick-private testlib qmlmodels-private qtHaveModule(widgets): QT += widgets diff --git a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp index fac8283e2c..32008f675a 100644 --- a/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp +++ b/tests/auto/quick/qquickvisualdatamodel/tst_qquickvisualdatamodel.cpp @@ -39,7 +39,7 @@ #include <QtQuick/qquickview.h> #include <private/qquicklistview_p.h> #include <QtQuick/private/qquicktext_p.h> -#include <QtQml/private/qqmldelegatemodel_p.h> +#include <QtQmlModels/private/qqmldelegatemodel_p.h> #include <private/qqmlvaluetype_p.h> #include <private/qqmlchangeset_p.h> #include <private/qqmlengine_p.h> diff --git a/tests/auto/quick/quick.pro b/tests/auto/quick/quick.pro index 7257a99d2a..b6ffdc0f7e 100644 --- a/tests/auto/quick/quick.pro +++ b/tests/auto/quick/quick.pro @@ -29,6 +29,7 @@ PRIVATETESTS += \ qquickanimations \ qquickapplication \ qquickbehaviors \ + qquickboundaryrule \ qquickfontloader \ qquickfontloader_static \ qquickfontmetrics \ diff --git a/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp b/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp index d7c54703ad..7db01180be 100644 --- a/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp +++ b/tests/benchmarks/qml/librarymetrics_performance/tst_librarymetrics_performance.cpp @@ -35,6 +35,16 @@ // This benchmark produces performance statistics // for the standard set of elements, properties and expressions which // are provided in the QtDeclarative library (QtQml and QtQuick). +// +// Note that we have hand-rolled our own benchmark harness, rather +// than directly using QBENCHMARK, in order to avoid contaminating +// the benchmark results with unwanted initialization or side-effects +// and allow us to more completely isolate the required specific area +// (compilation, instantiation, or cached type-data instantiation) +// that we wish to benchmark. + +#define AVERAGE_OVER_N 10 +#define IGNORE_N_OUTLIERS 2 class ModuleApi : public QObject { @@ -197,6 +207,18 @@ void tst_librarymetrics_performance::metrics_data() QTest::newRow("062) positioning - binding (with grid) positioning") << testFileUrl("data/bindingwithgridpositioning.qml"); } +// This method is intended to benchmark the amount of time / cpu cycles +// required to compile the given QML input. +// Note that this deliberately does NOT include the time taken to +// construct the QML engine. +// Also note that between each iteration, we attempt to clean the +// engine state (destroying and reconstructing the engine, clearing +// the QML type registrations, etc) to simulate a cold-start environment. +// +// The benchmark result is expected to be dominated by QML parsing, +// compilation, and optimization paths, and since the compiled component +// is never instantiated, no construction or binding evaluation should +// occur. void tst_librarymetrics_performance::compilation() { QFETCH(QUrl, qmlfile); @@ -211,37 +233,148 @@ void tst_librarymetrics_performance::compilation() } } - QBENCHMARK { + QList<qint64> nResults; + + // generate AVERAGE_OVER_N results + for (int i = 0; i < AVERAGE_OVER_N; ++i) { cleanState(&e); - QQmlComponent c(e, this); - c.loadUrl(qmlfile); // just compile. + { + QElapsedTimer et; + et.start(); + // BEGIN benchmarked code block + QQmlComponent c(e, this); + c.loadUrl(qmlfile); + // END benchmarked code block + qint64 etime = et.nsecsElapsed(); + nResults.append(etime); + } } + + // sort the list + qSort(nResults); + + // remove IGNORE_N_OUTLIERS*2 from ONLY the worst end (remove gc interference) + for (int i = 0; i < IGNORE_N_OUTLIERS; ++i) { + if (!nResults.isEmpty()) nResults.removeLast(); + if (!nResults.isEmpty()) nResults.removeLast(); + } + + // now generate an average + qint64 totaltime = 0; + if (nResults.size() == 0) nResults.append(9999); + for (int i = 0; i < nResults.size(); ++i) + totaltime += nResults.at(i); + double average = ((double)totaltime) / nResults.count(); + + // and return it as the result + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); // twice to workaround bug in QTestLib } +// This method is intended to benchmark the amount of time / cpu cycles +// required to compile and instantiate the given QML input, +// where the QML state has NOT been cleared in between each iteration. +// Thus, cached type data should be used when instantiating the object. +// +// The benchmark result is expected to be dominated by QObject +// hierarchy construction and first-time binding evaluation. void tst_librarymetrics_performance::instantiation_cached() { QFETCH(QUrl, qmlfile); + cleanState(&e); + QList<qint64> nResults; - QBENCHMARK { + // generate AVERAGE_OVER_N results + for (int i = 0; i < AVERAGE_OVER_N; ++i) { + QElapsedTimer et; + et.start(); + // BEGIN benchmarked code block QQmlComponent c(e, this); - c.loadUrl(qmlfile); // just compile. + c.loadUrl(qmlfile); QObject *o = c.create(); + // END benchmarked code block + qint64 etime = et.nsecsElapsed(); + nResults.append(etime); delete o; } + + // sort the list + qSort(nResults); + + // remove IGNORE_N_OUTLIERS*2 from ONLY the worst end (remove gc interference) + for (int i = 0; i < IGNORE_N_OUTLIERS; ++i) { + if (!nResults.isEmpty()) nResults.removeLast(); + if (!nResults.isEmpty()) nResults.removeLast(); + } + + // now generate an average + qint64 totaltime = 0; + if (nResults.size() == 0) nResults.append(9999); + for (int i = 0; i < nResults.size(); ++i) + totaltime += nResults.at(i); + double average = ((double)totaltime) / nResults.count(); + + // and return it as the result + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); // twice to workaround bug in QTestLib } +// This method is intended to benchmark the amount of time / cpu cycles +// required to compile and instantiate the given QML input, +// where the QML state has been cleared in between each iteration. +// This will cause the engine to parse, compile and optimize the +// input QML in each iteration. After compilation, the component +// will be instantiated, and so QObject hierarchy construction and +// first time binding evaluation will contribute to the result. +// +// The compilation phase is expected to dominate the result, however +// in some cases (complex positioning via expensive bindings, or +// other pathological cases) instantiation may dominate. Those +// cases are prime candidates for further investigation... void tst_librarymetrics_performance::instantiation() { QFETCH(QUrl, qmlfile); - QBENCHMARK { + cleanState(&e); + QList<qint64> nResults; + + // generate AVERAGE_OVER_N results + for (int i = 0; i < AVERAGE_OVER_N; ++i) { cleanState(&e); - QQmlComponent c(e, this); - c.loadUrl(qmlfile); // just compile. - QObject *o = c.create(); - delete o; + { + QElapsedTimer et; + et.start(); + // BEGIN benchmarked code block + QQmlComponent c(e, this); + c.loadUrl(qmlfile); + QObject *o = c.create(); + // END benchmarked code block + qint64 etime = et.nsecsElapsed(); + nResults.append(etime); + delete o; + } + } + + // sort the list + qSort(nResults); + + // remove IGNORE_N_OUTLIERS*2 from ONLY the worst end (remove gc interference) + for (int i = 0; i < IGNORE_N_OUTLIERS; ++i) { + if (!nResults.isEmpty()) nResults.removeLast(); + if (!nResults.isEmpty()) nResults.removeLast(); } + + // now generate an average + qint64 totaltime = 0; + if (nResults.size() == 0) nResults.append(9999); + for (int i = 0; i < nResults.size(); ++i) + totaltime += nResults.at(i); + double average = ((double)totaltime) / nResults.count(); + + // and return it as the result + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); // twice to workaround bug in QTestLib } void tst_librarymetrics_performance::positioners_data() @@ -256,19 +389,58 @@ void tst_librarymetrics_performance::positioners_data() QTest::newRow("07) positioning - binding (with grid) positioning") << testFileUrl("data/bindingwithgridpositioning.2.qml"); } -// this test triggers repositioning a large number of times, +// This method triggers repositioning a large number of times, // so we can track the cost of different repositioning methods. +// Note that the engine state is cleared before every iteration, +// so the benchmark result will include the cost of compilation +// as well as instantiation and first-time binding evaluation. +// +// The repositioning triggered within the QML is expected +// to dominate the benchmark time, especially if slow-path +// binding evaluation occurs. void tst_librarymetrics_performance::positioners() { QFETCH(QUrl, qmlfile); - QBENCHMARK { + cleanState(&e); + QList<qint64> nResults; + + // generate AVERAGE_OVER_N results + for (int i = 0; i < AVERAGE_OVER_N; ++i) { cleanState(&e); - QQmlComponent c(e, this); - c.loadUrl(qmlfile); // just compile. - QObject *o = c.create(); - delete o; + { + QElapsedTimer et; + et.start(); + // BEGIN benchmarked code block + QQmlComponent c(e, this); + c.loadUrl(qmlfile); + QObject *o = c.create(); + // END benchmarked code block + qint64 etime = et.nsecsElapsed(); + nResults.append(etime); + delete o; + } } + + // sort the list + qSort(nResults); + + // remove IGNORE_N_OUTLIERS*2 from ONLY the worst end (remove gc interference) + for (int i = 0; i < IGNORE_N_OUTLIERS; ++i) { + if (!nResults.isEmpty()) nResults.removeLast(); + if (!nResults.isEmpty()) nResults.removeLast(); + } + + // now generate an average + qint64 totaltime = 0; + if (nResults.size() == 0) nResults.append(9999); + for (int i = 0; i < nResults.size(); ++i) + totaltime += nResults.at(i); + double average = ((double)totaltime) / nResults.count(); + + // and return it as the result + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); + QTest::setBenchmarkResult(average, QTest::WalltimeNanoseconds); // twice to workaround bug in QTestLib } QTEST_MAIN(tst_librarymetrics_performance) diff --git a/tests/libfuzzer/qml/jsapi/evaluate/evaluate.pro b/tests/libfuzzer/qml/jsapi/evaluate/evaluate.pro new file mode 100644 index 0000000000..301b4f606a --- /dev/null +++ b/tests/libfuzzer/qml/jsapi/evaluate/evaluate.pro @@ -0,0 +1,6 @@ +QT -= gui +QT += qml +CONFIG += console +CONFIG -= app_bundle +SOURCES += main.cpp +LIBS += -fsanitize=fuzzer diff --git a/tests/libfuzzer/qml/jsapi/evaluate/main.cpp b/tests/libfuzzer/qml/jsapi/evaluate/main.cpp new file mode 100644 index 0000000000..9e90ba7cbd --- /dev/null +++ b/tests/libfuzzer/qml/jsapi/evaluate/main.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 <QCoreApplication> +#include <QJSEngine> + +// libfuzzer test for QJSEngine::evaluate() + +extern "C" int LLVMFuzzerTestOneInput(const char *Data, size_t Size) { + const QByteArray ba(Data, Size); + // avoid potential endless loops + if (ba.contains("for") || ba.contains("while")) + return 1; + int c = 0; + QCoreApplication a(c, nullptr); + QJSEngine().evaluate(ba); + return 0; +} diff --git a/tests/manual/pointer/content/FakeFlickable.qml b/tests/manual/pointer/content/FakeFlickable.qml index ffb5c4e914..636399ba2c 100644 --- a/tests/manual/pointer/content/FakeFlickable.qml +++ b/tests/manual/pointer/content/FakeFlickable.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the manual tests of the Qt Toolkit. @@ -26,10 +26,12 @@ ** ****************************************************************************/ -import QtQuick 2.12 +import QtQuick 2.14 +import Qt.labs.animation 1.0 Item { id: root + objectName: "viewport" default property alias data: __contentItem.data property alias velocity: anim.velocity property alias contentX: __contentItem.x // sign is reversed compared to Flickable.contentX @@ -45,52 +47,81 @@ Item { width: childrenRect.width height: childrenRect.height - property real xlimit: root.width - __contentItem.width - property real ylimit: root.height - __contentItem.height + BoundaryRule on x { + id: xbr + minimum: root.width - __contentItem.width + maximum: 0 + minimumOvershoot: 100 + maximumOvershoot: 100 + overshootFilter: BoundaryRule.Peak + } - function returnToBounds() { - if (x > 0) { - returnXAnim.to = 0 - returnXAnim.start() - } else if (x < xlimit) { - returnXAnim.to = xlimit - returnXAnim.start() - } - if (y > 0) { - returnYAnim.to = 0 - returnYAnim.start() - } else if (y < ylimit) { - returnYAnim.to = ylimit - returnYAnim.start() - } + BoundaryRule on y { + id: ybr + minimum: root.height - __contentItem.height + maximum: 0 + minimumOvershoot: 100 + maximumOvershoot: 100 + overshootFilter: BoundaryRule.Peak } DragHandler { id: dragHandler - onActiveChanged: if (!active) anim.restart(centroid.velocity) + onActiveChanged: + if (active) { + anim.stop() + root.flickStarted() + } else { + var vel = centroid.velocity + if (xbr.returnToBounds()) + vel.x = 0 + if (ybr.returnToBounds()) + vel.y = 0 + if (vel.x !== 0 || vel.y !== 0) + anim.restart(vel) + else + root.flickEnded() + } + } + WheelHandler { + rotationScale: 15 + property: "x" + orientation: Qt.Horizontal + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onActiveChanged: + // emitting signals in both instances is redundant but hard to avoid + // when the touchpad is flicking along both axes + if (active) { + anim.stop() + root.flickStarted() + } else { + xbr.returnToBounds() + root.flickEnded() + } + } + WheelHandler { + rotationScale: 15 + property: "y" + orientation: Qt.Vertical + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onActiveChanged: + if (active) { + anim.stop() + root.flickStarted() + } else { + ybr.returnToBounds() + root.flickEnded() + } } MomentumAnimation { id: anim target: __contentItem onStarted: root.flickStarted() onStopped: { - __contentItem.returnToBounds() + xbr.returnToBounds() + ybr.returnToBounds() root.flickEnded() } } - NumberAnimation { - id: returnXAnim - target: __contentItem - property: "x" - duration: 200 - easing.type: Easing.OutQuad - } - NumberAnimation { - id: returnYAnim - target: __contentItem - property: "y" - duration: 200 - easing.type: Easing.OutQuad - } } } diff --git a/tests/manual/pointer/content/LeftDrawer.qml b/tests/manual/pointer/content/LeftDrawer.qml new file mode 100644 index 0000000000..08f2f67b5c --- /dev/null +++ b/tests/manual/pointer/content/LeftDrawer.qml @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests 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$ +** +****************************************************************************/ +import QtQuick 2.14 +import Qt.labs.animation 1.0 +import QtGraphicalEffects 1.14 + +Item { + id: root + objectName: "LeftHandlerDrawer" + width: 100 + height: 400 + property real stickout: 4 + property real xOpen: rect.radius * -2 + property real xClosed: stickout - width + x: xClosed + y: 10 + + function close() { + openCloseAnimation.to = xClosed + openCloseAnimation.start() + } + function open() { + openCloseAnimation.to = xOpen + openCloseAnimation.start() + } + + DragHandler { + id: dragHandler + yAxis.enabled: false + xAxis.minimum: -1000 + margin: 20 + onActiveChanged: + if (!active) { + if (xbr.returnToBounds()) + return; + if (translation.x > 0) + open() + if (translation.x < 0) + close() + } + } + + BoundaryRule on x { + id: xbr + minimum: xClosed + maximum: xOpen + minimumOvershoot: rect.radius + maximumOvershoot: rect.radius + } + + NumberAnimation on x { + id: openCloseAnimation + duration: 300 + easing { type: Easing.OutBounce; overshoot: 5 } + } + + RectangularGlow { + id: effect + anchors.fill: parent + glowRadius: dragHandler.margin + spread: 0.2 + color: "cyan" + cornerRadius: rect.radius + glowRadius + } + + Rectangle { + id: rect + anchors.fill: parent + anchors.margins: 3 + color: "#333" + border.color: "cyan" + border.width: 2 + radius: 10 + antialiasing: true + } +} diff --git a/tests/manual/pointer/content/Slider.qml b/tests/manual/pointer/content/Slider.qml index c381d97c7c..beb84b176b 100644 --- a/tests/manual/pointer/content/Slider.qml +++ b/tests/manual/pointer/content/Slider.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the manual tests of the Qt Toolkit. @@ -26,7 +26,8 @@ ** ****************************************************************************/ -import QtQuick 2.12 +import QtQuick 2.14 +import Qt.labs.animation 1.0 Item { id: root @@ -42,8 +43,16 @@ Item { objectName: label.text + " DragHandler" target: knob xAxis.enabled: false - yAxis.minimum: slot.y - yAxis.maximum: slot.height + slot.y - knob.height + } + + WheelHandler { + id: wheelHandler + objectName: label.text + " WheelHandler" + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + invertible: false // Don't let the system "natural scrolling" setting affect this + rotationScale: -0.5 // But make it go consistently in the same direction as the fingers or wheel, a bit slow + target: knob + property: "y" } Rectangle { @@ -51,8 +60,8 @@ Item { anchors.top: parent.top anchors.bottom: parent.bottom anchors.margins: 10 - anchors.topMargin: 30 - anchors.bottomMargin: 30 + anchors.topMargin: label.height + 6 + anchors.bottomMargin: valueLabel.height + 4 anchors.horizontalCenter: parent.horizontalCenter width: 10 color: "black" @@ -82,10 +91,10 @@ Item { height: root.width / 2 width: implicitWidth / implicitHeight * height property bool programmatic: false - property real multiplier: root.maximumValue / (dragHandler.yAxis.maximum - dragHandler.yAxis.minimum) - onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - dragHandler.yAxis.minimum) * multiplier + property real multiplier: root.maximumValue / (ybr.maximum - ybr.minimum) + onYChanged: if (!programmatic) root.value = root.maximumValue - (knob.y - ybr.minimum) * multiplier transformOrigin: Item.Center - function setValue(value) { knob.y = dragHandler.yAxis.maximum - value / knob.multiplier } + function setValue(value) { knob.y = ybr.maximum - value / knob.multiplier } TapHandler { id: tap objectName: label.text + " TapHandler" @@ -95,9 +104,15 @@ Item { root.tapped } } + BoundaryRule on y { + id: ybr + minimum: slot.y + maximum: slot.height + slot.y - knob.height + } } Text { + id: valueLabel font.pointSize: 16 color: "red" anchors.bottom: parent.bottom diff --git a/tests/manual/pointer/fakeFlickable.qml b/tests/manual/pointer/fakeFlickable.qml index 284e0d1f34..be52e4dbaa 100644 --- a/tests/manual/pointer/fakeFlickable.qml +++ b/tests/manual/pointer/fakeFlickable.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the manual tests of the Qt Toolkit. @@ -30,39 +30,34 @@ import QtQuick 2.12 import "content" Rectangle { + id: root color: "#444" width: 480 - height: 480 + height: 640 FakeFlickable { id: ff anchors.fill: parent + anchors.leftMargin: 20 anchors.rightMargin: rightSB.width - Row { - Item { - width: 100 - height: 400 - Slider { - id: slider - label: "font size" - anchors.fill: parent - maximumValue: 36 - value: 14 - } - } - Text { - id: text - color: "beige" - font.family: "mono" - font.pointSize: slider.value - onTextChanged: console.log("text geom " + width + "x" + height + - ", parent " + parent + " geom " + parent.width + "x" + parent.height) - } - } + Text { + id: text + color: "beige" + font.family: "mono" + font.pointSize: slider.value + onTextChanged: console.log("text geom " + width + "x" + height + + ", parent " + parent + " geom " + parent.width + "x" + parent.height) + } - onFlickStarted: console.log("flick started with velocity " + velocity) - onFlickEnded: console.log("flick ended with velocity " + velocity) + onFlickStarted: { + root.border.color = "green" + console.log("flick started with velocity " + velocity) + } + onFlickEnded: { + root.border.color = "transparent" + console.log("flick ended with velocity " + velocity) + } Component.onCompleted: { var request = new XMLHttpRequest() @@ -101,4 +96,17 @@ Rectangle { bottom: parent.bottom } } + + LeftDrawer { + width: 100 + anchors.verticalCenter: parent.verticalCenter + Slider { + id: slider + label: "font\nsize" + anchors.fill: parent + anchors.margins: 10 + maximumValue: 36 + value: 14 + } + } } diff --git a/tests/manual/pointer/map.qml b/tests/manual/pointer/map.qml index c400874d58..0e815ccd9c 100644 --- a/tests/manual/pointer/map.qml +++ b/tests/manual/pointer/map.qml @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the manual tests of the Qt Toolkit. @@ -26,7 +26,7 @@ ** ****************************************************************************/ -import QtQuick 2.12 +import QtQuick 2.14 Item { width: 640 @@ -41,6 +41,20 @@ Item { height: image.height transform: Rotation { id: tilt; origin.x: width / 2; origin.y: height / 2; axis { x: 1; y: 0; z: 0 } } + WheelHandler { + id: wheelHandler + objectName: "vertical mouse wheel for scaling" + property: "scale" + onWheel: console.log("rotation " + event.angleDelta + " scaled " + rotation + " @ " + point.position + " => " + map.scale) + } + + WheelHandler { + id: horizontalWheelHandler + objectName: "horizontal mouse wheel for side-scrolling" + property: "x" + orientation: Qt.Horizontal + } + Image { id: image anchors.centerIn: parent diff --git a/tests/manual/pointer/pinchAndWheel.qml b/tests/manual/pointer/pinchAndWheel.qml new file mode 100644 index 0000000000..0944717bb7 --- /dev/null +++ b/tests/manual/pointer/pinchAndWheel.qml @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the manual tests 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$ +** +****************************************************************************/ + +import QtQuick 2.14 +import Qt.labs.animation 1.0 +import "content" + +Rectangle { + id: root + width: 1024; height: 600 + color: "#eee" + + CheckBox { + id: cbTouchpadEnabled + x: 10; y: 6 + label: "Touchpad wheel emulation enabled" + } + + CheckBox { + id: cbHorzWheelEnabled + x: cbTouchpadEnabled.width + 20; y: 6 + label: "Horizontal wheel rotation" + } + + function getTransformationDetails(item, pinchhandler) { + return "\n\npinch.scale:" + pinchhandler.scale.toFixed(2) + + "\npinch.rotation:" + pinchhandler.rotation.toFixed(2) + + "°\npinch.translation:" + "(" + pinchhandler.translation.x.toFixed(2) + "," + pinchhandler.translation.y.toFixed(2) + ")" + + "\nscale wheel.rotation:" + scaleWheelHandler.rotation.toFixed(2) + + "°\nhorizontal wheel.rotation:" + horizontalRotationWheelHandler.rotation.toFixed(2) + + "°\ncontrol-rotation wheel.rotation:" + controlRotationWheelHandler.rotation.toFixed(2) + + "°\nrect.scale: " + item.scale.toFixed(2) + + "\nrect.rotation: " + item.rotation.toFixed(2) + + "°\nrect.position: " + "(" + item.x.toFixed(2) + "," + item.y.toFixed(2) + ")" + } + + Rectangle { + id: transformable + width: parent.width - 100; height: parent.height - 100; x: 50; y: 50 + color: "#ffe0e0e0" + antialiasing: true + + PinchHandler { + id: parentPinch + objectName: "parent pinch" + minimumScale: 0.5 + maximumScale: 3 + } + + WheelHandler { + id: scaleWheelHandler + objectName: "mouse wheel for scaling" + acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse + acceptedModifiers: Qt.NoModifier + property: "scale" + onActiveChanged: if (!active) sbr.returnToBounds(); + onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.scale) + } + + BoundaryRule on scale { + id: sbr + minimum: 0.1 + maximum: 2 + minimumOvershoot: 0.05 + maximumOvershoot: 0.05 + } + + BoundaryRule on rotation { + id: rbr + minimum: -90 + maximum: 360 + minimumOvershoot: 10 + maximumOvershoot: 10 + } + + WheelHandler { + id: horizontalRotationWheelHandler + enabled: cbHorzWheelEnabled.checked + objectName: "horizontal mouse wheel for rotation" + acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse + acceptedModifiers: Qt.NoModifier + property: "rotation" + orientation: Qt.Horizontal + onActiveChanged: if (!active) rbr.returnToBounds() + onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.rotation) + } + + WheelHandler { + id: controlRotationWheelHandler + objectName: "control-mouse wheel for rotation" + acceptedDevices: cbTouchpadEnabled.checked ? PointerDevice.Mouse | PointerDevice.TouchPad : PointerDevice.Mouse + acceptedModifiers: Qt.ControlModifier + property: "rotation" + orientation: Qt.Vertical // already the default + // TODO returnToBounds() causes trouble because position isn't being adjusted when this happens + onActiveChanged: if (!active) rbr.returnToBounds() + onWheel: console.log(objectName + ": rotation " + event.angleDelta.y + " scaled " + rotation + " @ " + point.position + " => " + parent.rotation) + } + + HoverHandler { + id: hover + acceptedDevices: PointerDevice.AllDevices + property var scenePoint: transformable.mapToItem(root, point.position.x, point.position.y) + } + + Text { + text: "Pinch with 2 fingers to scale, rotate and translate\nMouse wheel to scale, Ctrl+mouse wheel or horizontal wheel to rotate" + + getTransformationDetails(parent, parentPinch) + } + } + + Rectangle { + width: 1; height: parent.height + color: "blue" + x: hover.scenePoint.x + Text { + x: implicitWidth / -2; style: Text.Outline; styleColor: "white" + y: 30 + color: "blue" + text: "outer " + parent.x.toFixed(2) + " inner " + hover.point.position.x.toFixed(2) + } + } + + Rectangle { + width: parent.width; height: 1 + color: "blue" + y: hover.scenePoint.y + Text { + x: 45 + y: implicitHeight / -2; style: Text.Outline; styleColor: "white" + color: "blue" + text: "outer " + parent.y.toFixed(2) + " inner " + hover.point.position.y.toFixed(2) + } + } +} diff --git a/tests/manual/tableview/tablemodel/form/RowForm.qml b/tests/manual/tableview/tablemodel/form/RowForm.qml new file mode 100644 index 0000000000..bb03e685c0 --- /dev/null +++ b/tests/manual/tableview/tablemodel/form/RowForm.qml @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.11 + +ScrollView { + clip: true + + function inputAsRow() { + return { + checked: checkedCheckBox.checked, + amount: amountSpinBox.value, + fruitType: fruitTypeTextField.text, + fruitName: fruitNameTextField.text, + fruitPrice: parseFloat(fruitPriceTextField.text) + } + } + + default property alias content: gridLayout.children + + GridLayout { + id: gridLayout + columns: 2 + + Label { + text: "checked" + } + CheckBox { + id: checkedCheckBox + } + + Label { + text: "amount" + } + SpinBox { + id: amountSpinBox + value: 1 + } + + Label { + text: "fruitType" + } + TextField { + id: fruitTypeTextField + text: "Pear" + } + + Label { + text: "fruitName" + } + TextField { + id: fruitNameTextField + text: "Williams" + } + + Label { + text: "fruitPrice" + } + TextField { + id: fruitPriceTextField + text: "1.50" + } + } +} diff --git a/tests/manual/tableview/tablemodel/form/form.pro b/tests/manual/tableview/tablemodel/form/form.pro new file mode 100644 index 0000000000..ba6f7a91b1 --- /dev/null +++ b/tests/manual/tableview/tablemodel/form/form.pro @@ -0,0 +1,10 @@ +TEMPLATE = app +TARGET = form +QT += qml quick +SOURCES += main.cpp +RESOURCES += main.qml RowForm.qml + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/tests/manual/tableview/tablemodel/form/main.cpp b/tests/manual/tableview/tablemodel/form/main.cpp new file mode 100644 index 0000000000..2a3b90d392 --- /dev/null +++ b/tests/manual/tableview/tablemodel/form/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/tests/manual/tableview/tablemodel/form/main.qml b/tests/manual/tableview/tablemodel/form/main.qml new file mode 100644 index 0000000000..6c6874fb4b --- /dev/null +++ b/tests/manual/tableview/tablemodel/form/main.qml @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import Qt.labs.qmlmodels 1.0 + +ApplicationWindow { + id: window + width: 800 + height: 800 + visible: true + + ColumnLayout { + anchors.fill: parent + + TableView { + id: tableView + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.horizontal: ScrollBar {} + ScrollBar.vertical: ScrollBar {} + + Layout.minimumHeight: window.height / 2 + Layout.fillWidth: true + Layout.fillHeight: true + + model: TableModel { + TableModelColumn { display: "checked" } + TableModelColumn { display: "amount" } + TableModelColumn { display: "fruitType" } + TableModelColumn { display: "fruitName" } + TableModelColumn { display: "fruitPrice" } + + // One row = one type of fruit that can be ordered + rows: [ + { + // Each object (line) is one column, + // and each property in that object represents a role. + checked: false, + amount: 1, + fruitType: "Apple", + fruitName: "Granny Smith", + fruitPrice: 1.50 + }, + { + checked: true, + amount: 4, + fruitType: "Orange", + fruitName: "Navel", + fruitPrice: 2.50 + }, + { + checked: false, + amount: 1, + fruitType: "Banana", + fruitName: "Cavendish", + fruitPrice: 3.50 + } + ] + } + + delegate: DelegateChooser { + DelegateChoice { + column: 0 + delegate: CheckBox { + objectName: "tableViewCheckBoxDelegate" + checked: model.display + onToggled: model.display = display + } + } + DelegateChoice { + column: 1 + delegate: SpinBox { + objectName: "tableViewSpinBoxDelegate" + value: model.display + onValueModified: model.display = value + } + } + DelegateChoice { + delegate: TextField { + objectName: "tableViewTextFieldDelegate" + text: model.display + selectByMouse: true + implicitWidth: 140 + onAccepted: model.display = text + } + } + } + } + + TabBar { + id: operationTabBar + + Layout.fillWidth: true + Layout.preferredHeight: 40 + + TabButton { + text: "Append" + } + TabButton { + text: "Clear" + } + TabButton { + text: "Insert" + } + TabButton { + text: "Move" + } + TabButton { + text: "Remove" + } + TabButton { + text: "Set" + } + } + + StackLayout { + currentIndex: operationTabBar.currentIndex + + ColumnLayout { + RowForm { + id: appendRowForm + + Layout.fillHeight: true + } + + Button { + text: "Append" + + Layout.alignment: Qt.AlignRight + + onClicked: tableView.model.appendRow(appendRowForm.inputAsRow()) + } + } + ColumnLayout { + Button { + text: "Clear" + enabled: tableView.rows > 0 + + onClicked: tableView.model.clear() + } + } + ColumnLayout { + RowForm { + id: insertRowForm + + Layout.fillHeight: true + + Label { + text: "Insert index" + } + SpinBox { + id: insertIndexSpinBox + from: 0 + to: tableView.rows + } + } + + Button { + text: "Insert" + + Layout.alignment: Qt.AlignRight + + onClicked: tableView.model.insertRow(insertIndexSpinBox.value, insertRowForm.inputAsRow()) + } + } + GridLayout { + columns: 2 + + Label { + text: "Move from index" + } + SpinBox { + id: moveFromIndexSpinBox + from: 0 + to: tableView.rows > 0 ? tableView.rows - 1 : 0 + } + + Label { + text: "Move to index" + } + SpinBox { + id: moveToIndexSpinBox + from: 0 + to: tableView.rows > 0 ? tableView.rows - 1 : 0 + } + + Label { + text: "Rows to move" + } + SpinBox { + id: rowsToMoveSpinBox + from: 1 + to: tableView.rows + } + + Button { + text: "Move" + enabled: tableView.rows > 0 + + Layout.alignment: Qt.AlignRight + Layout.columnSpan: 2 + + onClicked: tableView.model.moveRow(moveFromIndexSpinBox.value, moveToIndexSpinBox.value, rowsToMoveSpinBox.value) + } + } + GridLayout { + Label { + text: "Remove index" + } + SpinBox { + id: removeIndexSpinBox + from: 0 + to: tableView.rows > 0 ? tableView.rows - 1 : 0 + } + + Button { + text: "Remove" + enabled: tableView.rows > 0 + + Layout.alignment: Qt.AlignRight + Layout.columnSpan: 2 + + onClicked: tableView.model.removeRow(removeIndexSpinBox.value) + } + } + ColumnLayout { + RowForm { + id: setRowForm + + Layout.fillHeight: true + + Label { + text: "Set index" + } + SpinBox { + id: setIndexSpinBox + from: 0 + to: tableView.rows > 0 ? tableView.rows - 1 : 0 + } + } + + Button { + text: "Set" + + onClicked: tableView.model.setRow(setIndexSpinBox.value, setRowForm.inputAsRow()); + } + } + } + } +} diff --git a/tests/manual/tableview/tablemodel/json/JsonData.js b/tests/manual/tableview/tablemodel/json/JsonData.js new file mode 100644 index 0000000000..a37233c539 --- /dev/null +++ b/tests/manual/tableview/tablemodel/json/JsonData.js @@ -0,0 +1,186 @@ +let drivers = [ + { + "driverId":"fisichella", + "code":"FIS", + "url":"http://en.wikipedia.org/wiki/Giancarlo_Fisichella", + "givenName":"Giancarlo", + "familyName":"Fisichella", + "dateOfBirth":"1973-01-14", + "nationality":"Italian" + }, + { + "driverId":"barrichello", + "code":"BAR", + "url":"http://en.wikipedia.org/wiki/Rubens_Barrichello", + "givenName":"Rubens", + "familyName":"Barrichello", + "dateOfBirth":"1972-05-23", + "nationality":"Brazilian" + }, + { + "driverId":"alonso", + "permanentNumber":"14", + "code":"ALO", + "url":"http://en.wikipedia.org/wiki/Fernando_Alonso", + "givenName":"Fernando", + "familyName":"Alonso", + "dateOfBirth":"1981-07-29", + "nationality":"Spanish" + }, + { + "driverId":"coulthard", + "code":"COU", + "url":"http://en.wikipedia.org/wiki/David_Coulthard", + "givenName":"David", + "familyName":"Coulthard", + "dateOfBirth":"1971-03-27", + "nationality":"British" + }, + { + "driverId":"webber", + "code":"WEB", + "url":"http://en.wikipedia.org/wiki/Mark_Webber", + "givenName":"Mark", + "familyName":"Webber", + "dateOfBirth":"1976-08-27", + "nationality":"Australian" + }, + { + "driverId":"montoya", + "code":"MON", + "url":"http://en.wikipedia.org/wiki/Juan_Pablo_Montoya", + "givenName":"Juan", + "familyName":"Pablo Montoya", + "dateOfBirth":"1975-09-20", + "nationality":"Colombian" + }, + { + "driverId":"klien", + "code":"KLI", + "url":"http://en.wikipedia.org/wiki/Christian_Klien", + "givenName":"Christian", + "familyName":"Klien", + "dateOfBirth":"1983-02-07", + "nationality":"Austrian" + }, + { + "driverId":"raikkonen", + "permanentNumber":"7", + "code":"RAI", + "url":"http://en.wikipedia.org/wiki/Kimi_R%C3%A4ikk%C3%B6nen", + "givenName":"Kimi", + "familyName":"Räikkönen", + "dateOfBirth":"1979-10-17", + "nationality":"Finnish" + }, + { + "driverId":"trulli", + "code":"TRU", + "url":"http://en.wikipedia.org/wiki/Jarno_Trulli", + "givenName":"Jarno", + "familyName":"Trulli", + "dateOfBirth":"1974-07-13", + "nationality":"Italian" + }, + { + "driverId":"massa", + "permanentNumber":"19", + "code":"MAS", + "url":"http://en.wikipedia.org/wiki/Felipe_Massa", + "givenName":"Felipe", + "familyName":"Massa", + "dateOfBirth":"1981-04-25", + "nationality":"Brazilian" + }, + { + "driverId":"button", + "permanentNumber":"22", + "code":"BUT", + "url":"http://en.wikipedia.org/wiki/Jenson_Button", + "givenName":"Jenson", + "familyName":"Button", + "dateOfBirth":"1980-01-19", + "nationality":"British" + }, + { + "driverId":"ralf_schumacher", + "code":"SCH", + "url":"http://en.wikipedia.org/wiki/Ralf_Schumacher", + "givenName":"Ralf", + "familyName":"Schumacher", + "dateOfBirth":"1975-06-30", + "nationality":"German" + }, + { + "driverId":"villeneuve", + "code":"VIL", + "url":"http://en.wikipedia.org/wiki/Jacques_Villeneuve", + "givenName":"Jacques", + "familyName":"Villeneuve", + "dateOfBirth":"1971-04-09", + "nationality":"Canadian" + }, + { + "driverId":"sato", + "code":"SAT", + "url":"http://en.wikipedia.org/wiki/Takuma_Sato", + "givenName":"Takuma", + "familyName":"Sato", + "dateOfBirth":"1977-01-28", + "nationality":"Japanese" + }, + { + "driverId":"karthikeyan", + "code":"KAR", + "url":"http://en.wikipedia.org/wiki/Narain_Karthikeyan", + "givenName":"Narain", + "familyName":"Karthikeyan", + "dateOfBirth":"1977-01-14", + "nationality":"Indian" + }, + { + "driverId":"monteiro", + "code":"TMO", + "url":"http://en.wikipedia.org/wiki/Tiago_Monteiro", + "givenName":"Tiago", + "familyName":"Monteiro", + "dateOfBirth":"1976-07-24", + "nationality":"Portuguese" + }, + { + "driverId":"friesacher", + "code":"FRI", + "url":"http://en.wikipedia.org/wiki/Patrick_Friesacher", + "givenName":"Patrick", + "familyName":"Friesacher", + "dateOfBirth":"1980-09-26", + "nationality":"Austrian" + }, + { + "driverId":"michael_schumacher", + "code":"MSC", + "url":"http://en.wikipedia.org/wiki/Michael_Schumacher", + "givenName":"Michael", + "familyName":"Schumacher", + "dateOfBirth":"1969-01-03", + "nationality":"German" + }, + { + "driverId":"heidfeld", + "code":"HEI", + "url":"http://en.wikipedia.org/wiki/Nick_Heidfeld", + "givenName":"Nick", + "familyName":"Heidfeld", + "dateOfBirth":"1977-05-10", + "nationality":"German" + }, + { + "driverId":"albers", + "code":"ALB", + "url":"http://en.wikipedia.org/wiki/Christijan_Albers", + "givenName":"Christijan", + "familyName":"Albers", + "dateOfBirth":"1979-04-16", + "nationality":"Dutch" + } +] diff --git a/tests/manual/tableview/tablemodel/json/json.pro b/tests/manual/tableview/tablemodel/json/json.pro new file mode 100644 index 0000000000..2339e488aa --- /dev/null +++ b/tests/manual/tableview/tablemodel/json/json.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +TARGET = json +QT += qml quick +SOURCES += main.cpp +RESOURCES += \ + main.qml \ + JsonData.js + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/tests/manual/tableview/tablemodel/json/main.cpp b/tests/manual/tableview/tablemodel/json/main.cpp new file mode 100644 index 0000000000..176b532d49 --- /dev/null +++ b/tests/manual/tableview/tablemodel/json/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQmlApplicationEngine> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); + + return app.exec(); +} diff --git a/tests/manual/tableview/tablemodel/json/main.qml b/tests/manual/tableview/tablemodel/json/main.qml new file mode 100644 index 0000000000..2a835459c0 --- /dev/null +++ b/tests/manual/tableview/tablemodel/json/main.qml @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import Qt.labs.qmlmodels 1.0 + +import "JsonData.js" as CachedJsonData + +ApplicationWindow { + id: window + width: 800 + height: 300 + visible: true + + function requestJson() { + let doc = new XMLHttpRequest() + doc.onreadystatechange = function() { + if (doc.readyState === XMLHttpRequest.DONE) { + var root = JSON.parse(doc.responseText) + var race = root.MRData.RaceTable.Races[0] + var raceResults = race.Results + var drivers = [] + for (let i = 0; i < raceResults.length; ++i) { + drivers.push(raceResults[i].Driver) + } + tableView.model.rows = drivers + print(JSON.stringify(drivers)) + } + } + + doc.open("GET", "http://ergast.com/api/f1/2005/1/results.json") + doc.send() + } + + Component.onCompleted: requestJson() + // Same as the data we get from ergast. Use it while developing + // to avoid flooding the server with requests. +// Component.onCompleted: tableView.model.rows = CachedJsonData.drivers + + ColumnLayout { + anchors.fill: parent + + TableView { + id: tableView + boundsBehavior: Flickable.StopAtBounds + + ScrollBar.horizontal: ScrollBar { + policy: ScrollBar.AlwaysOn + } + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOn + } + + Layout.minimumHeight: window.height / 2 + Layout.fillWidth: true + Layout.fillHeight: true + + model: TableModel { + TableModelColumn { display: "driverId" } + TableModelColumn { display: "code" } + TableModelColumn { display: "url" } + TableModelColumn { display: "givenName" } + TableModelColumn { display: "familyName" } + TableModelColumn { display: "dateOfBirth" } + TableModelColumn { display: "nationality" } + } + + delegate: TextField { + objectName: "tableViewTextFieldDelegate" + text: model.display + selectByMouse: true + implicitWidth: 140 + onAccepted: model.display = text + } + } + } +} diff --git a/tests/manual/tableview/tablemodel/tablemodel.pro b/tests/manual/tableview/tablemodel/tablemodel.pro new file mode 100644 index 0000000000..4e4eba7653 --- /dev/null +++ b/tests/manual/tableview/tablemodel/tablemodel.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS += form |