5935 lines
236 KiB
C++
5935 lines
236 KiB
C++
/****************************************************************************
|
|
**
|
|
** 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 <qtest.h>
|
|
#include <QtQml/qqmlengine.h>
|
|
#include <QtQml/qqmlcomponent.h>
|
|
#include <QtQml/qqmlincubator.h>
|
|
#include <QtCore/qcoreapplication.h>
|
|
#include <QtCore/qfile.h>
|
|
#include <QtCore/qdebug.h>
|
|
#include <QtCore/qfileinfo.h>
|
|
#include <QtCore/qdir.h>
|
|
#include <QtCore/qscopeguard.h>
|
|
#include <QSignalSpy>
|
|
#include <QFont>
|
|
#include <QQmlFileSelector>
|
|
#include <QFileSelector>
|
|
#include <QEasingCurve>
|
|
#include <QScopeGuard>
|
|
|
|
#include <private/qqmlproperty_p.h>
|
|
#include <private/qqmlmetatype_p.h>
|
|
#include <private/qqmlglobal_p.h>
|
|
#include <private/qqmlscriptstring_p.h>
|
|
#include <private/qqmlvmemetaobject_p.h>
|
|
#include <private/qqmlcomponent_p.h>
|
|
|
|
#include "testtypes.h"
|
|
#include "testhttpserver.h"
|
|
|
|
#include "../../shared/util.h"
|
|
|
|
#include <deque>
|
|
|
|
#if defined(Q_OS_MAC)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
|
|
|
|
static inline bool isCaseSensitiveFileSystem(const QString &path) {
|
|
Q_UNUSED(path)
|
|
#if defined(Q_OS_MAC)
|
|
return pathconf(path.toLatin1().constData(), _PC_CASE_SENSITIVE);
|
|
#elif defined(Q_OS_WIN)
|
|
return false;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
This test case covers QML language issues. This covers everything that does not
|
|
involve evaluating ECMAScript expressions and bindings.
|
|
|
|
Evaluation of expressions and bindings is covered in qmlecmascript
|
|
*/
|
|
class tst_qqmllanguage : public QQmlDataTest
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
|
|
void errors_data();
|
|
void errors();
|
|
|
|
void insertedSemicolon_data();
|
|
void insertedSemicolon();
|
|
|
|
void simpleObject();
|
|
void simpleContainer();
|
|
void interfaceProperty();
|
|
void interfaceQList();
|
|
void assignObjectToSignal();
|
|
void assignObjectToVariant();
|
|
void assignLiteralSignalProperty();
|
|
void assignQmlComponent();
|
|
void assignBasicTypes();
|
|
void assignTypeExtremes();
|
|
void assignCompositeToType();
|
|
void assignLiteralToVariant();
|
|
void assignLiteralToVar();
|
|
void assignLiteralToJSValue();
|
|
void assignNullStrings();
|
|
void bindJSValueToVar();
|
|
void bindJSValueToVariant();
|
|
void bindJSValueToType();
|
|
void bindTypeToJSValue();
|
|
void customParserTypes();
|
|
void rootAsQmlComponent();
|
|
void rootItemIsComponent();
|
|
void inlineQmlComponents();
|
|
void idProperty();
|
|
void autoNotifyConnection();
|
|
void assignSignal();
|
|
void assignSignalFunctionExpression();
|
|
void overrideSignal_data();
|
|
void overrideSignal();
|
|
void dynamicProperties();
|
|
void dynamicPropertiesNested();
|
|
void listProperties();
|
|
void listPropertiesInheritanceNoCrash();
|
|
void badListItemType();
|
|
void dynamicObjectProperties();
|
|
void dynamicSignalsAndSlots();
|
|
void simpleBindings();
|
|
void noDoubleEvaluationForFlushedBindings_data();
|
|
void noDoubleEvaluationForFlushedBindings();
|
|
void autoComponentCreation();
|
|
void autoComponentCreationInGroupProperty();
|
|
void propertyValueSource();
|
|
void requiredProperty();
|
|
void requiredPropertyFromCpp_data();
|
|
void requiredPropertyFromCpp();
|
|
void attachedProperties();
|
|
void dynamicObjects();
|
|
void customVariantTypes();
|
|
void valueTypes();
|
|
void cppnamespace();
|
|
void aliasProperties();
|
|
void aliasPropertiesAndSignals();
|
|
void aliasPropertyChangeSignals();
|
|
void componentCompositeType();
|
|
void i18n();
|
|
void i18n_data();
|
|
void onCompleted();
|
|
void onDestruction();
|
|
void scriptString();
|
|
void scriptStringJs();
|
|
void scriptStringWithoutSourceCode();
|
|
void scriptStringComparison();
|
|
void defaultPropertyListOrder();
|
|
void declaredPropertyValues();
|
|
void dontDoubleCallClassBegin();
|
|
void reservedWords_data();
|
|
void reservedWords();
|
|
void inlineAssignmentsOverrideBindings();
|
|
void nestedComponentRoots();
|
|
void registrationOrder();
|
|
void readonly();
|
|
void readonlyObjectProperties();
|
|
void receivers();
|
|
void registeredCompositeType();
|
|
void registeredCompositeTypeWithEnum();
|
|
void registeredCompositeTypeWithAttachedProperty();
|
|
void implicitImportsLast();
|
|
|
|
void basicRemote_data();
|
|
void basicRemote();
|
|
void importsBuiltin_data();
|
|
void importsBuiltin();
|
|
void importsLocal_data();
|
|
void importsLocal();
|
|
void importsRemote_data();
|
|
void importsRemote();
|
|
void importsInstalled_data();
|
|
void importsInstalled();
|
|
void importsInstalledRemote_data();
|
|
void importsInstalledRemote();
|
|
void importsPath_data();
|
|
void importsPath();
|
|
void importsOrder_data();
|
|
void importsOrder();
|
|
void importIncorrectCase();
|
|
void importJs_data();
|
|
void importJs();
|
|
void importJsModule_data();
|
|
void importJsModule();
|
|
void explicitSelfImport();
|
|
void importInternalType();
|
|
|
|
void qmlAttachedPropertiesObjectMethod();
|
|
void customOnProperty();
|
|
void variantNotify();
|
|
|
|
void revisions();
|
|
void revisionOverloads();
|
|
|
|
void subclassedUncreateableRevision_data();
|
|
void subclassedUncreateableRevision();
|
|
|
|
void subclassedExtendedUncreateableRevision_data();
|
|
void subclassedExtendedUncreateableRevision();
|
|
|
|
void uncreatableTypesAsProperties();
|
|
|
|
void propertyInit();
|
|
void remoteLoadCrash();
|
|
void signalWithDefaultArg();
|
|
void signalParameterTypes();
|
|
void functionParameterTypes();
|
|
|
|
// regression tests for crashes
|
|
void crash1();
|
|
void crash2();
|
|
|
|
void globalEnums();
|
|
void lowercaseEnumRuntime_data();
|
|
void lowercaseEnumRuntime();
|
|
void lowercaseEnumCompileTime_data();
|
|
void lowercaseEnumCompileTime();
|
|
void scopedEnum();
|
|
void scopedEnumsWithNameClash();
|
|
void scopedEnumsWithResolvedNameClash();
|
|
void enumNoScopeLeak();
|
|
void qmlEnums();
|
|
void literals_data();
|
|
void literals();
|
|
|
|
void objectDeletionNotify_data();
|
|
void objectDeletionNotify();
|
|
|
|
void scopedProperties();
|
|
|
|
void deepProperty();
|
|
|
|
void compositeSingletonProperties();
|
|
void compositeSingletonSameEngine();
|
|
void compositeSingletonDifferentEngine();
|
|
void compositeSingletonNonTypeError();
|
|
void compositeSingletonQualifiedNamespace();
|
|
void compositeSingletonModule();
|
|
void compositeSingletonModuleVersioned();
|
|
void compositeSingletonModuleQualified();
|
|
void compositeSingletonInstantiateError();
|
|
void compositeSingletonDynamicPropertyError();
|
|
void compositeSingletonDynamicSignalAndJavaScriptPragma();
|
|
void compositeSingletonQmlRegisterTypeError();
|
|
void compositeSingletonQmldirNoPragmaError();
|
|
void compositeSingletonQmlDirError();
|
|
void compositeSingletonRemote();
|
|
void compositeSingletonSelectors();
|
|
void compositeSingletonRegistered();
|
|
void compositeSingletonCircular();
|
|
|
|
void singletonsHaveContextAndEngine();
|
|
|
|
void customParserBindingScopes();
|
|
void customParserEvaluateEnum();
|
|
void customParserProperties();
|
|
void customParserWithExtendedObject();
|
|
void nestedCustomParsers();
|
|
|
|
void preservePropertyCacheOnGroupObjects();
|
|
void propertyCacheInSync();
|
|
|
|
void rootObjectInCreationNotForSubObjects();
|
|
void lazyDeferredSubObject();
|
|
void deferredProperties();
|
|
void executeDeferredPropertiesOnce();
|
|
|
|
void noChildEvents();
|
|
|
|
void earlyIdObjectAccess();
|
|
|
|
void deleteSingletons();
|
|
|
|
void arrayBuffer_data();
|
|
void arrayBuffer();
|
|
|
|
void defaultListProperty();
|
|
void namespacedPropertyTypes();
|
|
|
|
void qmlTypeCanBeResolvedByName_data();
|
|
void qmlTypeCanBeResolvedByName();
|
|
|
|
void instanceof_data();
|
|
void instanceof();
|
|
|
|
void concurrentLoadQmlDir();
|
|
|
|
void accessDeletedObject();
|
|
|
|
void lowercaseTypeNames();
|
|
|
|
void thisInQmlScope();
|
|
|
|
void valueTypeGroupPropertiesInBehavior();
|
|
|
|
void retrieveQmlTypeId();
|
|
|
|
void polymorphicFunctionLookup();
|
|
void anchorsToParentInPropertyChanges();
|
|
|
|
void typeWrapperToVariant();
|
|
|
|
void extendedForeignTypes();
|
|
|
|
void inlineComponent();
|
|
void inlineComponent_data();
|
|
void inlineComponentReferenceCycle_data();
|
|
void inlineComponentReferenceCycle();
|
|
void nestedInlineComponentNotAllowed();
|
|
void inlineComponentStaticTypeResolution();
|
|
void inlineComponentInSingleton();
|
|
void nonExistingInlineComponent_data();
|
|
void nonExistingInlineComponent();
|
|
void inlineComponentFoundBeforeOtherImports();
|
|
void inlineComponentDuplicateNameError();
|
|
|
|
void selfReference();
|
|
void selfReferencingSingleton();
|
|
|
|
void listContainingDeletedObject();
|
|
void overrideSingleton();
|
|
void revisionedPropertyOfAttachedObjectProperty();
|
|
|
|
void arrayToContainer();
|
|
void qualifiedScopeInCustomParser();
|
|
|
|
void checkUncreatableNoReason();
|
|
private:
|
|
QQmlEngine engine;
|
|
QStringList defaultImportPathList;
|
|
|
|
void testType(const QString& qml, const QString& type, const QString& error, bool partialMatch = false);
|
|
|
|
// When calling into JavaScript, the specific type of the return value can differ if that return
|
|
// value is a number. This is not only the case for non-integral numbers, or numbers that do not
|
|
// fit into the (signed) integer range, but it also depends on which optimizations are run. So,
|
|
// to check if the return value is of a number type, use this method instead of checking against
|
|
// a specific userType.
|
|
static bool isJSNumberType(int userType)
|
|
{
|
|
return userType == (int) QVariant::Int
|
|
|| userType == (int) QVariant::UInt
|
|
|| userType == (int) QVariant::Double;
|
|
}
|
|
|
|
void getSingletonInstance(QQmlEngine& engine, const char* fileName, const char* propertyName, QObject** result /* out */);
|
|
void getSingletonInstance(QObject* o, const char* propertyName, QObject** result /* out */);
|
|
};
|
|
|
|
#define DETERMINE_ERRORS(errorfile,expected,actual)\
|
|
QList<QByteArray> expected; \
|
|
QList<QByteArray> actual; \
|
|
do { \
|
|
QFile file(testFile(errorfile)); \
|
|
QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text)); \
|
|
QByteArray data = file.readAll(); \
|
|
file.close(); \
|
|
expected = data.split('\n'); \
|
|
expected.removeAll(QByteArray("")); \
|
|
QList<QQmlError> errors = component.errors(); \
|
|
for (int ii = 0; ii < errors.count(); ++ii) { \
|
|
const QQmlError &error = errors.at(ii); \
|
|
QByteArray errorStr = QByteArray::number(error.line()) + ':' + \
|
|
QByteArray::number(error.column()) + ':' + \
|
|
error.description().toUtf8(); \
|
|
actual << errorStr; \
|
|
} \
|
|
} while (false);
|
|
|
|
#define VERIFY_ERRORS(errorfile) \
|
|
if (!errorfile) { \
|
|
if (qgetenv("DEBUG") != "" && !component.errors().isEmpty()) \
|
|
qWarning() << "Unexpected Errors:" << component.errors(); \
|
|
QVERIFY2(!component.isError(), qPrintable(component.errorString())); \
|
|
QVERIFY(component.errors().isEmpty()); \
|
|
} else { \
|
|
DETERMINE_ERRORS(errorfile,expected,actual);\
|
|
if (qgetenv("DEBUG") != "" && expected != actual) \
|
|
qWarning() << "Expected:" << expected << "Actual:" << actual; \
|
|
if (qgetenv("QDECLARATIVELANGUAGE_UPDATEERRORS") != "" && expected != actual) {\
|
|
QFile file(testFile(errorfile)); \
|
|
QVERIFY(file.open(QIODevice::WriteOnly)); \
|
|
for (int ii = 0; ii < actual.count(); ++ii) { \
|
|
file.write(actual.at(ii)); file.write("\n"); \
|
|
} \
|
|
file.close(); \
|
|
} else { \
|
|
QCOMPARE(actual, expected); \
|
|
} \
|
|
}
|
|
|
|
void tst_qqmllanguage::cleanupTestCase()
|
|
{
|
|
if (dataDirectoryUrl().scheme() != QLatin1String("qrc"))
|
|
QVERIFY(QFile::remove(testFile(QString::fromUtf8("I18nType\303\201\303\242\303\243\303\244\303\245.qml"))));
|
|
}
|
|
|
|
void tst_qqmllanguage::insertedSemicolon_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::addColumn<QString>("errorFile");
|
|
QTest::addColumn<bool>("create");
|
|
|
|
QTest::newRow("insertedSemicolon.1") << "insertedSemicolon.1.qml" << "insertedSemicolon.1.errors.txt" << false;
|
|
}
|
|
|
|
void tst_qqmllanguage::insertedSemicolon()
|
|
{
|
|
QFETCH(QString, file);
|
|
QFETCH(QString, errorFile);
|
|
QFETCH(bool, create);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
|
|
QScopedPointer<QObject> object;
|
|
|
|
if(create) {
|
|
object.reset(component.create());
|
|
QVERIFY(object.isNull());
|
|
}
|
|
|
|
VERIFY_ERRORS(errorFile.toLatin1().constData());
|
|
}
|
|
|
|
void tst_qqmllanguage::errors_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::addColumn<QString>("errorFile");
|
|
QTest::addColumn<bool>("create");
|
|
|
|
QTest::newRow("nonexistantProperty.1") << "nonexistantProperty.1.qml" << "nonexistantProperty.1.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.2") << "nonexistantProperty.2.qml" << "nonexistantProperty.2.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.3") << "nonexistantProperty.3.qml" << "nonexistantProperty.3.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.4") << "nonexistantProperty.4.qml" << "nonexistantProperty.4.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.5") << "nonexistantProperty.5.qml" << "nonexistantProperty.5.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.6") << "nonexistantProperty.6.qml" << "nonexistantProperty.6.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.7") << "nonexistantProperty.7.qml" << "nonexistantProperty.7.errors.txt" << false;
|
|
QTest::newRow("nonexistantProperty.8") << "nonexistantProperty.8.qml" << "nonexistantProperty.8.errors.txt" << false;
|
|
|
|
QTest::newRow("wrongType (string for int)") << "wrongType.1.qml" << "wrongType.1.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for bool)") << "wrongType.2.qml" << "wrongType.2.errors.txt" << false;
|
|
QTest::newRow("wrongType (bad rect)") << "wrongType.3.qml" << "wrongType.3.errors.txt" << false;
|
|
|
|
QTest::newRow("wrongType (invalid enum)") << "wrongType.4.qml" << "wrongType.4.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for uint)") << "wrongType.5.qml" << "wrongType.5.errors.txt" << false;
|
|
QTest::newRow("wrongType (string for real)") << "wrongType.6.qml" << "wrongType.6.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for color)") << "wrongType.7.qml" << "wrongType.7.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for date)") << "wrongType.8.qml" << "wrongType.8.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for time)") << "wrongType.9.qml" << "wrongType.9.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for datetime)") << "wrongType.10.qml" << "wrongType.10.errors.txt" << false;
|
|
QTest::newRow("wrongType (string for point)") << "wrongType.11.qml" << "wrongType.11.errors.txt" << false;
|
|
QTest::newRow("wrongType (color for size)") << "wrongType.12.qml" << "wrongType.12.errors.txt" << false;
|
|
QTest::newRow("wrongType (number string for int)") << "wrongType.13.qml" << "wrongType.13.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for string)") << "wrongType.14.qml" << "wrongType.14.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for url)") << "wrongType.15.qml" << "wrongType.15.errors.txt" << false;
|
|
QTest::newRow("wrongType (invalid object)") << "wrongType.16.qml" << "wrongType.16.errors.txt" << false;
|
|
QTest::newRow("wrongType (int for enum)") << "wrongType.17.qml" << "wrongType.17.errors.txt" << false;
|
|
|
|
QTest::newRow("readOnly.1") << "readOnly.1.qml" << "readOnly.1.errors.txt" << false;
|
|
QTest::newRow("readOnly.2") << "readOnly.2.qml" << "readOnly.2.errors.txt" << false;
|
|
QTest::newRow("readOnly.3") << "readOnly.3.qml" << "readOnly.3.errors.txt" << false;
|
|
QTest::newRow("readOnly.4") << "readOnly.4.qml" << "readOnly.4.errors.txt" << false;
|
|
QTest::newRow("readOnly.5") << "readOnly.5.qml" << "readOnly.5.errors.txt" << false;
|
|
|
|
QTest::newRow("listAssignment.1") << "listAssignment.1.qml" << "listAssignment.1.errors.txt" << false;
|
|
QTest::newRow("listAssignment.2") << "listAssignment.2.qml" << "listAssignment.2.errors.txt" << false;
|
|
QTest::newRow("listAssignment.3") << "listAssignment.3.qml" << "listAssignment.3.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidID.1") << "invalidID.qml" << "invalidID.errors.txt" << false;
|
|
QTest::newRow("invalidID.2") << "invalidID.2.qml" << "invalidID.2.errors.txt" << false;
|
|
QTest::newRow("invalidID.3") << "invalidID.3.qml" << "invalidID.3.errors.txt" << false;
|
|
QTest::newRow("invalidID.4") << "invalidID.4.qml" << "invalidID.4.errors.txt" << false;
|
|
QTest::newRow("invalidID.5") << "invalidID.5.qml" << "invalidID.5.errors.txt" << false;
|
|
QTest::newRow("invalidID.6") << "invalidID.6.qml" << "invalidID.6.errors.txt" << false;
|
|
QTest::newRow("invalidID.7") << "invalidID.7.qml" << "invalidID.7.errors.txt" << false;
|
|
QTest::newRow("invalidID.8") << "invalidID.8.qml" << "invalidID.8.errors.txt" << false;
|
|
QTest::newRow("invalidID.9") << "invalidID.9.qml" << "invalidID.9.errors.txt" << false;
|
|
QTest::newRow("invalidID.10") << "invalidID.10.qml" << "invalidID.10.errors.txt" << false;
|
|
|
|
QTest::newRow("scriptString.1") << "scriptString.1.qml" << "scriptString.1.errors.txt" << false;
|
|
QTest::newRow("scriptString.2") << "scriptString.2.qml" << "scriptString.2.errors.txt" << false;
|
|
|
|
QTest::newRow("unsupportedProperty") << "unsupportedProperty.qml" << "unsupportedProperty.errors.txt" << false;
|
|
QTest::newRow("nullDotProperty") << "nullDotProperty.qml" << "nullDotProperty.errors.txt" << true;
|
|
QTest::newRow("fakeDotProperty") << "fakeDotProperty.qml" << "fakeDotProperty.errors.txt" << false;
|
|
QTest::newRow("duplicateIDs") << "duplicateIDs.qml" << "duplicateIDs.errors.txt" << false;
|
|
QTest::newRow("unregisteredObject") << "unregisteredObject.qml" << "unregisteredObject.errors.txt" << false;
|
|
QTest::newRow("empty") << "empty.qml" << "empty.errors.txt" << false;
|
|
QTest::newRow("missingObject") << "missingObject.qml" << "missingObject.errors.txt" << false;
|
|
QTest::newRow("failingComponent") << "failingComponentTest.qml" << "failingComponent.errors.txt" << false;
|
|
QTest::newRow("missingSignal") << "missingSignal.qml" << "missingSignal.errors.txt" << false;
|
|
QTest::newRow("missingSignal2") << "missingSignal.2.qml" << "missingSignal.2.errors.txt" << false;
|
|
QTest::newRow("finalOverride") << "finalOverride.qml" << "finalOverride.errors.txt" << false;
|
|
QTest::newRow("customParserIdNotAllowed") << "customParserIdNotAllowed.qml" << "customParserIdNotAllowed.errors.txt" << false;
|
|
|
|
QTest::newRow("nullishCoalescing_LHS_Or") << "nullishCoalescing_LHS_Or.qml" << "nullishCoalescing_LHS_Or.errors.txt" << false;
|
|
QTest::newRow("nullishCoalescing_LHS_And") << "nullishCoalescing_LHS_And.qml" << "nullishCoalescing_LHS_And.errors.txt" << false;
|
|
QTest::newRow("nullishCoalescing_RHS_Or") << "nullishCoalescing_RHS_Or.qml" << "nullishCoalescing_RHS_Or.errors.txt" << false;
|
|
QTest::newRow("nullishCoalescing_RHS_And") << "nullishCoalescing_RHS_And.qml" << "nullishCoalescing_RHS_And.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidGroupedProperty.1") << "invalidGroupedProperty.1.qml" << "invalidGroupedProperty.1.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.2") << "invalidGroupedProperty.2.qml" << "invalidGroupedProperty.2.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.3") << "invalidGroupedProperty.3.qml" << "invalidGroupedProperty.3.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.4") << "invalidGroupedProperty.4.qml" << "invalidGroupedProperty.4.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.5") << "invalidGroupedProperty.5.qml" << "invalidGroupedProperty.5.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.6") << "invalidGroupedProperty.6.qml" << "invalidGroupedProperty.6.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.7") << "invalidGroupedProperty.7.qml" << "invalidGroupedProperty.7.errors.txt" << true;
|
|
QTest::newRow("invalidGroupedProperty.8") << "invalidGroupedProperty.8.qml" << "invalidGroupedProperty.8.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.9") << "invalidGroupedProperty.9.qml" << "invalidGroupedProperty.9.errors.txt" << false;
|
|
QTest::newRow("invalidGroupedProperty.10") << "invalidGroupedProperty.10.qml" << "invalidGroupedProperty.10.errors.txt" << false;
|
|
|
|
QTest::newRow("importNamespaceConflict") << "importNamespaceConflict.qml" << "importNamespaceConflict.errors.txt" << false;
|
|
QTest::newRow("importVersionMissing (builtin)") << "importVersionMissingBuiltIn.qml" << "importVersionMissingBuiltIn.errors.txt" << false;
|
|
QTest::newRow("importVersionMissing (installed)") << "importVersionMissingInstalled.qml" << "importVersionMissingInstalled.errors.txt" << false;
|
|
QTest::newRow("importNonExist (installed)") << "importNonExist.qml" << "importNonExist.errors.txt" << false;
|
|
QTest::newRow("importNonExistOlder (installed)") << "importNonExistOlder.qml" << "importNonExistOlder.errors.txt" << false;
|
|
QTest::newRow("importNewerVersion (installed)") << "importNewerVersion.qml" << "importNewerVersion.errors.txt" << false;
|
|
QTest::newRow("invalidImportID") << "invalidImportID.qml" << "invalidImportID.errors.txt" << false;
|
|
QTest::newRow("importFile") << "importFile.qml" << "importFile.errors.txt" << false;
|
|
|
|
QTest::newRow("signal.1") << "signal.1.qml" << "signal.1.errors.txt" << false;
|
|
QTest::newRow("signal.2") << "signal.2.qml" << "signal.2.errors.txt" << false;
|
|
QTest::newRow("signal.3") << "signal.3.qml" << "signal.3.errors.txt" << false;
|
|
QTest::newRow("signal.4") << "signal.4.qml" << "signal.4.errors.txt" << false;
|
|
QTest::newRow("signal.5") << "signal.5.qml" << "signal.5.errors.txt" << false;
|
|
QTest::newRow("signal.6") << "signal.6.qml" << "signal.6.errors.txt" << false;
|
|
|
|
QTest::newRow("method.1") << "method.1.qml" << "method.1.errors.txt" << false;
|
|
|
|
QTest::newRow("property.1") << "property.1.qml" << "property.1.errors.txt" << false;
|
|
QTest::newRow("property.2") << "property.2.qml" << "property.2.errors.txt" << false;
|
|
QTest::newRow("property.3") << "property.3.qml" << "property.3.errors.txt" << false;
|
|
QTest::newRow("property.4") << "property.4.qml" << "property.4.errors.txt" << false;
|
|
QTest::newRow("property.6") << "property.6.qml" << "property.6.errors.txt" << false;
|
|
QTest::newRow("property.7") << "property.7.qml" << "property.7.errors.txt" << false;
|
|
|
|
QTest::newRow("importScript.1") << "importscript.1.qml" << "importscript.1.errors.txt" << false;
|
|
|
|
QTest::newRow("Component.1") << "component.1.qml" << "component.1.errors.txt" << false;
|
|
QTest::newRow("Component.2") << "component.2.qml" << "component.2.errors.txt" << false;
|
|
QTest::newRow("Component.3") << "component.3.qml" << "component.3.errors.txt" << false;
|
|
QTest::newRow("Component.4") << "component.4.qml" << "component.4.errors.txt" << false;
|
|
QTest::newRow("Component.5") << "component.5.qml" << "component.5.errors.txt" << false;
|
|
QTest::newRow("Component.6") << "component.6.qml" << "component.6.errors.txt" << false;
|
|
QTest::newRow("Component.7") << "component.7.qml" << "component.7.errors.txt" << false;
|
|
QTest::newRow("Component.8") << "component.8.qml" << "component.8.errors.txt" << false;
|
|
QTest::newRow("Component.9") << "component.9.qml" << "component.9.errors.txt" << false;
|
|
|
|
QTest::newRow("MultiSet.1") << "multiSet.1.qml" << "multiSet.1.errors.txt" << false;
|
|
QTest::newRow("MultiSet.2") << "multiSet.2.qml" << "multiSet.2.errors.txt" << false;
|
|
QTest::newRow("MultiSet.3") << "multiSet.3.qml" << "multiSet.3.errors.txt" << false;
|
|
QTest::newRow("MultiSet.4") << "multiSet.4.qml" << "multiSet.4.errors.txt" << false;
|
|
QTest::newRow("MultiSet.5") << "multiSet.5.qml" << "multiSet.5.errors.txt" << false;
|
|
QTest::newRow("MultiSet.6") << "multiSet.6.qml" << "multiSet.6.errors.txt" << false;
|
|
QTest::newRow("MultiSet.7") << "multiSet.7.qml" << "multiSet.7.errors.txt" << false;
|
|
QTest::newRow("MultiSet.8") << "multiSet.8.qml" << "multiSet.8.errors.txt" << false;
|
|
QTest::newRow("MultiSet.9") << "multiSet.9.qml" << "multiSet.9.errors.txt" << false;
|
|
QTest::newRow("MultiSet.10") << "multiSet.10.qml" << "multiSet.10.errors.txt" << false;
|
|
QTest::newRow("MultiSet.11") << "multiSet.11.qml" << "multiSet.11.errors.txt" << false;
|
|
|
|
QTest::newRow("dynamicMeta.1") << "dynamicMeta.1.qml" << "dynamicMeta.1.errors.txt" << false;
|
|
QTest::newRow("dynamicMeta.2") << "dynamicMeta.2.qml" << "dynamicMeta.2.errors.txt" << false;
|
|
QTest::newRow("dynamicMeta.3") << "dynamicMeta.3.qml" << "dynamicMeta.3.errors.txt" << false;
|
|
QTest::newRow("dynamicMeta.4") << "dynamicMeta.4.qml" << "dynamicMeta.4.errors.txt" << false;
|
|
QTest::newRow("dynamicMeta.5") << "dynamicMeta.5.qml" << "dynamicMeta.5.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidAlias.1") << "invalidAlias.1.qml" << "invalidAlias.1.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.2") << "invalidAlias.2.qml" << "invalidAlias.2.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.3") << "invalidAlias.3.qml" << "invalidAlias.3.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.4") << "invalidAlias.4.qml" << "invalidAlias.4.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.5") << "invalidAlias.5.qml" << "invalidAlias.5.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.6") << "invalidAlias.6.qml" << "invalidAlias.6.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.7") << "invalidAlias.7.qml" << "invalidAlias.7.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.8") << "invalidAlias.8.qml" << "invalidAlias.8.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.9") << "invalidAlias.9.qml" << "invalidAlias.9.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.10") << "invalidAlias.10.qml" << "invalidAlias.10.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.11") << "invalidAlias.11.qml" << "invalidAlias.11.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.12") << "invalidAlias.12.qml" << "invalidAlias.12.errors.txt" << false;
|
|
QTest::newRow("invalidAlias.13") << "invalidAlias.13.qml" << "invalidAlias.13.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidAttachedProperty.1") << "invalidAttachedProperty.1.qml" << "invalidAttachedProperty.1.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.2") << "invalidAttachedProperty.2.qml" << "invalidAttachedProperty.2.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.3") << "invalidAttachedProperty.3.qml" << "invalidAttachedProperty.3.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.4") << "invalidAttachedProperty.4.qml" << "invalidAttachedProperty.4.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.5") << "invalidAttachedProperty.5.qml" << "invalidAttachedProperty.5.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.6") << "invalidAttachedProperty.6.qml" << "invalidAttachedProperty.6.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.7") << "invalidAttachedProperty.7.qml" << "invalidAttachedProperty.7.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.8") << "invalidAttachedProperty.8.qml" << "invalidAttachedProperty.8.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.9") << "invalidAttachedProperty.9.qml" << "invalidAttachedProperty.9.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.10") << "invalidAttachedProperty.10.qml" << "invalidAttachedProperty.10.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.11") << "invalidAttachedProperty.11.qml" << "invalidAttachedProperty.11.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.12") << "invalidAttachedProperty.12.qml" << "invalidAttachedProperty.12.errors.txt" << false;
|
|
QTest::newRow("invalidAttachedProperty.13") << "invalidAttachedProperty.13.qml" << "invalidAttachedProperty.13.errors.txt" << false;
|
|
|
|
QTest::newRow("assignValueToSignal") << "assignValueToSignal.qml" << "assignValueToSignal.errors.txt" << false;
|
|
QTest::newRow("emptySignal") << "emptySignal.qml" << "emptySignal.errors.txt" << false;
|
|
|
|
QTest::newRow("nestedErrors") << "nestedErrors.qml" << "nestedErrors.errors.txt" << false;
|
|
QTest::newRow("defaultGrouped") << "defaultGrouped.qml" << "defaultGrouped.errors.txt" << false;
|
|
QTest::newRow("doubleSignal") << "doubleSignal.qml" << "doubleSignal.errors.txt" << false;
|
|
QTest::newRow("missingValueTypeProperty") << "missingValueTypeProperty.qml" << "missingValueTypeProperty.errors.txt" << false;
|
|
QTest::newRow("objectValueTypeProperty") << "objectValueTypeProperty.qml" << "objectValueTypeProperty.errors.txt" << false;
|
|
QTest::newRow("enumTypes") << "enumTypes.qml" << "enumTypes.errors.txt" << false;
|
|
QTest::newRow("noCreation") << "noCreation.qml" << "noCreation.errors.txt" << false;
|
|
QTest::newRow("destroyedSignal") << "destroyedSignal.qml" << "destroyedSignal.errors.txt" << false;
|
|
QTest::newRow("assignToNamespace") << "assignToNamespace.qml" << "assignToNamespace.errors.txt" << false;
|
|
QTest::newRow("invalidOn") << "invalidOn.qml" << "invalidOn.errors.txt" << false;
|
|
QTest::newRow("invalidProperty") << "invalidProperty.qml" << "invalidProperty.errors.txt" << false;
|
|
QTest::newRow("nonScriptableProperty") << "nonScriptableProperty.qml" << "nonScriptableProperty.errors.txt" << false;
|
|
QTest::newRow("notAvailable") << "notAvailable.qml" << "notAvailable.errors.txt" << false;
|
|
QTest::newRow("singularProperty") << "singularProperty.qml" << "singularProperty.errors.txt" << false;
|
|
QTest::newRow("singularProperty.2") << "singularProperty.2.qml" << "singularProperty.2.errors.txt" << false;
|
|
|
|
QTest::newRow("scopedEnumList") << "scopedEnumList.qml" << "scopedEnumList.errors.txt" << false;
|
|
QTest::newRow("lowercase enum value") << "lowercaseQmlEnum.1.qml" << "lowercaseQmlEnum.1.errors.txt" << false;
|
|
QTest::newRow("lowercase enum type") << "lowercaseQmlEnum.2.qml" << "lowercaseQmlEnum.2.errors.txt" << false;
|
|
QTest::newRow("string enum value") << "invalidQmlEnumValue.1.qml" << "invalidQmlEnumValue.1.errors.txt" << false;
|
|
QTest::newRow("identifier enum type") << "invalidQmlEnumValue.2.qml" << "invalidQmlEnumValue.2.errors.txt" << false;
|
|
QTest::newRow("enum value too large") << "invalidQmlEnumValue.3.qml" << "invalidQmlEnumValue.3.errors.txt" << false;
|
|
QTest::newRow("non-integer enum value") << "invalidQmlEnumValue.4.qml" << "invalidQmlEnumValue.4.errors.txt" << false;
|
|
|
|
const QString expectedError = isCaseSensitiveFileSystem(dataDirectory()) ?
|
|
QStringLiteral("incorrectCase.errors.sensitive.txt") :
|
|
QStringLiteral("incorrectCase.errors.insensitive.txt");
|
|
QTest::newRow("incorrectCase") << "incorrectCase.qml" << expectedError << false;
|
|
|
|
QTest::newRow("metaobjectRevision.1") << "metaobjectRevision.1.qml" << "metaobjectRevision.1.errors.txt" << false;
|
|
QTest::newRow("metaobjectRevision.2") << "metaobjectRevision.2.qml" << "metaobjectRevision.2.errors.txt" << false;
|
|
QTest::newRow("metaobjectRevision.3") << "metaobjectRevision.3.qml" << "metaobjectRevision.3.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidRoot.1") << "invalidRoot.1.qml" << "invalidRoot.1.errors.txt" << false;
|
|
QTest::newRow("invalidRoot.2") << "invalidRoot.2.qml" << "invalidRoot.2.errors.txt" << false;
|
|
QTest::newRow("invalidRoot.3") << "invalidRoot.3.qml" << "invalidRoot.3.errors.txt" << false;
|
|
QTest::newRow("invalidRoot.4") << "invalidRoot.4.qml" << "invalidRoot.4.errors.txt" << false;
|
|
|
|
QTest::newRow("invalidTypeName.1") << "invalidTypeName.1.qml" << "invalidTypeName.1.errors.txt" << false;
|
|
QTest::newRow("invalidTypeName.2") << "invalidTypeName.2.qml" << "invalidTypeName.2.errors.txt" << false;
|
|
QTest::newRow("invalidTypeName.3") << "invalidTypeName.3.qml" << "invalidTypeName.3.errors.txt" << false;
|
|
QTest::newRow("invalidTypeName.4") << "invalidTypeName.4.qml" << "invalidTypeName.4.errors.txt" << false;
|
|
|
|
QTest::newRow("Major version isolation") << "majorVersionIsolation.qml" << "majorVersionIsolation.errors.txt" << false;
|
|
|
|
QTest::newRow("badCompositeRegistration.1") << "badCompositeRegistration.1.qml" << "badCompositeRegistration.1.errors.txt" << false;
|
|
QTest::newRow("badCompositeRegistration.2") << "badCompositeRegistration.2.qml" << "badCompositeRegistration.2.errors.txt" << false;
|
|
|
|
QTest::newRow("assignComponentToWrongType") << "assignComponentToWrongType.qml" << "assignComponentToWrongType.errors.txt" << false;
|
|
QTest::newRow("cyclicAlias") << "cyclicAlias.qml" << "cyclicAlias.errors.txt" << false;
|
|
|
|
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("fuzzed.3") << "fuzzed.3.qml" << "fuzzed.3.errors.txt" << false;
|
|
|
|
QTest::newRow("bareQmlImport") << "bareQmlImport.qml" << "bareQmlImport.errors.txt" << false;
|
|
|
|
QTest::newRow("typeAnnotations.2") << "typeAnnotations.2.qml" << "typeAnnotations.2.errors.txt" << false;
|
|
|
|
QTest::newRow("propertyUnknownType") << "propertyUnknownType.qml" << "propertyUnknownType.errors.txt" << false;
|
|
|
|
QTest::newRow("selfInstantiation") << "SelfInstantiation.qml" << "SelfInstantiation.errors.txt" << false;
|
|
}
|
|
|
|
void tst_qqmllanguage::errors()
|
|
{
|
|
#ifdef Q_OS_ANDROID
|
|
if (qstrcmp(QTest::currentDataTag(), "fuzzed.2") == 0) {
|
|
QSKIP("Gives different errors on Android");
|
|
/* Only gives one error on Android:
|
|
|
|
qrc:/data/fuzzed.2.qml:1:1: "
|
|
import"
|
|
^
|
|
So, it seems to complain about the first import (which is understandable)
|
|
*/
|
|
}
|
|
#endif
|
|
QFETCH(QString, file);
|
|
QFETCH(QString, errorFile);
|
|
QFETCH(bool, create);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
QTRY_VERIFY(!component.isLoading());
|
|
|
|
QScopedPointer<QObject> object;
|
|
|
|
if (create) {
|
|
object.reset(component.create());
|
|
QVERIFY(object.isNull());
|
|
}
|
|
|
|
VERIFY_ERRORS(errorFile.toLatin1().constData());
|
|
}
|
|
|
|
void tst_qqmllanguage::simpleObject()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("simpleObject.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
void tst_qqmllanguage::simpleContainer()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("simpleContainer.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> container(qobject_cast<MyContainer*>(component.create()));
|
|
QVERIFY(container != nullptr);
|
|
QCOMPARE(container->getChildren()->count(),2);
|
|
}
|
|
|
|
void tst_qqmllanguage::interfaceProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("interfaceProperty.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->interface());
|
|
QCOMPARE(object->interface()->id, 913);
|
|
}
|
|
|
|
void tst_qqmllanguage::interfaceQList()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("interfaceQList.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> container(qobject_cast<MyContainer*>(component.create()));
|
|
QVERIFY(container != nullptr);
|
|
QCOMPARE(container->getQListInterfaces()->count(), 2);
|
|
for(int ii = 0; ii < 2; ++ii)
|
|
QCOMPARE(container->getQListInterfaces()->at(ii)->id, 913);
|
|
}
|
|
|
|
void tst_qqmllanguage::assignObjectToSignal()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignObjectToSignal.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QTest::ignoreMessage(QtWarningMsg, "MyQmlObject::basicSlot");
|
|
emit object->basicSignal();
|
|
}
|
|
|
|
void tst_qqmllanguage::assignObjectToVariant()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignObjectToVariant.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QVariant v = object->property("a");
|
|
QVERIFY(v.userType() == qMetaTypeId<QObject *>());
|
|
}
|
|
|
|
void tst_qqmllanguage::assignLiteralSignalProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralSignalProperty.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->onLiteralSignal(), 10);
|
|
}
|
|
|
|
// Test is an external component can be loaded and assigned (to a qlist)
|
|
void tst_qqmllanguage::assignQmlComponent()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignQmlComponent.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->getChildren()->count(), 1);
|
|
QObject *child = object->getChildren()->at(0);
|
|
QCOMPARE(child->property("x"), QVariant(10));
|
|
QCOMPARE(child->property("y"), QVariant(11));
|
|
}
|
|
|
|
// Test literal assignment to all the basic types
|
|
void tst_qqmllanguage::assignBasicTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignBasicTypes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3);
|
|
QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2);
|
|
QCOMPARE(object->qtEnumProperty(), Qt::RichText);
|
|
QCOMPARE(object->mirroredEnumProperty(), MyTypeObject::MirroredEnumVal3);
|
|
QCOMPARE(object->relatedEnumProperty(), MyEnumContainer::RelatedValue);
|
|
QCOMPARE(object->stringProperty(), QString("Hello World!"));
|
|
QCOMPARE(object->uintProperty(), uint(10));
|
|
QCOMPARE(object->intProperty(), -19);
|
|
QCOMPARE((float)object->realProperty(), float(23.2));
|
|
QCOMPARE((float)object->doubleProperty(), float(-19.7));
|
|
QCOMPARE((float)object->floatProperty(), float(8.5));
|
|
QCOMPARE(object->colorProperty(), QColor("red"));
|
|
QCOMPARE(object->dateProperty(), QDate(1982, 11, 25));
|
|
QCOMPARE(object->timeProperty(), QTime(11, 11, 32));
|
|
QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1)));
|
|
QCOMPARE(object->pointProperty(), QPoint(99,13));
|
|
QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3));
|
|
QCOMPARE(object->sizeProperty(), QSize(99, 13));
|
|
QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2));
|
|
QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200));
|
|
QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99));
|
|
QCOMPARE(object->boolProperty(), true);
|
|
QCOMPARE(object->variantProperty(), QVariant("Hello World!"));
|
|
QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2f));
|
|
QCOMPARE(object->vector2Property(), QVector2D(2, 3));
|
|
QCOMPARE(object->vector4Property(), QVector4D(10, 1, 2.2f, 2.3f));
|
|
const QUrl encoded = QUrl::fromEncoded("main.qml?with%3cencoded%3edata", QUrl::TolerantMode);
|
|
QCOMPARE(object->urlProperty(), encoded);
|
|
QVERIFY(object->objectProperty() != nullptr);
|
|
MyTypeObject *child = qobject_cast<MyTypeObject *>(object->objectProperty());
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->intProperty(), 8);
|
|
|
|
//these used to go via script. Ensure they no longer do
|
|
QCOMPARE(object->property("qtEnumTriggeredChange").toBool(), false);
|
|
QCOMPARE(object->property("mirroredEnumTriggeredChange").toBool(), false);
|
|
}
|
|
|
|
// Test edge case type assignments
|
|
void tst_qqmllanguage::assignTypeExtremes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignTypeExtremes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->uintProperty(), 0xEE6B2800);
|
|
QCOMPARE(object->intProperty(), -0x77359400);
|
|
}
|
|
|
|
// Test that a composite type can assign to a property of its base type
|
|
void tst_qqmllanguage::assignCompositeToType()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignCompositeToType.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
// Test that literals are stored correctly in variant properties
|
|
void tst_qqmllanguage::assignLiteralToVariant()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToVariant.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QVERIFY(isJSNumberType(object->property("test1").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test2").userType()));
|
|
QCOMPARE(object->property("test3").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test4").userType(), (int)QVariant::Color);
|
|
QCOMPARE(object->property("test5").userType(), (int)QVariant::RectF);
|
|
QCOMPARE(object->property("test6").userType(), (int)QVariant::PointF);
|
|
QCOMPARE(object->property("test7").userType(), (int)QVariant::SizeF);
|
|
QCOMPARE(object->property("test8").userType(), (int)QVariant::Vector3D);
|
|
QCOMPARE(object->property("test9").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test10").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test11").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test12").userType(), (int)QVariant::Vector4D);
|
|
|
|
QCOMPARE(object->property("test1"), QVariant(1));
|
|
QCOMPARE(object->property("test2"), QVariant((double)1.7));
|
|
QVERIFY(object->property("test3") == QVariant(QString(QLatin1String("Hello world!"))));
|
|
QCOMPARE(object->property("test4"), QVariant(QColor::fromRgb(0xFF008800)));
|
|
QVERIFY(object->property("test5") == QVariant(QRectF(10, 10, 10, 10)));
|
|
QVERIFY(object->property("test6") == QVariant(QPointF(10, 10)));
|
|
QVERIFY(object->property("test7") == QVariant(QSizeF(10, 10)));
|
|
QVERIFY(object->property("test8") == QVariant(QVector3D(100, 100, 100)));
|
|
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test10"), QVariant(bool(true)));
|
|
QCOMPARE(object->property("test11"), QVariant(bool(false)));
|
|
QVERIFY(object->property("test12") == QVariant(QVector4D(100, 100, 100, 100)));
|
|
}
|
|
|
|
// Test that literals are stored correctly in "var" properties
|
|
// Note that behaviour differs from "variant" properties in that
|
|
// no conversion from "special strings" to QVariants is performed.
|
|
void tst_qqmllanguage::assignLiteralToVar()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToVar.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QVERIFY(isJSNumberType(object->property("test1").userType()));
|
|
QCOMPARE(object->property("test2").userType(), (int)QMetaType::Double);
|
|
QCOMPARE(object->property("test3").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test4").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test5").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test6").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test7").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test8").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test9").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test10").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test11").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test12").userType(), (int)QVariant::Color);
|
|
QCOMPARE(object->property("test13").userType(), (int)QVariant::RectF);
|
|
QCOMPARE(object->property("test14").userType(), (int)QVariant::PointF);
|
|
QCOMPARE(object->property("test15").userType(), (int)QVariant::SizeF);
|
|
QCOMPARE(object->property("test16").userType(), (int)QVariant::Vector3D);
|
|
QVERIFY(isJSNumberType(object->property("variantTest1Bound").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test1Bound").userType()));
|
|
|
|
QCOMPARE(object->property("test1"), QVariant(5));
|
|
QCOMPARE(object->property("test2"), QVariant((double)1.7));
|
|
QCOMPARE(object->property("test3"), QVariant(QString(QLatin1String("Hello world!"))));
|
|
QCOMPARE(object->property("test4"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test5"), QVariant(QString(QLatin1String("10,10,10x10"))));
|
|
QCOMPARE(object->property("test6"), QVariant(QString(QLatin1String("10,10"))));
|
|
QCOMPARE(object->property("test7"), QVariant(QString(QLatin1String("10x10"))));
|
|
QCOMPARE(object->property("test8"), QVariant(QString(QLatin1String("100,100,100"))));
|
|
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test10"), QVariant(bool(true)));
|
|
QCOMPARE(object->property("test11"), QVariant(bool(false)));
|
|
QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
|
|
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
|
|
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
|
|
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
|
|
QCOMPARE(object->property("test16"), QVariant(QVector3D(100, 100, 100)));
|
|
QCOMPARE(object->property("variantTest1Bound"), QVariant(9));
|
|
QCOMPARE(object->property("test1Bound"), QVariant(11));
|
|
}
|
|
|
|
void tst_qqmllanguage::assignLiteralToJSValue()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToJSValue.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root != nullptr);
|
|
|
|
{
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test1");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(5));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test2");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(1.7));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test3");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("Hello world!")));
|
|
}{
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test4");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("#FF008800")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test5");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("10,10,10x10")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test6");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("10,10")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test7");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("10x10")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test8");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("100,100,100")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test9");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("#FF008800")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test10");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isBool());
|
|
QCOMPARE(value.toBool(), true);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test11");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isBool());
|
|
QCOMPARE(value.toBool(), false);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test20");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isCallable());
|
|
QCOMPARE(value.call(QList<QJSValue> () << QJSValue(4)).toInt(), 12);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test21");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isUndefined());
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test22");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNull());
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test1Bound");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(9));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test20Bound");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(27));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("test23");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isQObject());
|
|
QCOMPARE(value.toQObject()->objectName(), "blah");
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::assignNullStrings()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignNullStrings.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->stringProperty().isNull());
|
|
QVERIFY(object->byteArrayProperty().isNull());
|
|
QMetaObject::invokeMethod(object.data(), "assignNullStringsFromJs", Qt::DirectConnection);
|
|
QVERIFY(object->stringProperty().isNull());
|
|
QVERIFY(object->byteArrayProperty().isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::bindJSValueToVar()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToJSValue.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root != nullptr);
|
|
|
|
QObject *object = root->findChild<QObject *>("varProperties");
|
|
|
|
QVERIFY(isJSNumberType(object->property("test1").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test2").userType()));
|
|
QCOMPARE(object->property("test3").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test4").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test5").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test6").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test7").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test8").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test9").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test10").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test11").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test12").userType(), (int)QVariant::Color);
|
|
QCOMPARE(object->property("test13").userType(), (int)QVariant::RectF);
|
|
QCOMPARE(object->property("test14").userType(), (int)QVariant::PointF);
|
|
QCOMPARE(object->property("test15").userType(), (int)QVariant::SizeF);
|
|
QCOMPARE(object->property("test16").userType(), (int)QVariant::Vector3D);
|
|
QVERIFY(isJSNumberType(object->property("test1Bound").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test20Bound").userType()));
|
|
|
|
QCOMPARE(object->property("test1"), QVariant(5));
|
|
QCOMPARE(object->property("test2"), QVariant((double)1.7));
|
|
QCOMPARE(object->property("test3"), QVariant(QString(QLatin1String("Hello world!"))));
|
|
QCOMPARE(object->property("test4"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test5"), QVariant(QString(QLatin1String("10,10,10x10"))));
|
|
QCOMPARE(object->property("test6"), QVariant(QString(QLatin1String("10,10"))));
|
|
QCOMPARE(object->property("test7"), QVariant(QString(QLatin1String("10x10"))));
|
|
QCOMPARE(object->property("test8"), QVariant(QString(QLatin1String("100,100,100"))));
|
|
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test10"), QVariant(bool(true)));
|
|
QCOMPARE(object->property("test11"), QVariant(bool(false)));
|
|
QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
|
|
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
|
|
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
|
|
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
|
|
QCOMPARE(object->property("test16"), QVariant(QVector3D(100, 100, 100)));
|
|
QCOMPARE(object->property("test1Bound"), QVariant(9));
|
|
QCOMPARE(object->property("test20Bound"), QVariant(27));
|
|
}
|
|
|
|
void tst_qqmllanguage::bindJSValueToVariant()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToJSValue.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root != nullptr);
|
|
|
|
QObject *object = root->findChild<QObject *>("variantProperties");
|
|
|
|
QVERIFY(isJSNumberType(object->property("test1").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test2").userType()));
|
|
QCOMPARE(object->property("test3").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test4").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test5").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test6").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test7").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test8").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test9").userType(), (int)QVariant::String);
|
|
QCOMPARE(object->property("test10").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test11").userType(), (int)QVariant::Bool);
|
|
QCOMPARE(object->property("test12").userType(), (int)QVariant::Color);
|
|
QCOMPARE(object->property("test13").userType(), (int)QVariant::RectF);
|
|
QCOMPARE(object->property("test14").userType(), (int)QVariant::PointF);
|
|
QCOMPARE(object->property("test15").userType(), (int)QVariant::SizeF);
|
|
QCOMPARE(object->property("test16").userType(), (int)QVariant::Vector3D);
|
|
QVERIFY(isJSNumberType(object->property("test1Bound").userType()));
|
|
QVERIFY(isJSNumberType(object->property("test20Bound").userType()));
|
|
|
|
QCOMPARE(object->property("test1"), QVariant(5));
|
|
QCOMPARE(object->property("test2"), QVariant((double)1.7));
|
|
QCOMPARE(object->property("test3"), QVariant(QString(QLatin1String("Hello world!"))));
|
|
QCOMPARE(object->property("test4"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test5"), QVariant(QString(QLatin1String("10,10,10x10"))));
|
|
QCOMPARE(object->property("test6"), QVariant(QString(QLatin1String("10,10"))));
|
|
QCOMPARE(object->property("test7"), QVariant(QString(QLatin1String("10x10"))));
|
|
QCOMPARE(object->property("test8"), QVariant(QString(QLatin1String("100,100,100"))));
|
|
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
|
|
QCOMPARE(object->property("test10"), QVariant(bool(true)));
|
|
QCOMPARE(object->property("test11"), QVariant(bool(false)));
|
|
QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
|
|
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
|
|
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
|
|
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
|
|
QCOMPARE(object->property("test16"), QVariant(QVector3D(100, 100, 100)));
|
|
QCOMPARE(object->property("test1Bound"), QVariant(9));
|
|
QCOMPARE(object->property("test20Bound"), QVariant(27));
|
|
}
|
|
|
|
void tst_qqmllanguage::bindJSValueToType()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignLiteralToJSValue.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root != nullptr);
|
|
|
|
{
|
|
MyTypeObject *object = root->findChild<MyTypeObject *>("typedProperties");
|
|
|
|
QCOMPARE(object->intProperty(), 5);
|
|
QCOMPARE(object->doubleProperty(), double(1.7));
|
|
QCOMPARE(object->stringProperty(), QString(QLatin1String("Hello world!")));
|
|
QCOMPARE(object->boolProperty(), true);
|
|
QCOMPARE(object->colorProperty(), QColor::fromRgbF(0.2, 0.3, 0.4, 0.5));
|
|
QCOMPARE(object->rectFProperty(), QRectF(10, 10, 10, 10));
|
|
QCOMPARE(object->pointFProperty(), QPointF(10, 10));
|
|
QCOMPARE(object->sizeFProperty(), QSizeF(10, 10));
|
|
QCOMPARE(object->vectorProperty(), QVector3D(100, 100, 100));
|
|
} {
|
|
MyTypeObject *object = root->findChild<MyTypeObject *>("stringProperties");
|
|
|
|
QCOMPARE(object->intProperty(), 1);
|
|
QCOMPARE(object->doubleProperty(), double(1.7));
|
|
QCOMPARE(object->stringProperty(), QString(QLatin1String("Hello world!")));
|
|
QCOMPARE(object->boolProperty(), true);
|
|
QCOMPARE(object->colorProperty(), QColor::fromRgb(0x00, 0x88, 0x00, 0xFF));
|
|
QCOMPARE(object->rectFProperty(), QRectF(10, 10, 10, 10));
|
|
QCOMPARE(object->pointFProperty(), QPointF(10, 10));
|
|
QCOMPARE(object->sizeFProperty(), QSizeF(10, 10));
|
|
QCOMPARE(object->vectorProperty(), QVector3D(100, 100, 100));
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::bindTypeToJSValue()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("bindTypeToJSValue.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root != nullptr);
|
|
|
|
{
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("flagProperty");
|
|
QVERIFY(object);
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("enumProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(MyTypeObject::EnumVal2));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("stringProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("Hello World!")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("uintProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(10));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("intProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(-19));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("realProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(23.2));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("doubleProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(-19.7));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("floatProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isNumber());
|
|
QCOMPARE(value.toNumber(), qreal(8.5));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("colorProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("r")).toNumber(), qreal(1.0));
|
|
QCOMPARE(value.property(QLatin1String("g")).toNumber(), qreal(0.0));
|
|
QCOMPARE(value.property(QLatin1String("b")).toNumber(), qreal(0.0));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("dateProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QCOMPARE(value.toDateTime().isValid(), true);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("timeProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QCOMPARE(value.toDateTime().isValid(), true);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("dateTimeProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QCOMPARE(value.toDateTime().isValid(), true);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("pointProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(99));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(13));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("pointFProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(-10.1));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(12.3));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("rectProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(9));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(7));
|
|
QCOMPARE(value.property(QLatin1String("width")).toNumber(), qreal(100));
|
|
QCOMPARE(value.property(QLatin1String("height")).toNumber(), qreal(200));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("rectFProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(1000.1));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(-10.9));
|
|
QCOMPARE(value.property(QLatin1String("width")).toNumber(), qreal(400));
|
|
QCOMPARE(value.property(QLatin1String("height")).toNumber(), qreal(90.99));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("boolProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isBool());
|
|
QCOMPARE(value.toBool(), true);
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("variantProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("Hello World!")));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("vectorProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(10.0f));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(1.0f));
|
|
QCOMPARE(value.property(QLatin1String("z")).toNumber(), qreal(2.2f));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("vector4Property");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isObject());
|
|
QCOMPARE(value.property(QLatin1String("x")).toNumber(), qreal(10.0f));
|
|
QCOMPARE(value.property(QLatin1String("y")).toNumber(), qreal(1.0f));
|
|
QCOMPARE(value.property(QLatin1String("z")).toNumber(), qreal(2.2f));
|
|
QCOMPARE(value.property(QLatin1String("w")).toNumber(), qreal(2.3f));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("urlProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
const QUrl encoded = QUrl::fromEncoded("main.qml?with%3cencoded%3edata", QUrl::TolerantMode);
|
|
QCOMPARE(value.toString(), encoded.toString());
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("objectProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isQObject());
|
|
QVERIFY(qobject_cast<MyTypeObject *>(value.toQObject()));
|
|
} {
|
|
MyQmlObject *object = root->findChild<MyQmlObject *>("varProperty");
|
|
QJSValue value = object->qjsvalue();
|
|
QVERIFY(value.isString());
|
|
QCOMPARE(value.toString(), QString(QLatin1String("Hello World!")));
|
|
}
|
|
}
|
|
|
|
// Tests that custom parser types can be instantiated
|
|
void tst_qqmllanguage::customParserTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customParserTypes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("count"), QVariant(2));
|
|
}
|
|
|
|
// Tests that the root item can be a custom component
|
|
void tst_qqmllanguage::rootAsQmlComponent()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("rootAsQmlComponent.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("x"), QVariant(11));
|
|
QCOMPARE(object->getChildren()->count(), 2);
|
|
}
|
|
|
|
void tst_qqmllanguage::rootItemIsComponent()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("rootItemIsComponent.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(qobject_cast<QQmlComponent*>(root.data()));
|
|
QScopedPointer<QObject> other(qobject_cast<QQmlComponent*>(root.data())->create());
|
|
QVERIFY(!other.isNull());
|
|
QQmlContext *context = qmlContext(other.data());
|
|
QVERIFY(context);
|
|
QCOMPARE(context->nameForObject(other.data()), QStringLiteral("blah"));
|
|
}
|
|
|
|
// Tests that components can be specified inline
|
|
void tst_qqmllanguage::inlineQmlComponents()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("inlineQmlComponents.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->getChildren()->count(), 1);
|
|
QQmlComponent *comp = qobject_cast<QQmlComponent *>(object->getChildren()->at(0));
|
|
QVERIFY(comp != nullptr);
|
|
QScopedPointer<MyQmlObject> compObject(qobject_cast<MyQmlObject *>(comp->create()));
|
|
QVERIFY(compObject != nullptr);
|
|
QCOMPARE(compObject->value(), 11);
|
|
}
|
|
|
|
// Tests that types that have an id property have it set
|
|
void tst_qqmllanguage::idProperty()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("idProperty.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->getChildren()->count(), 2);
|
|
MyTypeObject *child =
|
|
qobject_cast<MyTypeObject *>(object->getChildren()->at(0));
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->id(), QString("myObjectId"));
|
|
QCOMPARE(object->property("object"), QVariant::fromValue((QObject *)child));
|
|
|
|
child =
|
|
qobject_cast<MyTypeObject *>(object->getChildren()->at(1));
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->id(), QString("name.with.dots"));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("idPropertyMismatch.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(!root.isNull());
|
|
QQmlContext *ctx = qmlContext(root.data());
|
|
QVERIFY(ctx);
|
|
QCOMPARE(ctx->nameForObject(root.data()), QString("root"));
|
|
}
|
|
}
|
|
|
|
// Tests automatic connection to notify signals if "onBlahChanged" syntax is used
|
|
// even if the notify signal for "blah" is not called "blahChanged"
|
|
void tst_qqmllanguage::autoNotifyConnection()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("autoNotifyConnection.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QMetaProperty prop = object->metaObject()->property(object->metaObject()->indexOfProperty("receivedNotify"));
|
|
QVERIFY(prop.isValid());
|
|
|
|
QCOMPARE(prop.read(object.data()), QVariant::fromValue(false));
|
|
object->setPropertyWithNotify(1);
|
|
QCOMPARE(prop.read(object.data()), QVariant::fromValue(true));
|
|
}
|
|
|
|
// Tests that signals can be assigned to
|
|
void tst_qqmllanguage::assignSignal()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignSignal.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QTest::ignoreMessage(QtWarningMsg, "MyQmlObject::basicSlot");
|
|
emit object->basicSignal();
|
|
QTest::ignoreMessage(QtWarningMsg, "MyQmlObject::basicSlotWithArgs(9)");
|
|
emit object->basicParameterizedSignal(9);
|
|
}
|
|
|
|
void tst_qqmllanguage::assignSignalFunctionExpression()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("assignSignalFunctionExpression.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QTest::ignoreMessage(QtWarningMsg, "MyQmlObject::basicSlot");
|
|
emit object->basicSignal();
|
|
QTest::ignoreMessage(QtWarningMsg, "MyQmlObject::basicSlotWithArgs(9)");
|
|
emit object->basicParameterizedSignal(9);
|
|
}
|
|
|
|
void tst_qqmllanguage::overrideSignal_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::addColumn<QString>("errorFile");
|
|
|
|
QTest::newRow("override signal with signal") << "overrideSignal.1.qml" << "overrideSignal.1.errors.txt";
|
|
QTest::newRow("override signal with method") << "overrideSignal.2.qml" << "overrideSignal.2.errors.txt";
|
|
QTest::newRow("override signal with property") << "overrideSignal.3.qml" << "";
|
|
QTest::newRow("override signal of alias property with signal") << "overrideSignal.4.qml" << "overrideSignal.4.errors.txt";
|
|
QTest::newRow("override signal of superclass with signal") << "overrideSignal.5.qml" << "overrideSignal.5.errors.txt";
|
|
QTest::newRow("override builtin signal with signal") << "overrideSignal.6.qml" << "overrideSignal.6.errors.txt";
|
|
}
|
|
|
|
void tst_qqmllanguage::overrideSignal()
|
|
{
|
|
QFETCH(QString, file);
|
|
QFETCH(QString, errorFile);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
if (errorFile.isEmpty()) {
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->property("success").toBool());
|
|
} else {
|
|
VERIFY_ERRORS(errorFile.toLatin1().constData());
|
|
}
|
|
}
|
|
|
|
// Tests the creation and assignment of dynamic properties
|
|
void tst_qqmllanguage::dynamicProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dynamicProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QCOMPARE(object->property("intProperty"), QVariant(10));
|
|
QCOMPARE(object->property("boolProperty"), QVariant(false));
|
|
QCOMPARE(object->property("doubleProperty"), QVariant(-10.1));
|
|
QCOMPARE(object->property("realProperty"), QVariant((qreal)-19.9));
|
|
QCOMPARE(object->property("stringProperty"), QVariant("Hello World!"));
|
|
QCOMPARE(object->property("urlProperty"), QVariant(QUrl("main.qml")));
|
|
QCOMPARE(object->property("colorProperty"), QVariant(QColor("red")));
|
|
QCOMPARE(object->property("dateProperty"), QVariant(QDate(1945, 9, 2)));
|
|
QCOMPARE(object->property("varProperty"), QVariant("Hello World!"));
|
|
}
|
|
|
|
// Test that nested types can use dynamic properties
|
|
void tst_qqmllanguage::dynamicPropertiesNested()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dynamicPropertiesNested.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("super_a").toInt(), 11); // Overridden
|
|
QCOMPARE(object->property("super_c").toInt(), 14); // Inherited
|
|
QCOMPARE(object->property("a").toInt(), 13); // New
|
|
QCOMPARE(object->property("b").toInt(), 12); // New
|
|
}
|
|
|
|
// Tests the creation and assignment to dynamic list properties
|
|
void tst_qqmllanguage::listProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("listProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("test").toInt(), 2);
|
|
}
|
|
|
|
// Tests that initializing list properties of a base class does not crash
|
|
// (QTBUG-82171)
|
|
void tst_qqmllanguage::listPropertiesInheritanceNoCrash()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("listPropertiesChild.qml"));
|
|
QScopedPointer<QObject> object(component.create()); // should not crash
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
void tst_qqmllanguage::badListItemType()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("badListItemType.qml"));
|
|
QVERIFY(component.isError());
|
|
VERIFY_ERRORS("badListItemType.errors.txt");
|
|
}
|
|
|
|
// Tests the creation and assignment of dynamic object properties
|
|
// ### Not complete
|
|
void tst_qqmllanguage::dynamicObjectProperties()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dynamicObjectProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("objectProperty"), QVariant::fromValue((QObject*)nullptr));
|
|
QVERIFY(object->property("objectProperty2") != QVariant::fromValue((QObject*)nullptr));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dynamicObjectProperties.2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QVERIFY(object->property("objectProperty") != QVariant::fromValue((QObject*)nullptr));
|
|
}
|
|
}
|
|
|
|
// Tests the declaration of dynamic signals and slots
|
|
void tst_qqmllanguage::dynamicSignalsAndSlots()
|
|
{
|
|
QTest::ignoreMessage(QtDebugMsg, "1921");
|
|
|
|
QQmlComponent component(&engine, testFileUrl("dynamicSignalsAndSlots.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->metaObject()->indexOfMethod("signal1()") != -1);
|
|
QVERIFY(object->metaObject()->indexOfMethod("signal2()") != -1);
|
|
QVERIFY(object->metaObject()->indexOfMethod("slot1()") != -1);
|
|
QVERIFY(object->metaObject()->indexOfMethod("slot2()") != -1);
|
|
|
|
QCOMPARE(object->property("test").toInt(), 0);
|
|
QMetaObject::invokeMethod(object.data(), "slot3", Qt::DirectConnection, Q_ARG(QVariant, QVariant(10)));
|
|
QCOMPARE(object->property("test").toInt(), 10);
|
|
}
|
|
|
|
void tst_qqmllanguage::simpleBindings()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("simpleBindings.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("value1"), QVariant(10));
|
|
QCOMPARE(object->property("value2"), QVariant(10));
|
|
QCOMPARE(object->property("value3"), QVariant(21));
|
|
QCOMPARE(object->property("value4"), QVariant(10));
|
|
QCOMPARE(object->property("objectProperty"), QVariant::fromValue(object.data()));
|
|
}
|
|
|
|
class EvaluationCounter : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
int counter = 0;
|
|
Q_INVOKABLE void increaseEvaluationCounter() { ++counter; }
|
|
};
|
|
|
|
void tst_qqmllanguage::noDoubleEvaluationForFlushedBindings_data()
|
|
{
|
|
QTest::addColumn<QString>("fileName");
|
|
QTest::newRow("order1") << QString("noDoubleEvaluationForFlushedBindings.qml");
|
|
QTest::newRow("order2") << QString("noDoubleEvaluationForFlushedBindings.2.qml");
|
|
}
|
|
|
|
void tst_qqmllanguage::noDoubleEvaluationForFlushedBindings()
|
|
{
|
|
QFETCH(QString, fileName);
|
|
QQmlEngine engine;
|
|
|
|
EvaluationCounter stats;
|
|
engine.rootContext()->setContextProperty("stats", &stats);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(fileName));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(!obj.isNull());
|
|
|
|
QCOMPARE(stats.counter, 1);
|
|
}
|
|
|
|
void tst_qqmllanguage::autoComponentCreation()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("autoComponentCreation.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->componentProperty() != nullptr);
|
|
QScopedPointer<MyTypeObject> child(qobject_cast<MyTypeObject *>(object->componentProperty()->create()));
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->realProperty(), qreal(9));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("autoComponentCreation.2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->componentProperty() != nullptr);
|
|
QScopedPointer<MyTypeObject> child(qobject_cast<MyTypeObject *>(object->componentProperty()->create()));
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->realProperty(), qreal(9));
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::autoComponentCreationInGroupProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("autoComponentCreationInGroupProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(object->componentProperty() != nullptr);
|
|
QScopedPointer<MyTypeObject> child(qobject_cast<MyTypeObject *>(object->componentProperty()->create()));
|
|
QVERIFY(child != nullptr);
|
|
QCOMPARE(child->realProperty(), qreal(9));
|
|
}
|
|
|
|
void tst_qqmllanguage::propertyValueSource()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("propertyValueSource.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QList<QObject *> valueSources;
|
|
QObjectList allChildren = object->findChildren<QObject*>();
|
|
foreach (QObject *child, allChildren) {
|
|
if (qobject_cast<QQmlPropertyValueSource *>(child))
|
|
valueSources.append(child);
|
|
}
|
|
|
|
QCOMPARE(valueSources.count(), 1);
|
|
MyPropertyValueSource *valueSource =
|
|
qobject_cast<MyPropertyValueSource *>(valueSources.at(0));
|
|
QVERIFY(valueSource != nullptr);
|
|
QCOMPARE(valueSource->prop.object(), qobject_cast<QObject*>(object.data()));
|
|
QCOMPARE(valueSource->prop.name(), QString(QLatin1String("intProperty")));
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("propertyValueSource.2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QList<QObject *> valueSources;
|
|
QObjectList allChildren = object->findChildren<QObject*>();
|
|
foreach (QObject *child, allChildren) {
|
|
if (qobject_cast<QQmlPropertyValueSource *>(child))
|
|
valueSources.append(child);
|
|
}
|
|
|
|
QCOMPARE(valueSources.count(), 1);
|
|
MyPropertyValueSource *valueSource =
|
|
qobject_cast<MyPropertyValueSource *>(valueSources.at(0));
|
|
QVERIFY(valueSource != nullptr);
|
|
QCOMPARE(valueSource->prop.object(), qobject_cast<QObject*>(object.data()));
|
|
QCOMPARE(valueSource->prop.name(), QString(QLatin1String("intProperty")));
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::requiredProperty()
|
|
{
|
|
QQmlEngine engine;
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.2.qml"));
|
|
QVERIFY(!component.errors().empty());
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.4.qml"));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!component.errors().empty());
|
|
QVERIFY(component.errorString().contains("Required property objectName was not initialized"));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.3.qml"));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!component.errors().empty());
|
|
QVERIFY(component.errorString().contains("Required property i was not initialized"));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.5.qml"));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!component.errors().empty());
|
|
QVERIFY(component.errorString().contains("Required property i was not initialized"));
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.6.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("requiredProperties.7.qml"));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!component.errors().empty());
|
|
QVERIFY(component.errorString().contains("Property blub was marked as required but does not exist"));
|
|
}
|
|
}
|
|
|
|
class MyClassWithRequiredProperty : public QObject
|
|
{
|
|
public:
|
|
Q_OBJECT
|
|
Q_PROPERTY(int test MEMBER m_test REQUIRED NOTIFY testChanged)
|
|
Q_SIGNAL void testChanged();
|
|
private:
|
|
int m_test;
|
|
};
|
|
|
|
class ChildClassWithoutOwnRequired : public MyClassWithRequiredProperty
|
|
{
|
|
public:
|
|
Q_OBJECT
|
|
Q_PROPERTY(int test2 MEMBER m_test2 NOTIFY test2Changed)
|
|
Q_SIGNAL void test2Changed();
|
|
private:
|
|
int m_test2;
|
|
};
|
|
|
|
class ChildClassWithOwnRequired : public MyClassWithRequiredProperty
|
|
{
|
|
public:
|
|
Q_OBJECT
|
|
Q_PROPERTY(int test2 MEMBER m_test2 REQUIRED NOTIFY test2Changed)
|
|
Q_SIGNAL void test2Changed();
|
|
private:
|
|
int m_test2;
|
|
};
|
|
|
|
void tst_qqmllanguage::requiredPropertyFromCpp_data()
|
|
{
|
|
qmlRegisterType<MyClassWithRequiredProperty>("example.org", 1, 0, "MyClass");
|
|
qmlRegisterType<ChildClassWithoutOwnRequired>("example.org", 1, 0, "Child");
|
|
qmlRegisterType<ChildClassWithOwnRequired>("example.org", 1, 0, "Child2");
|
|
|
|
|
|
QTest::addColumn<QUrl>("setFile");
|
|
QTest::addColumn<QUrl>("notSetFile");
|
|
QTest::addColumn<QString>("errorMessage");
|
|
QTest::addColumn<int>("expectedValue");
|
|
|
|
QTest::addRow("direct") << testFileUrl("cppRequiredProperty.qml") << testFileUrl("cppRequiredPropertyNotSet.qml") << QString(":4 Required property test was not initialized\n") << 42;
|
|
QTest::addRow("in parent") << testFileUrl("cppRequiredPropertyInParent.qml") << testFileUrl("cppRequiredPropertyInParentNotSet.qml") << QString(":4 Required property test was not initialized\n") << 42;
|
|
QTest::addRow("in child and parent") << testFileUrl("cppRequiredPropertyInChildAndParent.qml") << testFileUrl("cppRequiredPropertyInChildAndParentNotSet.qml") << QString(":4 Required property test2 was not initialized\n") << 18;
|
|
}
|
|
|
|
void tst_qqmllanguage::requiredPropertyFromCpp()
|
|
{
|
|
QQmlEngine engine;
|
|
QFETCH(QUrl, setFile);
|
|
QFETCH(QUrl, notSetFile);
|
|
QFETCH(QString, errorMessage);
|
|
QFETCH(int, expectedValue);
|
|
{
|
|
QQmlComponent comp(&engine, notSetFile);
|
|
QScopedPointer<QObject> o { comp.create() };
|
|
QVERIFY(o.isNull());
|
|
QVERIFY(comp.isError());
|
|
QCOMPARE(comp.errorString(), notSetFile.toString() + errorMessage);
|
|
}
|
|
{
|
|
QQmlComponent comp(&engine, setFile);
|
|
QScopedPointer<QObject> o { comp.create() };
|
|
QVERIFY(!o.isNull());
|
|
QCOMPARE(o->property("test").toInt(), expectedValue);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::attachedProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("attachedProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QObject *attached = qmlAttachedPropertiesObject<MyQmlObject>(object.data());
|
|
QVERIFY(attached != nullptr);
|
|
QCOMPARE(attached->property("value"), QVariant(10));
|
|
QCOMPARE(attached->property("value2"), QVariant(13));
|
|
}
|
|
|
|
// Tests non-static object properties
|
|
void tst_qqmllanguage::dynamicObjects()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dynamicObject.1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
// Tests the registration of custom variant string converters
|
|
void tst_qqmllanguage::customVariantTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customVariantTypes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->customType().a, 10);
|
|
}
|
|
|
|
void tst_qqmllanguage::valueTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("valueTypes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QString message = component.url().toString() + ":2:1: QML MyTypeObject: Binding loop detected for property \"rectProperty.width\"";
|
|
QTest::ignoreMessage(QtWarningMsg, qPrintable(message));
|
|
QTest::ignoreMessage(QtWarningMsg, qPrintable(message));
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
|
|
QCOMPARE(object->rectProperty(), QRect(10, 11, 12, 13));
|
|
QCOMPARE(object->rectProperty2(), QRect(10, 11, 12, 13));
|
|
QCOMPARE(object->intProperty(), 10);
|
|
object->doAction();
|
|
QCOMPARE(object->rectProperty(), QRect(12, 11, 14, 13));
|
|
QCOMPARE(object->rectProperty2(), QRect(12, 11, 14, 13));
|
|
QCOMPARE(object->intProperty(), 12);
|
|
|
|
// ###
|
|
#if 0
|
|
QQmlProperty p(object, "rectProperty.x");
|
|
QCOMPARE(p.read(), QVariant(12));
|
|
p.write(13);
|
|
QCOMPARE(p.read(), QVariant(13));
|
|
|
|
quint32 r = QQmlPropertyPrivate::saveValueType(p.coreIndex(), p.valueTypeCoreIndex());
|
|
QQmlProperty p2;
|
|
QQmlPropertyPrivate::restore(p2, r, object);
|
|
QCOMPARE(p2.read(), QVariant(13));
|
|
#endif
|
|
}
|
|
|
|
void tst_qqmllanguage::cppnamespace()
|
|
{
|
|
QScopedPointer<QObject> object;
|
|
|
|
auto create = [&](const char *file) {
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
VERIFY_ERRORS(0);
|
|
object.reset(component.create());
|
|
QVERIFY(object != nullptr);
|
|
};
|
|
|
|
auto createAndCheck = [&](const char *file) {
|
|
create(file);
|
|
return !QTest::currentTestFailed();
|
|
};
|
|
|
|
QVERIFY(createAndCheck("cppnamespace.qml"));
|
|
QCOMPARE(object->property("intProperty").toInt(),
|
|
(int)MyNamespace::MyOtherNSEnum::OtherKey2);
|
|
|
|
QVERIFY(createAndCheck("cppstaticnamespace.qml"));
|
|
QCOMPARE(object->property("intProperty").toInt(),
|
|
(int)MyStaticNamespace::MyOtherNSEnum::OtherKey2);
|
|
|
|
QVERIFY(createAndCheck("cppnamespace.2.qml"));
|
|
QVERIFY(createAndCheck("cppstaticnamespace.2.qml"));
|
|
}
|
|
|
|
void tst_qqmllanguage::aliasProperties()
|
|
{
|
|
// Simple "int" alias
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
// Read through alias
|
|
QCOMPARE(object->property("valueAlias").toInt(), 10);
|
|
object->setProperty("value", QVariant(13));
|
|
QCOMPARE(object->property("valueAlias").toInt(), 13);
|
|
|
|
// Write through alias
|
|
object->setProperty("valueAlias", QVariant(19));
|
|
QCOMPARE(object->property("valueAlias").toInt(), 19);
|
|
QCOMPARE(object->property("value").toInt(), 19);
|
|
}
|
|
|
|
// Complex object alias
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
// Read through alias
|
|
MyQmlObject *v =
|
|
qvariant_cast<MyQmlObject *>(object->property("aliasObject"));
|
|
QVERIFY(v != nullptr);
|
|
QCOMPARE(v->value(), 10);
|
|
|
|
// Write through alias
|
|
MyQmlObject *v2 = new MyQmlObject();
|
|
v2->setParent(object.data());
|
|
object->setProperty("aliasObject", QVariant::fromValue(v2));
|
|
MyQmlObject *v3 =
|
|
qvariant_cast<MyQmlObject *>(object->property("aliasObject"));
|
|
QVERIFY(v3 != nullptr);
|
|
QCOMPARE(v3, v2);
|
|
}
|
|
|
|
// Nested aliases
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.3.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("value").toInt(), 1892);
|
|
QCOMPARE(object->property("value2").toInt(), 1892);
|
|
|
|
object->setProperty("value", QVariant(1313));
|
|
QCOMPARE(object->property("value").toInt(), 1313);
|
|
QCOMPARE(object->property("value2").toInt(), 1313);
|
|
|
|
object->setProperty("value2", QVariant(8080));
|
|
QCOMPARE(object->property("value").toInt(), 8080);
|
|
QCOMPARE(object->property("value2").toInt(), 8080);
|
|
}
|
|
|
|
// Enum aliases
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.4.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("enumAlias").toInt(), 1);
|
|
}
|
|
|
|
// Id aliases
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.5.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QVariant v = object->property("otherAlias");
|
|
QCOMPARE(v.userType(), qMetaTypeId<MyQmlObject*>());
|
|
MyQmlObject *o = qvariant_cast<MyQmlObject*>(v);
|
|
QCOMPARE(o->value(), 10);
|
|
|
|
delete o;
|
|
|
|
v = object->property("otherAlias");
|
|
QCOMPARE(v.userType(), qMetaTypeId<MyQmlObject*>());
|
|
o = qvariant_cast<MyQmlObject*>(v);
|
|
QVERIFY(!o);
|
|
}
|
|
|
|
// Nested aliases - this used to cause a crash
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.6.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("a").toInt(), 1923);
|
|
}
|
|
|
|
// Ptr Alias Cleanup - check that aliases to ptr types return 0
|
|
// if the object aliased to is removed
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.7.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QObject *object1 = qvariant_cast<QObject *>(object->property("object"));
|
|
QVERIFY(object1 != nullptr);
|
|
QObject *object2 = qvariant_cast<QObject *>(object1->property("object"));
|
|
QVERIFY(object2 != nullptr);
|
|
|
|
QObject *alias = qvariant_cast<QObject *>(object->property("aliasedObject"));
|
|
QCOMPARE(alias, object2);
|
|
|
|
delete object1;
|
|
|
|
QObject *alias2 = object.data(); // "Random" start value
|
|
int status = -1;
|
|
void *a[] = { &alias2, nullptr, &status };
|
|
QMetaObject::metacall(object.data(), QMetaObject::ReadProperty,
|
|
object->metaObject()->indexOfProperty("aliasedObject"), a);
|
|
QVERIFY(!alias2);
|
|
}
|
|
|
|
// Simple composite type
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.8.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("value").toInt(), 10);
|
|
}
|
|
|
|
// Complex composite type
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.9.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("value").toInt(), 10);
|
|
}
|
|
|
|
// Valuetype alias
|
|
// Simple "int" alias
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.10.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
// Read through alias
|
|
QCOMPARE(object->property("valueAlias").toRect(), QRect(10, 11, 9, 8));
|
|
object->setProperty("rectProperty", QVariant(QRect(33, 12, 99, 100)));
|
|
QCOMPARE(object->property("valueAlias").toRect(), QRect(33, 12, 99, 100));
|
|
|
|
// Write through alias
|
|
object->setProperty("valueAlias", QVariant(QRect(3, 3, 4, 9)));
|
|
QCOMPARE(object->property("valueAlias").toRect(), QRect(3, 3, 4, 9));
|
|
QCOMPARE(object->property("rectProperty").toRect(), QRect(3, 3, 4, 9));
|
|
}
|
|
|
|
// Valuetype sub-alias
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.11.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
// Read through alias
|
|
QCOMPARE(object->property("aliasProperty").toInt(), 19);
|
|
object->setProperty("rectProperty", QVariant(QRect(33, 8, 102, 111)));
|
|
QCOMPARE(object->property("aliasProperty").toInt(), 33);
|
|
|
|
// Write through alias
|
|
object->setProperty("aliasProperty", QVariant(4));
|
|
QCOMPARE(object->property("aliasProperty").toInt(), 4);
|
|
QCOMPARE(object->property("rectProperty").toRect(), QRect(4, 8, 102, 111));
|
|
}
|
|
|
|
// Nested aliases with a qml file
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.12.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QPointer<QObject> subObject = qvariant_cast<QObject*>(object->property("referencingSubObject"));
|
|
QVERIFY(!subObject.isNull());
|
|
|
|
QVERIFY(subObject->property("success").toBool());
|
|
}
|
|
|
|
// Nested aliases with a qml file with reverse ordering
|
|
{
|
|
// This is known to fail at the moment.
|
|
QQmlComponent component(&engine, testFileUrl("alias.13.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QPointer<QObject> subObject = qvariant_cast<QObject*>(object->property("referencingSubObject"));
|
|
QVERIFY(!subObject.isNull());
|
|
|
|
QVERIFY(subObject->property("success").toBool());
|
|
}
|
|
|
|
// "Nested" aliases within an object that require iterative resolution
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.14.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QPointer<QObject> subObject = qvariant_cast<QObject*>(object->property("referencingSubObject"));
|
|
QVERIFY(!subObject.isNull());
|
|
|
|
QVERIFY(subObject->property("success").toBool());
|
|
}
|
|
|
|
// Property bindings on group properties that are actually aliases (QTBUG-51043)
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.15.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QPointer<QObject> subItem = qvariant_cast<QObject*>(object->property("symbol"));
|
|
QVERIFY(!subItem.isNull());
|
|
|
|
QCOMPARE(subItem->property("y").toInt(), 1);
|
|
}
|
|
|
|
// Alias to sub-object with binding (QTBUG-57041)
|
|
{
|
|
// This is shold *not* crash.
|
|
QQmlComponent component(&engine, testFileUrl("alias.16.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
}
|
|
|
|
// Alias to grouped property
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.17.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
QVERIFY(object->property("success").toBool());
|
|
}
|
|
|
|
// Alias to grouped property updates
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.17.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
QObject *aliasUser = object->findChild<QObject*>(QLatin1String("aliasUser"));
|
|
QVERIFY(aliasUser);
|
|
QQmlProperty checkValueProp(object.get(), "checkValue");
|
|
QVERIFY(checkValueProp.isValid());
|
|
checkValueProp.write(777);
|
|
QCOMPARE(object->property("checkValue").toInt(), 777);
|
|
QCOMPARE(aliasUser->property("topMargin").toInt(), 777);
|
|
}
|
|
|
|
// Write to alias to grouped property
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.17.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
QObject *aliasUser = object->findChild<QObject*>(QLatin1String("aliasUser"));
|
|
QVERIFY(aliasUser);
|
|
QQmlProperty topMarginProp {aliasUser, "topMargin"};
|
|
QVERIFY(topMarginProp.isValid());
|
|
topMarginProp.write(777);
|
|
QObject *myItem = object->findChild<QObject*>(QLatin1String("myItem"));
|
|
QVERIFY(myItem);
|
|
auto anchors = myItem->property("anchors").value<QObject*>();
|
|
QVERIFY(anchors);
|
|
QCOMPARE(anchors->property("topMargin").toInt(), 777);
|
|
}
|
|
|
|
// Binding to alias to grouped property gets updated
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.17.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
QObject *aliasUser = object->findChild<QObject*>(QLatin1String("aliasUser"));
|
|
QVERIFY(aliasUser);
|
|
QQmlProperty topMarginProp {aliasUser, "topMargin"};
|
|
QVERIFY(topMarginProp.isValid());
|
|
topMarginProp.write(20);
|
|
QObject *myText = object->findChild<QObject*>(QLatin1String("myText"));
|
|
QVERIFY(myText);
|
|
auto text = myText->property("text").toString();
|
|
QCOMPARE(text, "alias:\n20");
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("alias.18.qml"));
|
|
VERIFY_ERRORS("alias.18.errors.txt");
|
|
}
|
|
}
|
|
|
|
// QTBUG-13374 Test that alias properties and signals can coexist
|
|
void tst_qqmllanguage::aliasPropertiesAndSignals()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("aliasPropertiesAndSignals.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o);
|
|
QCOMPARE(o->property("test").toBool(), true);
|
|
}
|
|
|
|
// Test that the root element in a composite type can be a Component
|
|
void tst_qqmllanguage::componentCompositeType()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("componentCompositeType.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
class TestType : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
TestType(QObject *p=nullptr) : QObject(p) {}
|
|
};
|
|
|
|
class TestType2 : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
TestType2(QObject *p=nullptr) : QObject(p) {}
|
|
};
|
|
|
|
void tst_qqmllanguage::i18n_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::addColumn<QString>("stringProperty");
|
|
QTest::newRow("i18nStrings") << "i18nStrings.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245 (5 accented 'a' letters)");
|
|
QTest::newRow("i18nDeclaredPropertyNames") << "i18nDeclaredPropertyNames.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245: 10");
|
|
QTest::newRow("i18nDeclaredPropertyUse") << "i18nDeclaredPropertyUse.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245: 15");
|
|
QTest::newRow("i18nScript") << "i18nScript.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245: 20");
|
|
QTest::newRow("i18nType") << "i18nType.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245: 30");
|
|
QTest::newRow("i18nNameSpace") << "i18nNameSpace.qml" << QString::fromUtf8("Test \303\241\303\242\303\243\303\244\303\245: 40");
|
|
}
|
|
|
|
void tst_qqmllanguage::i18n()
|
|
{
|
|
QFETCH(QString, file);
|
|
QFETCH(QString, stringProperty);
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->stringProperty(), stringProperty);
|
|
}
|
|
|
|
// Check that the Component::onCompleted attached property works
|
|
void tst_qqmllanguage::onCompleted()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("onCompleted.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QTest::ignoreMessage(QtDebugMsg, "Completed 6 10");
|
|
QTest::ignoreMessage(QtDebugMsg, "Completed 6 10");
|
|
QTest::ignoreMessage(QtDebugMsg, "Completed 10 11");
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
}
|
|
|
|
// Check that the Component::onDestruction attached property works
|
|
void tst_qqmllanguage::onDestruction()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("onDestruction.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QTest::ignoreMessage(QtDebugMsg, "Destruction 6 10");
|
|
QTest::ignoreMessage(QtDebugMsg, "Destruction 6 10");
|
|
QTest::ignoreMessage(QtDebugMsg, "Destruction 10 11");
|
|
}
|
|
|
|
// Check that assignments to QQmlScriptString properties work
|
|
void tst_qqmllanguage::scriptString()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QCOMPARE(object->scriptProperty().stringLiteral(), QString());
|
|
bool ok;
|
|
QCOMPARE(object->scriptProperty().numberLiteral(&ok), qreal(0.));
|
|
QCOMPARE(ok, false);
|
|
|
|
const QQmlScriptStringPrivate *scriptPrivate = QQmlScriptStringPrivate::get(object->scriptProperty());
|
|
QVERIFY(scriptPrivate != nullptr);
|
|
QCOMPARE(scriptPrivate->script, QString("foo + bar"));
|
|
QCOMPARE(scriptPrivate->scope, qobject_cast<QObject*>(object.data()));
|
|
QCOMPARE(scriptPrivate->context, qmlContext(object.data()));
|
|
|
|
QVERIFY(object->grouped() != nullptr);
|
|
const QQmlScriptStringPrivate *groupedPrivate = QQmlScriptStringPrivate::get(object->grouped()->script());
|
|
QCOMPARE(groupedPrivate->script, QString("console.log(1921)"));
|
|
QCOMPARE(groupedPrivate->scope, qobject_cast<QObject*>(object.data()));
|
|
QCOMPARE(groupedPrivate->context, qmlContext(object.data()));
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->scriptProperty().stringLiteral(), QString("hello\\n\\\"world\\\""));
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString3.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
bool ok;
|
|
QCOMPARE(object->scriptProperty().numberLiteral(&ok), qreal(12.345));
|
|
QCOMPARE(ok, true);
|
|
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString4.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
bool ok;
|
|
QCOMPARE(object->scriptProperty().booleanLiteral(&ok), true);
|
|
QCOMPARE(ok, true);
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString5.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->scriptProperty().isNullLiteral(), true);
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString6.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->scriptProperty().isUndefinedLiteral(), true);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptString7.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QQmlScriptString ss = object->scriptProperty();
|
|
|
|
{
|
|
QQmlExpression expr(ss, /*context*/nullptr, object.data());
|
|
QCOMPARE(expr.evaluate().toInt(), int(100));
|
|
}
|
|
|
|
{
|
|
SimpleObjectWithCustomParser testScope;
|
|
QVERIFY(testScope.metaObject()->indexOfProperty("intProperty") != object->metaObject()->indexOfProperty("intProperty"));
|
|
|
|
testScope.setIntProperty(42);
|
|
QQmlExpression expr(ss, /*context*/nullptr, &testScope);
|
|
QCOMPARE(expr.evaluate().toInt(), int(42));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check that assignments to QQmlScriptString properties works also from within Javascript
|
|
void tst_qqmllanguage::scriptStringJs()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scriptStringJs.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QQmlContext *context = QQmlEngine::contextForObject(object.data());
|
|
QVERIFY(context != nullptr);
|
|
bool ok;
|
|
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("\" hello \\\" world \""));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QCOMPARE(object->scriptProperty().stringLiteral(), QString(" hello \\\" world "));
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
|
|
|
|
QJSValue inst = engine.newQObject(object.data());
|
|
QJSValue func = engine.evaluate("(function(value) { this.scriptProperty = value })");
|
|
|
|
func.callWithInstance(inst, QJSValueList() << "test a \"string ");
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("\"test a \\\"string \""));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QCOMPARE(object->scriptProperty().stringLiteral(), QString("test a \\\"string "));
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
|
|
|
|
func.callWithInstance(inst, QJSValueList() << QJSValue::UndefinedValue);
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("undefined"));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
|
|
|
|
func.callWithInstance(inst, QJSValueList() << true);
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("true"));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(object->scriptProperty().booleanLiteral(&ok) && ok);
|
|
|
|
func.callWithInstance(inst, QJSValueList() << false);
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("false"));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && ok);
|
|
|
|
func.callWithInstance(inst, QJSValueList() << QJSValue::NullValue);
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("null"));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(object->scriptProperty().isNullLiteral());
|
|
QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
|
|
|
|
func.callWithInstance(inst, QJSValueList() << 12.34);
|
|
QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("12.34"));
|
|
QVERIFY(!object->scriptProperty().isEmpty());
|
|
QVERIFY(!object->scriptProperty().isUndefinedLiteral());
|
|
QVERIFY(!object->scriptProperty().isNullLiteral());
|
|
QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
|
|
QVERIFY(object->scriptProperty().numberLiteral(&ok) == 12.34 && ok);
|
|
QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
|
|
}
|
|
|
|
struct FreeUnitData
|
|
{
|
|
static void cleanup(const QV4::CompiledData::Unit *readOnlyQmlUnit)
|
|
{
|
|
if (readOnlyQmlUnit && !(readOnlyQmlUnit->flags & QV4::CompiledData::Unit::StaticData))
|
|
free(const_cast<QV4::CompiledData::Unit *>(readOnlyQmlUnit));
|
|
}
|
|
};
|
|
|
|
void tst_qqmllanguage::scriptStringWithoutSourceCode()
|
|
{
|
|
QUrl url = testFileUrl("scriptString7.qml");
|
|
QScopedPointer<const QV4::CompiledData::Unit, FreeUnitData> readOnlyQmlUnit;
|
|
{
|
|
QQmlEnginePrivate *eng = QQmlEnginePrivate::get(&engine);
|
|
QQmlRefPointer<QQmlTypeData> td = eng->typeLoader.getType(url);
|
|
Q_ASSERT(td);
|
|
|
|
QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit = td->compilationUnit();
|
|
readOnlyQmlUnit.reset(compilationUnit->unitData());
|
|
Q_ASSERT(readOnlyQmlUnit);
|
|
QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(malloc(readOnlyQmlUnit->unitSize));
|
|
memcpy(qmlUnit, readOnlyQmlUnit.data(), readOnlyQmlUnit->unitSize);
|
|
|
|
qmlUnit->flags &= ~QV4::CompiledData::Unit::StaticData;
|
|
compilationUnit->setUnitData(qmlUnit);
|
|
|
|
const QV4::CompiledData::Object *rootObject = compilationUnit->objectAt(/*root object*/0);
|
|
QCOMPARE(compilationUnit->stringAt(rootObject->inheritedTypeNameIndex), QString("MyTypeObject"));
|
|
quint32 i;
|
|
for (i = 0; i < rootObject->nBindings; ++i) {
|
|
const QV4::CompiledData::Binding *binding = rootObject->bindingTable() + i;
|
|
if (compilationUnit->stringAt(binding->propertyNameIndex) != QString("scriptProperty"))
|
|
continue;
|
|
QCOMPARE(compilationUnit->bindingValueAsScriptString(binding), QString("intProperty"));
|
|
const_cast<QV4::CompiledData::Binding*>(binding)->stringIndex = 0; // empty string index
|
|
QVERIFY(compilationUnit->bindingValueAsScriptString(binding).isEmpty());
|
|
break;
|
|
}
|
|
QVERIFY(i < rootObject->nBindings);
|
|
}
|
|
QQmlComponent component(&engine, url);
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QQmlScriptString ss = object->scriptProperty();
|
|
QVERIFY(!ss.isEmpty());
|
|
QCOMPARE(ss.stringLiteral(), QString());
|
|
bool ok;
|
|
QCOMPARE(ss.numberLiteral(&ok), qreal(0.));
|
|
QCOMPARE(ok, false);
|
|
|
|
const QQmlScriptStringPrivate *scriptPrivate = QQmlScriptStringPrivate::get(ss);
|
|
QVERIFY(scriptPrivate != nullptr);
|
|
QVERIFY(scriptPrivate->script.isEmpty());
|
|
QCOMPARE(scriptPrivate->scope, qobject_cast<QObject*>(object.data()));
|
|
QCOMPARE(scriptPrivate->context, qmlContext(object.data()));
|
|
|
|
{
|
|
QQmlExpression expr(ss, /*context*/nullptr, object.data());
|
|
QCOMPARE(expr.evaluate().toInt(), int(100));
|
|
}
|
|
}
|
|
|
|
// Test the QQmlScriptString comparison operators. The script strings are considered
|
|
// equal if there evaluation would produce the same result.
|
|
void tst_qqmllanguage::scriptStringComparison()
|
|
{
|
|
QQmlComponent component1(&engine, testFileUrl("scriptString.qml"));
|
|
QVERIFY(!component1.isError() && component1.errors().isEmpty());
|
|
QScopedPointer<MyTypeObject> object1(qobject_cast<MyTypeObject*>(component1.create()));
|
|
QVERIFY(object1 != nullptr);
|
|
|
|
QQmlComponent component2(&engine, testFileUrl("scriptString2.qml"));
|
|
QVERIFY(!component2.isError() && component2.errors().isEmpty());
|
|
QScopedPointer<MyTypeObject> object2(qobject_cast<MyTypeObject*>(component2.create()));
|
|
QVERIFY(object2 != nullptr);
|
|
|
|
QQmlComponent component3(&engine, testFileUrl("scriptString3.qml"));
|
|
QVERIFY(!component3.isError() && component3.errors().isEmpty());
|
|
QScopedPointer<MyTypeObject> object3(qobject_cast<MyTypeObject*>(component3.create()));
|
|
QVERIFY(object3 != nullptr);
|
|
|
|
//QJSValue inst1 = engine.newQObject(object1);
|
|
QJSValue inst2 = engine.newQObject(object2.data());
|
|
QJSValue inst3 = engine.newQObject(object3.data());
|
|
QJSValue func = engine.evaluate("(function(value) { this.scriptProperty = value })");
|
|
|
|
const QString s = "hello\\n\\\"world\\\"";
|
|
const qreal n = 12.345;
|
|
bool ok;
|
|
|
|
QCOMPARE(object2->scriptProperty().stringLiteral(), s);
|
|
QVERIFY(object3->scriptProperty().numberLiteral(&ok) == n && ok);
|
|
QCOMPARE(object1->scriptProperty(), object1->scriptProperty());
|
|
QCOMPARE(object2->scriptProperty(), object2->scriptProperty());
|
|
QCOMPARE(object3->scriptProperty(), object3->scriptProperty());
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
QVERIFY(object1->scriptProperty() != object2->scriptProperty());
|
|
QVERIFY(object1->scriptProperty() != object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << n);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << s);
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << s);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << QJSValue::UndefinedValue);
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << QJSValue::UndefinedValue);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << QJSValue::NullValue);
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << QJSValue::NullValue);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << false);
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << false);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
func.callWithInstance(inst2, QJSValueList() << true);
|
|
QVERIFY(object2->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << true);
|
|
QCOMPARE(object2->scriptProperty(), object3->scriptProperty());
|
|
|
|
QVERIFY(object1->scriptProperty() != object2->scriptProperty());
|
|
object2->setScriptProperty(object1->scriptProperty());
|
|
QCOMPARE(object1->scriptProperty(), object2->scriptProperty());
|
|
|
|
QVERIFY(object1->scriptProperty() != object3->scriptProperty());
|
|
func.callWithInstance(inst3, QJSValueList() << engine.toScriptValue(object1->scriptProperty()));
|
|
QCOMPARE(object1->scriptProperty(), object3->scriptProperty());
|
|
|
|
// While this are two instances of the same object they are still considered different
|
|
// because the (none literal) script string may access variables which have different
|
|
// values in both instances and hence evaluated to different results.
|
|
QScopedPointer<MyTypeObject> object1_2(qobject_cast<MyTypeObject*>(component1.create()));
|
|
QVERIFY(object1_2 != nullptr);
|
|
QVERIFY(object1->scriptProperty() != object1_2->scriptProperty());
|
|
}
|
|
|
|
// Check that default property assignments are correctly spliced into explicit
|
|
// property assignments
|
|
void tst_qqmllanguage::defaultPropertyListOrder()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("defaultPropertyListOrder.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<MyContainer> container(qobject_cast<MyContainer *>(component.create()));
|
|
QVERIFY(container != nullptr);
|
|
|
|
QCOMPARE(container->getChildren()->count(), 6);
|
|
QCOMPARE(container->getChildren()->at(0)->property("index"), QVariant(0));
|
|
QCOMPARE(container->getChildren()->at(1)->property("index"), QVariant(1));
|
|
QCOMPARE(container->getChildren()->at(2)->property("index"), QVariant(2));
|
|
QCOMPARE(container->getChildren()->at(3)->property("index"), QVariant(3));
|
|
QCOMPARE(container->getChildren()->at(4)->property("index"), QVariant(4));
|
|
QCOMPARE(container->getChildren()->at(5)->property("index"), QVariant(5));
|
|
}
|
|
|
|
void tst_qqmllanguage::declaredPropertyValues()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("declaredPropertyValues.qml"));
|
|
VERIFY_ERRORS(0);
|
|
}
|
|
|
|
void tst_qqmllanguage::dontDoubleCallClassBegin()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("dontDoubleCallClassBegin.qml"));
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o);
|
|
|
|
MyParserStatus *o2 = qobject_cast<MyParserStatus *>(qvariant_cast<QObject *>(o->property("object")));
|
|
QVERIFY(o2);
|
|
QCOMPARE(o2->classBeginCount(), 1);
|
|
QCOMPARE(o2->componentCompleteCount(), 1);
|
|
}
|
|
|
|
void tst_qqmllanguage::reservedWords_data()
|
|
{
|
|
QTest::addColumn<QByteArray>("word");
|
|
|
|
QTest::newRow("abstract") << QByteArray("abstract");
|
|
QTest::newRow("as") << QByteArray("as");
|
|
QTest::newRow("boolean") << QByteArray("boolean");
|
|
QTest::newRow("break") << QByteArray("break");
|
|
QTest::newRow("byte") << QByteArray("byte");
|
|
QTest::newRow("case") << QByteArray("case");
|
|
QTest::newRow("catch") << QByteArray("catch");
|
|
QTest::newRow("char") << QByteArray("char");
|
|
QTest::newRow("class") << QByteArray("class");
|
|
QTest::newRow("continue") << QByteArray("continue");
|
|
QTest::newRow("const") << QByteArray("const");
|
|
QTest::newRow("debugger") << QByteArray("debugger");
|
|
QTest::newRow("default") << QByteArray("default");
|
|
QTest::newRow("delete") << QByteArray("delete");
|
|
QTest::newRow("do") << QByteArray("do");
|
|
QTest::newRow("double") << QByteArray("double");
|
|
QTest::newRow("else") << QByteArray("else");
|
|
QTest::newRow("enum") << QByteArray("enum");
|
|
QTest::newRow("export") << QByteArray("export");
|
|
QTest::newRow("extends") << QByteArray("extends");
|
|
QTest::newRow("false") << QByteArray("false");
|
|
QTest::newRow("final") << QByteArray("final");
|
|
QTest::newRow("finally") << QByteArray("finally");
|
|
QTest::newRow("float") << QByteArray("float");
|
|
QTest::newRow("for") << QByteArray("for");
|
|
QTest::newRow("function") << QByteArray("function");
|
|
QTest::newRow("goto") << QByteArray("goto");
|
|
QTest::newRow("if") << QByteArray("if");
|
|
QTest::newRow("implements") << QByteArray("implements");
|
|
QTest::newRow("import") << QByteArray("import");
|
|
QTest::newRow("pragma") << QByteArray("pragma");
|
|
QTest::newRow("in") << QByteArray("in");
|
|
QTest::newRow("instanceof") << QByteArray("instanceof");
|
|
QTest::newRow("int") << QByteArray("int");
|
|
QTest::newRow("interface") << QByteArray("interface");
|
|
QTest::newRow("long") << QByteArray("long");
|
|
QTest::newRow("native") << QByteArray("native");
|
|
QTest::newRow("new") << QByteArray("new");
|
|
QTest::newRow("null") << QByteArray("null");
|
|
QTest::newRow("package") << QByteArray("package");
|
|
QTest::newRow("private") << QByteArray("private");
|
|
QTest::newRow("protected") << QByteArray("protected");
|
|
QTest::newRow("public") << QByteArray("public");
|
|
QTest::newRow("return") << QByteArray("return");
|
|
QTest::newRow("short") << QByteArray("short");
|
|
QTest::newRow("static") << QByteArray("static");
|
|
QTest::newRow("super") << QByteArray("super");
|
|
QTest::newRow("switch") << QByteArray("switch");
|
|
QTest::newRow("synchronized") << QByteArray("synchronized");
|
|
QTest::newRow("this") << QByteArray("this");
|
|
QTest::newRow("throw") << QByteArray("throw");
|
|
QTest::newRow("throws") << QByteArray("throws");
|
|
QTest::newRow("transient") << QByteArray("transient");
|
|
QTest::newRow("true") << QByteArray("true");
|
|
QTest::newRow("try") << QByteArray("try");
|
|
QTest::newRow("typeof") << QByteArray("typeof");
|
|
QTest::newRow("var") << QByteArray("var");
|
|
QTest::newRow("void") << QByteArray("void");
|
|
QTest::newRow("volatile") << QByteArray("volatile");
|
|
QTest::newRow("while") << QByteArray("while");
|
|
QTest::newRow("with") << QByteArray("with");
|
|
}
|
|
|
|
void tst_qqmllanguage::reservedWords()
|
|
{
|
|
QFETCH(QByteArray, word);
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQuick 2.0\nQtObject { property string " + word + " }", QUrl());
|
|
QCOMPARE(component.errorString(), QLatin1String(":2 Expected token `identifier'\n"));
|
|
}
|
|
|
|
// Check that first child of qml is of given type. Empty type insists on error.
|
|
void tst_qqmllanguage::testType(const QString& qml, const QString& type, const QString& expectederror, bool partialMatch)
|
|
{
|
|
if (engine.importPathList() == defaultImportPathList)
|
|
engine.addImportPath(testFile("lib"));
|
|
|
|
QQmlComponent component(&engine);
|
|
component.setData(qml.toUtf8(), testFileUrl("empty.qml")); // just a file for relative local imports
|
|
|
|
QTRY_VERIFY(!component.isLoading());
|
|
|
|
if (type.isEmpty()) {
|
|
QVERIFY(component.isError());
|
|
QString actualerror;
|
|
foreach (const QQmlError e, component.errors()) {
|
|
if (!actualerror.isEmpty())
|
|
actualerror.append("; ");
|
|
actualerror.append(e.description());
|
|
}
|
|
QCOMPARE(actualerror.left(partialMatch ? expectederror.length(): -1),expectederror);
|
|
} else {
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
const QMetaObject *meta = object->metaObject();
|
|
for (; meta; meta = meta->superClass()) {
|
|
const QString className(meta->className());
|
|
if (!className.contains("_QMLTYPE_") && !className.contains("_QML_")) {
|
|
QCOMPARE(className, type);
|
|
break;
|
|
}
|
|
}
|
|
QVERIFY(meta != nullptr);
|
|
}
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
// QTBUG-17276
|
|
void tst_qqmllanguage::inlineAssignmentsOverrideBindings()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("inlineAssignmentsOverrideBindings.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
QCOMPARE(o->property("test").toInt(), 11);
|
|
}
|
|
|
|
// QTBUG-19354
|
|
void tst_qqmllanguage::nestedComponentRoots()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("nestedComponentRoots.qml"));
|
|
}
|
|
|
|
// Import tests (QT-558)
|
|
void tst_qqmllanguage::importsBuiltin_data()
|
|
{
|
|
// QT-610
|
|
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
|
|
// import built-ins
|
|
QTest::newRow("missing import")
|
|
<< "Test {}"
|
|
<< ""
|
|
<< "Test is not a type";
|
|
QTest::newRow("not in version 0.0")
|
|
<< "import org.qtproject.Test 0.0\n"
|
|
"Test {}"
|
|
<< ""
|
|
<< "Test is not a type";
|
|
QTest::newRow("version not installed")
|
|
<< "import org.qtproject.Test 99.0\n"
|
|
"Test {}"
|
|
<< ""
|
|
<< "module \"org.qtproject.Test\" version 99.0 is not installed";
|
|
QTest::newRow("in version 0.0")
|
|
<< "import org.qtproject.Test 0.0\n"
|
|
"TestTP {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("qualified in version 0.0")
|
|
<< "import org.qtproject.Test 0.0 as T\n"
|
|
"T.TestTP {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("in version 1.0")
|
|
<< "import org.qtproject.Test 1.0\n"
|
|
"Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("qualified wrong")
|
|
<< "import org.qtproject.Test 1.0 as T\n" // QT-610
|
|
"Test {}"
|
|
<< ""
|
|
<< "Test is not a type";
|
|
QTest::newRow("qualified right")
|
|
<< "import org.qtproject.Test 1.0 as T\n"
|
|
"T.Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("qualified right but not in version 0.0")
|
|
<< "import org.qtproject.Test 0.0 as T\n"
|
|
"T.Test {}"
|
|
<< ""
|
|
<< "T.Test is not a type";
|
|
QTest::newRow("in version 1.1")
|
|
<< "import org.qtproject.Test 1.1\n"
|
|
"Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("in version 1.3")
|
|
<< "import org.qtproject.Test 1.3\n"
|
|
"Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("in version 1.5")
|
|
<< "import org.qtproject.Test 1.5\n"
|
|
"Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("changed in version 1.8")
|
|
<< "import org.qtproject.Test 1.8\n"
|
|
"Test {}"
|
|
<< "TestType2"
|
|
<< "";
|
|
QTest::newRow("in version 1.12")
|
|
<< "import org.qtproject.Test 1.12\n"
|
|
"Test {}"
|
|
<< "TestType2"
|
|
<< "";
|
|
QTest::newRow("old in version 1.9")
|
|
<< "import org.qtproject.Test 1.9\n"
|
|
"OldTest {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("old in version 1.11")
|
|
<< "import org.qtproject.Test 1.11\n"
|
|
"OldTest {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("multiversion 1")
|
|
<< "import org.qtproject.Test 1.11\n"
|
|
"import org.qtproject.Test 1.12\n"
|
|
"Test {}"
|
|
<< (!qmlCheckTypes()?"TestType2":"")
|
|
<< (!qmlCheckTypes()?"":"Test is ambiguous. Found in org/qtproject/Test/ in version 1.12 and 1.11");
|
|
QTest::newRow("multiversion 2")
|
|
<< "import org.qtproject.Test 1.11\n"
|
|
"import org.qtproject.Test 1.12\n"
|
|
"OldTest {}"
|
|
<< (!qmlCheckTypes()?"TestType":"")
|
|
<< (!qmlCheckTypes()?"":"OldTest is ambiguous. Found in org/qtproject/Test/ in version 1.12 and 1.11");
|
|
QTest::newRow("qualified multiversion 3")
|
|
<< "import org.qtproject.Test 1.0 as T0\n"
|
|
"import org.qtproject.Test 1.8 as T8\n"
|
|
"T0.Test {}"
|
|
<< "TestType"
|
|
<< "";
|
|
QTest::newRow("qualified multiversion 4")
|
|
<< "import org.qtproject.Test 1.0 as T0\n"
|
|
"import org.qtproject.Test 1.8 as T8\n"
|
|
"T8.Test {}"
|
|
<< "TestType2"
|
|
<< "";
|
|
}
|
|
|
|
void tst_qqmllanguage::importsBuiltin()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
testType(qml,type,error);
|
|
}
|
|
|
|
void tst_qqmllanguage::importsLocal_data()
|
|
{
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
|
|
// import locals
|
|
QTest::newRow("local import")
|
|
<< "import \"subdir\"\n" // QT-613
|
|
"Test {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("local import second")
|
|
<< "import QtQuick 2.0\nimport \"subdir\"\n"
|
|
"Test {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("local import subsubdir")
|
|
<< "import QtQuick 2.0\nimport \"subdir/subsubdir\"\n"
|
|
"SubTest {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("local import QTBUG-7721 A")
|
|
<< "subdir.Test {}" // no longer allowed (QTBUG-7721)
|
|
<< ""
|
|
<< "subdir.Test - subdir is neither a type nor a namespace";
|
|
QTest::newRow("local import QTBUG-7721 B")
|
|
<< "import \"subdir\" as X\n"
|
|
"X.subsubdir.SubTest {}" // no longer allowed (QTBUG-7721)
|
|
<< ""
|
|
<< "X.subsubdir.SubTest - subsubdir is not a type";
|
|
QTest::newRow("local import as")
|
|
<< "import \"subdir\" as T\n"
|
|
"T.Test {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("wrong local import as")
|
|
<< "import \"subdir\" as T\n"
|
|
"Test {}"
|
|
<< ""
|
|
<< "Test is not a type";
|
|
QTest::newRow("library precedence over local import")
|
|
<< "import \"subdir\"\n"
|
|
"import org.qtproject.Test 1.0\n"
|
|
"Test {}"
|
|
<< (!qmlCheckTypes()?"TestType":"")
|
|
<< (!qmlCheckTypes()?"":"Test is ambiguous. Found in org/qtproject/Test/ and in subdir/");
|
|
|
|
if (dataDirectoryUrl().scheme() != QLatin1String("qrc")) {
|
|
// file URL doesn't work with qrc scheme
|
|
QTest::newRow("file URL survives percent-encoding")
|
|
<< "import \"" + QUrl::fromLocalFile(QDir::currentPath() + "/{subdir}").toString() + "\"\n"
|
|
"Test {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::importsLocal()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
testType(qml,type,error);
|
|
}
|
|
|
|
void tst_qqmllanguage::basicRemote_data()
|
|
{
|
|
QTest::addColumn<QUrl>("url");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
|
|
QString serverdir = "/qtest/qml/qqmllanguage/";
|
|
|
|
QTest::newRow("no need for qmldir") << QUrl(serverdir+"Test.qml") << "" << "";
|
|
QTest::newRow("absent qmldir") << QUrl(serverdir+"/noqmldir/Test.qml") << "" << "";
|
|
QTest::newRow("need qmldir") << QUrl(serverdir+"TestNamed.qml") << "" << "";
|
|
}
|
|
|
|
void tst_qqmllanguage::basicRemote()
|
|
{
|
|
QFETCH(QUrl, url);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
url = server.baseUrl().resolved(url);
|
|
|
|
QQmlComponent component(&engine, url);
|
|
|
|
QTRY_VERIFY(!component.isLoading());
|
|
|
|
if (error.isEmpty()) {
|
|
if (component.isError())
|
|
qDebug() << component.errors();
|
|
QVERIFY(!component.isError());
|
|
} else {
|
|
QVERIFY(component.isError());
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::importsRemote_data()
|
|
{
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
|
|
QString serverdir = "{{ServerBaseUrl}}/qtest/qml/qqmllanguage";
|
|
|
|
QTest::newRow("remote import") << "import \""+serverdir+"\"\nTest {}" << "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("remote import with subdir") << "import \""+serverdir+"\"\nTestSubDir {}" << "QQuickText"
|
|
<< "";
|
|
QTest::newRow("remote import with local") << "import \""+serverdir+"\"\nTestLocal {}" << "QQuickImage"
|
|
<< "";
|
|
QTest::newRow("remote import with qualifier") << "import \""+serverdir+"\" as NS\nNS.NamedLocal {}" << "QQuickImage"
|
|
<< "";
|
|
QTest::newRow("wrong remote import with undeclared local") << "import \""+serverdir+"\"\nWrongTestLocal {}" << ""
|
|
<< "WrongTestLocal is not a type";
|
|
QTest::newRow("wrong remote import of internal local") << "import \""+serverdir+"\"\nLocalInternal {}" << ""
|
|
<< "LocalInternal is not a type";
|
|
QTest::newRow("wrong remote import of undeclared local") << "import \""+serverdir+"\"\nUndeclaredLocal {}" << ""
|
|
<< "UndeclaredLocal is not a type";
|
|
}
|
|
|
|
void tst_qqmllanguage::importsRemote()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
qml.replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString());
|
|
|
|
testType(qml,type,error);
|
|
}
|
|
|
|
void tst_qqmllanguage::importsInstalled_data()
|
|
{
|
|
// QT-610
|
|
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
|
|
// import installed
|
|
QTest::newRow("installed import 0")
|
|
<< "import org.qtproject.installedtest0 0.0\n"
|
|
"InstalledTestTP {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("installed import 0 as TP")
|
|
<< "import org.qtproject.installedtest0 0.0 as TP\n"
|
|
"TP.InstalledTestTP {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("installed import 1")
|
|
<< "import org.qtproject.installedtest 1.0\n"
|
|
"InstalledTest {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("installed import 2")
|
|
<< "import org.qtproject.installedtest 1.3\n"
|
|
"InstalledTest {}"
|
|
<< "QQuickRectangle"
|
|
<< "";
|
|
QTest::newRow("installed import 3")
|
|
<< "import org.qtproject.installedtest 1.4\n"
|
|
"InstalledTest {}"
|
|
<< "QQuickText"
|
|
<< "";
|
|
QTest::newRow("installed import minor version not available") // QTBUG-11936
|
|
<< "import org.qtproject.installedtest 0.1\n"
|
|
"InstalledTest {}"
|
|
<< ""
|
|
<< "module \"org.qtproject.installedtest\" version 0.1 is not installed";
|
|
QTest::newRow("installed import minor version not available") // QTBUG-9627
|
|
<< "import org.qtproject.installedtest 1.10\n"
|
|
"InstalledTest {}"
|
|
<< ""
|
|
<< "module \"org.qtproject.installedtest\" version 1.10 is not installed";
|
|
QTest::newRow("installed import major version not available") // QTBUG-9627
|
|
<< "import org.qtproject.installedtest 9.0\n"
|
|
"InstalledTest {}"
|
|
<< ""
|
|
<< "module \"org.qtproject.installedtest\" version 9.0 is not installed";
|
|
QTest::newRow("installed import visibility") // QT-614
|
|
<< "import org.qtproject.installedtest 1.4\n"
|
|
"PrivateType {}"
|
|
<< ""
|
|
<< "PrivateType is not a type";
|
|
QTest::newRow("installed import version QML clash")
|
|
<< "import org.qtproject.installedtest1 1.0\n"
|
|
"Test {}"
|
|
<< ""
|
|
<< "\"Test\" version 1.0 is defined more than once in module \"org.qtproject.installedtest1\"";
|
|
QTest::newRow("installed import version JS clash")
|
|
<< "import org.qtproject.installedtest2 1.0\n"
|
|
"Test {}"
|
|
<< ""
|
|
<< "\"Test\" version 1.0 is defined more than once in module \"org.qtproject.installedtest2\"";
|
|
}
|
|
|
|
void tst_qqmllanguage::importsInstalled()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
testType(qml,type,error);
|
|
}
|
|
|
|
void tst_qqmllanguage::importsInstalledRemote_data()
|
|
{
|
|
// Repeat the tests for local installed data
|
|
importsInstalled_data();
|
|
}
|
|
|
|
void tst_qqmllanguage::importsInstalledRemote()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
QString serverdir = server.urlString("/lib/");
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << serverdir);
|
|
|
|
testType(qml,type,error);
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::importsPath_data()
|
|
{
|
|
QTest::addColumn<QStringList>("importPath");
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("value");
|
|
|
|
QTest::newRow("local takes priority normal")
|
|
<< (QStringList() << testFile("lib") << "{{ServerBaseUrl}}/lib2/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "foo";
|
|
|
|
QTest::newRow("local takes priority reversed")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib/" << testFile("lib2"))
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "bar";
|
|
|
|
QTest::newRow("earlier takes priority 1")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib/" << "{{ServerBaseUrl}}/lib2/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "foo";
|
|
|
|
QTest::newRow("earlier takes priority 2")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib2/" << "{{ServerBaseUrl}}/lib/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "bar";
|
|
|
|
QTest::newRow("major version takes priority over unversioned")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib/" << "{{ServerBaseUrl}}/lib3/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "baz";
|
|
|
|
QTest::newRow("major version takes priority over minor")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib4/" << "{{ServerBaseUrl}}/lib3/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "baz";
|
|
|
|
QTest::newRow("minor version takes priority over unversioned")
|
|
<< (QStringList() << "{{ServerBaseUrl}}/lib/" << "{{ServerBaseUrl}}/lib4/")
|
|
<< "import testModule 1.0\n"
|
|
"Test {}"
|
|
<< "qux";
|
|
}
|
|
|
|
void tst_qqmllanguage::importsPath()
|
|
{
|
|
QFETCH(QStringList, importPath);
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, value);
|
|
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
for (int i = 0; i < importPath.count(); ++i)
|
|
importPath[i].replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString());
|
|
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << importPath);
|
|
|
|
QQmlComponent component(&engine);
|
|
component.setData(qml.toUtf8(), testFileUrl("empty.qml"));
|
|
|
|
QTRY_VERIFY(component.isReady());
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("test").toString(), value);
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::importsOrder_data()
|
|
{
|
|
QTest::addColumn<QString>("qml");
|
|
QTest::addColumn<QString>("type");
|
|
QTest::addColumn<QString>("error");
|
|
QTest::addColumn<bool>("partialMatch");
|
|
|
|
QTest::newRow("double import") <<
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"InstalledTest {}"
|
|
<< (!qmlCheckTypes()?"QQuickText":"")
|
|
<< (!qmlCheckTypes()?"":"InstalledTest is ambiguous. Found in lib/org/qtproject/installedtest/ in version 1.4 and 1.4")
|
|
<< false;
|
|
QTest::newRow("installed import overrides 1") <<
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"InstalledTest {}"
|
|
<< (!qmlCheckTypes()?"QQuickText":"")
|
|
<< (!qmlCheckTypes()?"":"InstalledTest is ambiguous. Found in lib/org/qtproject/installedtest/ in version 1.4 and 1.0")
|
|
<< false;
|
|
QTest::newRow("installed import overrides 2") <<
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"InstalledTest {}"
|
|
<< (!qmlCheckTypes()?"QQuickRectangle":"")
|
|
<< (!qmlCheckTypes()?"":"InstalledTest is ambiguous. Found in lib/org/qtproject/installedtest/ in version 1.0 and 1.4")
|
|
<< false;
|
|
QTest::newRow("installed import re-overrides 1") <<
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"InstalledTest {}"
|
|
<< (!qmlCheckTypes()?"QQuickText":"")
|
|
<< (!qmlCheckTypes()?"":"InstalledTest is ambiguous. Found in lib/org/qtproject/installedtest/ in version 1.4 and 1.0")
|
|
<< false;
|
|
QTest::newRow("installed import re-overrides 2") <<
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"import org.qtproject.installedtest 1.4\n"
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"InstalledTest {}"
|
|
<< (!qmlCheckTypes()?"QQuickRectangle":"")
|
|
<< (!qmlCheckTypes()?"":"InstalledTest is ambiguous. Found in lib/org/qtproject/installedtest/ in version 1.0 and 1.4")
|
|
<< false;
|
|
QTest::newRow("installed import versus builtin 1") <<
|
|
"import org.qtproject.installedtest 1.5\n"
|
|
"import QtQuick 2.0\n"
|
|
"Rectangle {}"
|
|
<< (!qmlCheckTypes()?"QQuickRectangle":"")
|
|
<< (!qmlCheckTypes()?"":"Rectangle is ambiguous. Found in file://")
|
|
<< true;
|
|
QTest::newRow("installed import versus builtin 2") <<
|
|
"import QtQuick 2.0\n"
|
|
"import org.qtproject.installedtest 1.5\n"
|
|
"Rectangle {}"
|
|
<< (!qmlCheckTypes()?"QQuickText":"")
|
|
<< (!qmlCheckTypes()?"":"Rectangle is ambiguous. Found in lib/org/qtproject/installedtest/ and in file://")
|
|
<< true;
|
|
QTest::newRow("namespaces cannot be overridden by types 1") <<
|
|
"import QtQuick 2.0 as Rectangle\n"
|
|
"import org.qtproject.installedtest 1.5\n"
|
|
"Rectangle {}"
|
|
<< ""
|
|
<< "Namespace Rectangle cannot be used as a type"
|
|
<< false;
|
|
QTest::newRow("namespaces cannot be overridden by types 2") <<
|
|
"import QtQuick 2.0 as Rectangle\n"
|
|
"import org.qtproject.installedtest 1.5\n"
|
|
"Rectangle.Image {}"
|
|
<< "QQuickImage"
|
|
<< ""
|
|
<< false;
|
|
QTest::newRow("local last 1") <<
|
|
"LocalLast {}"
|
|
<< "QQuickText"
|
|
<< ""
|
|
<< false;
|
|
QTest::newRow("local last 2") <<
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"LocalLast {}"
|
|
<< (!qmlCheckTypes()?"QQuickRectangle":"")// i.e. from org.qtproject.installedtest, not data/LocalLast.qml
|
|
<< (!qmlCheckTypes()?"":"LocalLast is ambiguous. Found in lib/org/qtproject/installedtest/ and in ")
|
|
<< false;
|
|
QTest::newRow("local last 3") << //Forces it to load the local qmldir to resolve types, but they shouldn't override anything
|
|
"import org.qtproject.installedtest 1.0\n"
|
|
"LocalLast {LocalLast2{}}"
|
|
<< (!qmlCheckTypes()?"QQuickRectangle":"")// i.e. from org.qtproject.installedtest, not data/LocalLast.qml
|
|
<< (!qmlCheckTypes()?"":"LocalLast is ambiguous. Found in lib/org/qtproject/installedtest/ and in ")
|
|
<< false;
|
|
}
|
|
|
|
void tst_qqmllanguage::importsOrder()
|
|
{
|
|
QFETCH(QString, qml);
|
|
QFETCH(QString, type);
|
|
QFETCH(QString, error);
|
|
QFETCH(bool, partialMatch);
|
|
testType(qml,type,error,partialMatch);
|
|
}
|
|
|
|
void tst_qqmllanguage::importIncorrectCase()
|
|
{
|
|
if (engine.importPathList() == defaultImportPathList)
|
|
engine.addImportPath(testFile("lib"));
|
|
|
|
// Load "importIncorrectCase.qml" using wrong case
|
|
QQmlComponent component(&engine, testFileUrl("ImportIncorrectCase.qml"));
|
|
|
|
QList<QQmlError> errors = component.errors();
|
|
QCOMPARE(errors.count(), 1);
|
|
|
|
const QString expectedError = isCaseSensitiveFileSystem(dataDirectory()) ?
|
|
QStringLiteral("No such file or directory") :
|
|
QStringLiteral("File name case mismatch");
|
|
QCOMPARE(errors.at(0).description(), expectedError);
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::importJs_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::addColumn<QString>("errorFile");
|
|
QTest::addColumn<bool>("performTest");
|
|
|
|
QTest::newRow("defaultVersion")
|
|
<< "importJs.1.qml"
|
|
<< "importJs.1.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("specifiedVersion")
|
|
<< "importJs.2.qml"
|
|
<< "importJs.2.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("excludeExcessiveVersion")
|
|
<< "importJs.3.qml"
|
|
<< "importJs.3.errors.txt"
|
|
<< false;
|
|
|
|
QTest::newRow("includeAppropriateVersion")
|
|
<< "importJs.4.qml"
|
|
<< "importJs.4.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("noDefaultVersion")
|
|
<< "importJs.5.qml"
|
|
<< "importJs.5.errors.txt"
|
|
<< false;
|
|
|
|
QTest::newRow("repeatImportFails")
|
|
<< "importJs.6.qml"
|
|
<< "importJs.6.errors.txt"
|
|
<< false;
|
|
|
|
QTest::newRow("multipleVersionImportFails")
|
|
<< "importJs.7.qml"
|
|
<< "importJs.7.errors.txt"
|
|
<< false;
|
|
|
|
QTest::newRow("namespacedImport")
|
|
<< "importJs.8.qml"
|
|
<< "importJs.8.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("namespacedVersionedImport")
|
|
<< "importJs.9.qml"
|
|
<< "importJs.9.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("namespacedRepeatImport")
|
|
<< "importJs.10.qml"
|
|
<< "importJs.10.errors.txt"
|
|
<< true;
|
|
|
|
QTest::newRow("emptyScript")
|
|
<< "importJs.11.qml"
|
|
<< "importJs.11.errors.txt"
|
|
<< true;
|
|
}
|
|
|
|
void tst_qqmllanguage::importJs()
|
|
{
|
|
QFETCH(QString, file);
|
|
QFETCH(QString, errorFile);
|
|
QFETCH(bool, performTest);
|
|
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << testFile("lib"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
|
|
{
|
|
DETERMINE_ERRORS(errorFile,expected,actual);
|
|
QCOMPARE(expected.size(), actual.size());
|
|
for (int i = 0; i < expected.size(); ++i)
|
|
{
|
|
const int compareLen = qMin(expected.at(i).length(), actual.at(i).length());
|
|
QCOMPARE(expected.at(i).left(compareLen), actual.at(i).left(compareLen));
|
|
}
|
|
}
|
|
|
|
if (performTest) {
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("test").toBool(),true);
|
|
}
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::importJsModule_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
|
|
QTest::newRow("plainImport")
|
|
<< "importJsModule.1.qml";
|
|
|
|
QTest::newRow("ImportQmlStyle")
|
|
<< "importJsModule.2.qml";
|
|
|
|
QTest::newRow("plainImportWithCycle")
|
|
<< "importJsModule.3.qml";
|
|
}
|
|
|
|
void tst_qqmllanguage::importJsModule()
|
|
{
|
|
QFETCH(QString, file);
|
|
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << testFile("lib"));
|
|
auto importPathGuard = qScopeGuard([this]{
|
|
engine.setImportPathList(defaultImportPathList);
|
|
});
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("test").toBool(),true);
|
|
}
|
|
|
|
void tst_qqmllanguage::explicitSelfImport()
|
|
{
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << testFile("lib"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl("mixedModuleWithSelfImport.qml"));
|
|
QVERIFY(component.errors().count() == 0);
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::importInternalType()
|
|
{
|
|
QQmlEngine engine;
|
|
engine.addImportPath(dataDirectory());
|
|
|
|
{
|
|
QQmlComponent component(&engine);
|
|
component.setData("import modulewithinternaltypes 1.0\nPublicType{}", QUrl());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(!obj.isNull());
|
|
QVERIFY(obj->property("myInternalType").value<QObject*>() != 0);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine);
|
|
component.setData("import modulewithinternaltypes 1.0\nPublicTypeWithExplicitImport{}", QUrl());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(!obj.isNull());
|
|
QVERIFY(obj->property("myInternalType").value<QObject*>() != 0);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::qmlAttachedPropertiesObjectMethod()
|
|
{
|
|
QObject object;
|
|
|
|
QCOMPARE(qmlAttachedPropertiesObject<MyQmlObject>(&object, false), (QObject *)nullptr);
|
|
QVERIFY(qmlAttachedPropertiesObject<MyQmlObject>(&object, true));
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("qmlAttachedPropertiesObjectMethod.1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(qmlAttachedPropertiesObject<MyQmlObject>(object.data(), false), (QObject *)nullptr);
|
|
QVERIFY(qmlAttachedPropertiesObject<MyQmlObject>(object.data(), true) != nullptr);
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("qmlAttachedPropertiesObjectMethod.2.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QVERIFY(qmlAttachedPropertiesObject<MyQmlObject>(object.data(), false) != nullptr);
|
|
QVERIFY(qmlAttachedPropertiesObject<MyQmlObject>(object.data(), true) != nullptr);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::crash1()
|
|
{
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQuick 2.0\nComponent {}", QUrl());
|
|
}
|
|
|
|
void tst_qqmllanguage::crash2()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("crash2.qml"));
|
|
}
|
|
|
|
// QTBUG-8676
|
|
void tst_qqmllanguage::customOnProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customOnProperty.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("on").toInt(), 10);
|
|
}
|
|
|
|
// QTBUG-12601
|
|
void tst_qqmllanguage::variantNotify()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("variantNotify.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("notifyCount").toInt(), 1);
|
|
}
|
|
|
|
void tst_qqmllanguage::revisions()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("revisions11.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyRevisionedClass> object(qobject_cast<MyRevisionedClass*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->prop2(), 10.0);
|
|
}
|
|
{
|
|
QQmlEngine myEngine;
|
|
QQmlComponent component(&myEngine, testFileUrl("revisionssub11.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyRevisionedSubclass> object(qobject_cast<MyRevisionedSubclass*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->prop1(), 10.0);
|
|
QCOMPARE(object->prop2(), 10.0);
|
|
QCOMPARE(object->prop3(), 10.0);
|
|
QCOMPARE(object->prop4(), 10.0);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("versionedbase.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MySubclass> object(qobject_cast<MySubclass*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->prop1(), 10.0);
|
|
QCOMPARE(object->prop2(), 10.0);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::revisionOverloads()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("allowedRevisionOverloads.qml"));
|
|
VERIFY_ERRORS(0);
|
|
}
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("disallowedRevisionOverloads.qml"));
|
|
QEXPECT_FAIL("", "QTBUG-13849", Abort);
|
|
QVERIFY(0);
|
|
VERIFY_ERRORS("disallowedRevisionOverloads.errors.txt");
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::subclassedUncreateableRevision_data()
|
|
{
|
|
QTest::addColumn<QString>("version");
|
|
QTest::addColumn<QString>("prop");
|
|
QTest::addColumn<bool>("shouldWork");
|
|
|
|
QTest::newRow("prop1 exists in 1.0") << "1.0" << "prop1" << true;
|
|
QTest::newRow("prop2 does not exist in 1.0") << "1.0" << "prop2" << false;
|
|
QTest::newRow("prop3 does not exist in 1.0") << "1.0" << "prop3" << false;
|
|
|
|
QTest::newRow("prop1 exists in 1.1") << "1.1" << "prop1" << true;
|
|
QTest::newRow("prop2 works because it's re-declared in Derived") << "1.1" << "prop2" << true;
|
|
QTest::newRow("prop3 only works if the Base REVISION 1 is picked up") << "1.1" << "prop3" << true;
|
|
|
|
}
|
|
|
|
void tst_qqmllanguage::subclassedUncreateableRevision()
|
|
{
|
|
QFETCH(QString, version);
|
|
QFETCH(QString, prop);
|
|
QFETCH(bool, shouldWork);
|
|
|
|
{
|
|
QQmlEngine engine;
|
|
QString qml = QString("import QtQuick 2.0\nimport Test %1\nMyUncreateableBaseClass {}").arg(version);
|
|
QQmlComponent c(&engine);
|
|
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
|
|
QScopedPointer<QObject> obj(c.create());
|
|
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
|
|
QCOMPARE(c.errors().count(), 1);
|
|
QCOMPARE(c.errors().first().description(), QString("Cannot create MyUncreateableBaseClass"));
|
|
}
|
|
|
|
QQmlEngine engine;
|
|
QString qml = QString("import QtQuick 2.0\nimport Test %1\nMyCreateableDerivedClass {\n%3: true\n}").arg(version).arg(prop);
|
|
QQmlComponent c(&engine);
|
|
if (!shouldWork)
|
|
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
|
|
QScopedPointer<QObject> obj(c.create());
|
|
if (!shouldWork) {
|
|
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
|
|
return;
|
|
}
|
|
|
|
QVERIFY(obj);
|
|
MyUncreateableBaseClass *base = qobject_cast<MyUncreateableBaseClass*>(obj.data());
|
|
QVERIFY(base);
|
|
QCOMPARE(base->property(prop.toLatin1()).toBool(), true);
|
|
}
|
|
|
|
void tst_qqmllanguage::subclassedExtendedUncreateableRevision_data()
|
|
{
|
|
QTest::addColumn<QString>("version");
|
|
QTest::addColumn<QString>("prop");
|
|
QTest::addColumn<bool>("shouldWork");
|
|
|
|
QTest::newRow("prop1 exists in 1.0") << "1.0" << "prop1" << true;
|
|
QTest::newRow("prop2 does not exist in 1.0") << "1.0" << "prop2" << false;
|
|
QTest::newRow("prop3 does not exist in 1.0") << "1.0" << "prop3" << false;
|
|
QTest::newRow("prop4 exists in 1.0") << "1.0" << "prop4" << true;
|
|
QTest::newRow("prop5 exists in 1.0") << "1.0" << "prop5" << true;
|
|
|
|
QTest::newRow("prop1 exists in 1.1") << "1.1" << "prop1" << true;
|
|
QTest::newRow("prop2 exists in 1.1") << "1.1" << "prop2" << true;
|
|
QTest::newRow("prop3 exists in 1.1") << "1.1" << "prop3" << true;
|
|
QTest::newRow("prop4 exists in 1.1") << "1.1" << "prop4" << true;
|
|
QTest::newRow("prop5 exists in 1.1") << "1.1" << "prop5" << true;
|
|
}
|
|
|
|
void tst_qqmllanguage::subclassedExtendedUncreateableRevision()
|
|
{
|
|
QFETCH(QString, version);
|
|
QFETCH(QString, prop);
|
|
QFETCH(bool, shouldWork);
|
|
|
|
{
|
|
QQmlEngine engine;
|
|
QString qml = QString("import QtQuick 2.0\nimport Test %1\nMyExtendedUncreateableBaseClass {}").arg(version);
|
|
QQmlComponent c(&engine);
|
|
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
|
|
QScopedPointer<QObject> obj(c.create());
|
|
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
|
|
QCOMPARE(c.errors().count(), 1);
|
|
QCOMPARE(c.errors().first().description(), QString("Cannot create MyExtendedUncreateableBaseClass"));
|
|
}
|
|
|
|
QQmlEngine engine;
|
|
QString qml = QString("import QtQuick 2.0\nimport Test %1\nMyExtendedCreateableDerivedClass {\n%3: true\n}").arg(version).arg(prop);
|
|
QQmlComponent c(&engine);
|
|
if (!shouldWork)
|
|
QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
|
|
QScopedPointer<QObject> obj(c.create());
|
|
if (!shouldWork) {
|
|
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
|
|
return;
|
|
}
|
|
|
|
QVERIFY(obj);
|
|
MyExtendedUncreateableBaseClass *base = qobject_cast<MyExtendedUncreateableBaseClass*>(obj.data());
|
|
QVERIFY(base);
|
|
QCOMPARE(base->property(prop.toLatin1()).toBool(), true);
|
|
}
|
|
|
|
void tst_qqmllanguage::uncreatableTypesAsProperties()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("uncreatableTypeAsProperty.qml"));
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::initTestCase()
|
|
{
|
|
QQmlDataTest::initTestCase();
|
|
if (dataDirectoryUrl().scheme() == QLatin1String("qrc"))
|
|
engine.addImportPath(dataDirectory());
|
|
else
|
|
QVERIFY2(QDir::setCurrent(dataDirectory()), qPrintable("Could not chdir to " + dataDirectory()));
|
|
|
|
|
|
defaultImportPathList = engine.importPathList();
|
|
|
|
QQmlMetaType::registerCustomStringConverter(qMetaTypeId<MyCustomVariantType>(), myCustomVariantTypeConverter);
|
|
|
|
registerTypes();
|
|
// Registered here because it uses testFileUrl
|
|
qmlRegisterType(testFileUrl("CompositeType.qml"), "Test", 1, 0, "RegisteredCompositeType");
|
|
qmlRegisterType(testFileUrl("CompositeType.DoesNotExist.qml"), "Test", 1, 0, "RegisteredCompositeType2");
|
|
qmlRegisterType(testFileUrl("invalidRoot.1.qml"), "Test", 1, 0, "RegisteredCompositeType3");
|
|
qmlRegisterType(testFileUrl("CompositeTypeWithEnum.qml"), "Test", 1, 0, "RegisteredCompositeTypeWithEnum");
|
|
qmlRegisterType(testFileUrl("CompositeTypeWithAttachedProperty.qml"), "Test", 1, 0, "RegisteredCompositeTypeWithAttachedProperty");
|
|
|
|
// Registering the TestType class in other modules should have no adverse effects
|
|
qmlRegisterType<TestType>("org.qtproject.TestPre", 1, 0, "Test");
|
|
|
|
qmlRegisterType<TestType>("org.qtproject.Test", 0, 0, "TestTP");
|
|
qmlRegisterType<TestType>("org.qtproject.Test", 1, 0, "Test");
|
|
qmlRegisterType<TestType>("org.qtproject.Test", 1, 5, "Test");
|
|
qmlRegisterType<TestType2>("org.qtproject.Test", 1, 8, "Test");
|
|
qmlRegisterType<TestType>("org.qtproject.Test", 1, 9, "OldTest");
|
|
qmlRegisterType<TestType2>("org.qtproject.Test", 1, 12, "Test");
|
|
|
|
// Registering the TestType class in other modules should have no adverse effects
|
|
qmlRegisterType<TestType>("org.qtproject.TestPost", 1, 0, "Test");
|
|
|
|
// Create locale-specific file
|
|
// For POSIX, this will just be data/I18nType.qml, since POSIX is 7-bit
|
|
// For iso8859-1 locale, this will just be data/I18nType?????.qml where ????? is 5 8-bit characters
|
|
// For utf-8 locale, this will be data/I18nType??????????.qml where ?????????? is 5 8-bit characters, UTF-8 encoded
|
|
if (dataDirectoryUrl().scheme() != QLatin1String("qrc")) {
|
|
QFile in(testFileUrl(QLatin1String("I18nType30.qml")).toLocalFile());
|
|
QVERIFY2(in.open(QIODevice::ReadOnly), qPrintable(QString::fromLatin1("Cannot open '%1': %2").arg(in.fileName(), in.errorString())));
|
|
QFile out(testFileUrl(QString::fromUtf8("I18nType\303\201\303\242\303\243\303\244\303\245.qml")).toLocalFile());
|
|
QVERIFY2(out.open(QIODevice::WriteOnly), qPrintable(QString::fromLatin1("Cannot open '%1': %2").arg(out.fileName(), out.errorString())));
|
|
out.write(in.readAll());
|
|
}
|
|
|
|
// Register a Composite Singleton.
|
|
qmlRegisterSingletonType(testFileUrl("singleton/RegisteredCompositeSingletonType.qml"), "org.qtproject.Test", 1, 0, "RegisteredSingleton");
|
|
}
|
|
|
|
void tst_qqmllanguage::aliasPropertyChangeSignals()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("aliasPropertyChangeSignals.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("test").toBool(), true);
|
|
}
|
|
|
|
// QTCREATORBUG-2769
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("aliasPropertyChangeSignals.2.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("test").toBool(), true);
|
|
}
|
|
}
|
|
|
|
// Tests property initializers
|
|
void tst_qqmllanguage::propertyInit()
|
|
{
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("propertyInit.1.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("test").toInt(), 1);
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("propertyInit.2.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("test").toInt(), 123);
|
|
}
|
|
}
|
|
|
|
// Test that registration order doesn't break type availability
|
|
// QTBUG-16878
|
|
void tst_qqmllanguage::registrationOrder()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("registrationOrder.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
QCOMPARE(o->metaObject(), &MyVersion2Class::staticMetaObject);
|
|
}
|
|
|
|
void tst_qqmllanguage::readonly()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("readonly.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("test1").toInt(), 10);
|
|
QCOMPARE(o->property("test2").toInt(), 18);
|
|
QCOMPARE(o->property("test3").toInt(), 13);
|
|
|
|
o->setProperty("testData", 13);
|
|
|
|
QCOMPARE(o->property("test1").toInt(), 10);
|
|
QCOMPARE(o->property("test2").toInt(), 22);
|
|
QCOMPARE(o->property("test3").toInt(), 13);
|
|
|
|
o->setProperty("testData2", 2);
|
|
|
|
QCOMPARE(o->property("test1").toInt(), 10);
|
|
QCOMPARE(o->property("test2").toInt(), 22);
|
|
QCOMPARE(o->property("test3").toInt(), 2);
|
|
|
|
o->setProperty("test1", 11);
|
|
o->setProperty("test2", 11);
|
|
o->setProperty("test3", 11);
|
|
|
|
QCOMPARE(o->property("test1").toInt(), 10);
|
|
QCOMPARE(o->property("test2").toInt(), 22);
|
|
QCOMPARE(o->property("test3").toInt(), 2);
|
|
}
|
|
|
|
void tst_qqmllanguage::readonlyObjectProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("readonlyObjectProperty.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
QQmlProperty prop(o.data(), QStringLiteral("subObject"), &engine);
|
|
QVERIFY(!prop.isWritable());
|
|
QVERIFY(!prop.write(QVariant::fromValue(o.data())));
|
|
|
|
QObject *subObject = qvariant_cast<QObject*>(prop.read());
|
|
QVERIFY(subObject);
|
|
QCOMPARE(subObject->property("readWrite").toInt(), int(42));
|
|
subObject->setProperty("readWrite", QVariant::fromValue(int(100)));
|
|
QCOMPARE(subObject->property("readWrite").toInt(), int(100));
|
|
}
|
|
|
|
void tst_qqmllanguage::receivers()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("receivers.qml"));
|
|
|
|
QScopedPointer<MyReceiversTestObject> o(qobject_cast<MyReceiversTestObject*>(component.create()));
|
|
QVERIFY(o != nullptr);
|
|
QCOMPARE(o->mySignalCount(), 1);
|
|
QCOMPARE(o->propChangedCount(), 2);
|
|
QCOMPARE(o->myUnconnectedSignalCount(), 0);
|
|
|
|
QVERIFY(o->isSignalConnected(QMetaMethod::fromSignal(&MyReceiversTestObject::mySignal)));
|
|
QVERIFY(o->isSignalConnected(QMetaMethod::fromSignal(&MyReceiversTestObject::propChanged)));
|
|
QVERIFY(!o->isSignalConnected(QMetaMethod::fromSignal(&MyReceiversTestObject::myUnconnectedSignal)));
|
|
}
|
|
|
|
void tst_qqmllanguage::registeredCompositeType()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("registeredCompositeType.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
}
|
|
|
|
// QTBUG-43582
|
|
void tst_qqmllanguage::registeredCompositeTypeWithEnum()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("registeredCompositeTypeWithEnum.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("enumValue0").toInt(), static_cast<int>(MyCompositeBaseType::EnumValue0));
|
|
QCOMPARE(o->property("enumValue42").toInt(), static_cast<int>(MyCompositeBaseType::EnumValue42));
|
|
QCOMPARE(o->property("enumValue15").toInt(), static_cast<int>(MyCompositeBaseType::ScopedCompositeEnum::EnumValue15));
|
|
}
|
|
|
|
// QTBUG-43581
|
|
void tst_qqmllanguage::registeredCompositeTypeWithAttachedProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("registeredCompositeTypeWithAttachedProperty.qml"));
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->property("attachedProperty").toString(), QStringLiteral("test"));
|
|
}
|
|
|
|
// QTBUG-18268
|
|
void tst_qqmllanguage::remoteLoadCrash()
|
|
{
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQuick 2.0; Text {}", server.url("/remoteLoadCrash.qml"));
|
|
while (component.isLoading())
|
|
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents, 50);
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
}
|
|
|
|
void tst_qqmllanguage::signalWithDefaultArg()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("signalWithDefaultArg.qml"));
|
|
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
|
|
QCOMPARE(object->property("signalCount").toInt(), 0);
|
|
QCOMPARE(object->property("signalArg").toInt(), 0);
|
|
|
|
emit object->signalWithDefaultArg();
|
|
QCOMPARE(object-> property("signalCount").toInt(), 1);
|
|
QCOMPARE(object->property("signalArg").toInt(), 5);
|
|
|
|
emit object->signalWithDefaultArg(15);
|
|
QCOMPARE(object->property("signalCount").toInt(), 2);
|
|
QCOMPARE(object->property("signalArg").toInt(), 15);
|
|
|
|
|
|
QMetaObject::invokeMethod(object.data(), "emitNoArgSignal");
|
|
QCOMPARE(object->property("signalCount").toInt(), 3);
|
|
QCOMPARE(object->property("signalArg").toInt(), 5);
|
|
|
|
QMetaObject::invokeMethod(object.data(), "emitArgSignal");
|
|
QCOMPARE(object->property("signalCount").toInt(), 4);
|
|
QCOMPARE(object->property("signalArg").toInt(), 22);
|
|
}
|
|
|
|
void tst_qqmllanguage::signalParameterTypes()
|
|
{
|
|
// bound signal handlers
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("signalParameterTypes.1.qml"));
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(obj != nullptr);
|
|
QVERIFY(obj->property("success").toBool());
|
|
}
|
|
|
|
// dynamic signal connections
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("signalParameterTypes.2.qml"));
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(obj != nullptr);
|
|
QVERIFY(obj->property("success").toBool());
|
|
}
|
|
|
|
// dynamic signal connections
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("signalParameterTypes.3.qml"));
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(obj != nullptr);
|
|
QVERIFY(obj->property("success").toBool());
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::functionParameterTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("functionParameterTypes.qml"));
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY2(!obj.isNull(), qPrintable(component.errorString()));
|
|
const QMetaObject *metaObject = obj->metaObject();
|
|
|
|
{
|
|
QMetaMethod slot = metaObject->method(metaObject->indexOfSlot("returnItem()"));
|
|
QVERIFY(slot.isValid());
|
|
QCOMPARE(slot.returnType(), QMetaType::type("QObject*"));
|
|
QObject *returnedPtr = nullptr;
|
|
slot.invoke(obj.data(), Qt::DirectConnection, Q_RETURN_ARG(QObject*, returnedPtr));
|
|
QCOMPARE(returnedPtr, obj.data());
|
|
}
|
|
|
|
{
|
|
QMetaMethod slot = metaObject->method(metaObject->indexOfSlot("takeString(QString)"));
|
|
QVERIFY(slot.isValid());
|
|
QCOMPARE(slot.parameterCount(), 1);
|
|
QCOMPARE(slot.parameterType(0), int(QMetaType::QString));
|
|
}
|
|
}
|
|
|
|
// QTBUG-20639
|
|
void tst_qqmllanguage::globalEnums()
|
|
{
|
|
qRegisterMetaType<MyEnum1Class::EnumA>();
|
|
qRegisterMetaType<MyEnum2Class::EnumB>();
|
|
qRegisterMetaType<Qt::TextFormat>();
|
|
|
|
QQmlComponent component(&engine, testFileUrl("globalEnums.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
MyEnum1Class *enum1Class = o->findChild<MyEnum1Class *>(QString::fromLatin1("enum1Class"));
|
|
QVERIFY(enum1Class != nullptr);
|
|
QVERIFY(enum1Class->getValue() == -1);
|
|
|
|
MyEnumDerivedClass *enum2Class = o->findChild<MyEnumDerivedClass *>(QString::fromLatin1("enumDerivedClass"));
|
|
QVERIFY(enum2Class != nullptr);
|
|
QVERIFY(enum2Class->getValueA() == -1);
|
|
QVERIFY(enum2Class->getValueB() == -1);
|
|
QVERIFY(enum2Class->getValueC() == 0);
|
|
QVERIFY(enum2Class->getValueD() == 0);
|
|
QVERIFY(enum2Class->getValueE() == -1);
|
|
QVERIFY(enum2Class->getValueE2() == -1);
|
|
|
|
QVERIFY(enum2Class->property("aValue") == 0);
|
|
QVERIFY(enum2Class->property("bValue") == 0);
|
|
QVERIFY(enum2Class->property("cValue") == 0);
|
|
QVERIFY(enum2Class->property("dValue") == 0);
|
|
QVERIFY(enum2Class->property("eValue") == 0);
|
|
QVERIFY(enum2Class->property("e2Value") == 0);
|
|
|
|
QSignalSpy signalA(enum2Class, SIGNAL(valueAChanged(MyEnum1Class::EnumA)));
|
|
QSignalSpy signalB(enum2Class, SIGNAL(valueBChanged(MyEnum2Class::EnumB)));
|
|
|
|
QMetaObject::invokeMethod(o.data(), "setEnumValues");
|
|
|
|
QVERIFY(enum1Class->getValue() == MyEnum1Class::A_13);
|
|
QVERIFY(enum2Class->getValueA() == MyEnum1Class::A_11);
|
|
QVERIFY(enum2Class->getValueB() == MyEnum2Class::B_37);
|
|
QVERIFY(enum2Class->getValueC() == Qt::RichText);
|
|
QVERIFY(enum2Class->getValueD() == Qt::ElideMiddle);
|
|
QVERIFY(enum2Class->getValueE() == MyEnum2Class::E_14);
|
|
QVERIFY(enum2Class->getValueE2() == MyEnum2Class::E_76);
|
|
|
|
QVERIFY(signalA.count() == 1);
|
|
QVERIFY(signalB.count() == 1);
|
|
|
|
QVERIFY(enum2Class->property("aValue") == MyEnum1Class::A_11);
|
|
QVERIFY(enum2Class->property("bValue") == 37);
|
|
QVERIFY(enum2Class->property("cValue") == 1);
|
|
QVERIFY(enum2Class->property("dValue") == 2);
|
|
QVERIFY(enum2Class->property("eValue") == 14);
|
|
QVERIFY(enum2Class->property("e2Value") == 76);
|
|
}
|
|
|
|
void tst_qqmllanguage::lowercaseEnumRuntime_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
|
|
QTest::newRow("enum from normal type") << "lowercaseEnumRuntime.1.qml";
|
|
QTest::newRow("enum from singleton type") << "lowercaseEnumRuntime.2.qml";
|
|
}
|
|
|
|
void tst_qqmllanguage::lowercaseEnumRuntime()
|
|
{
|
|
QFETCH(QString, file);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
VERIFY_ERRORS(0);
|
|
delete component.create();
|
|
}
|
|
|
|
void tst_qqmllanguage::lowercaseEnumCompileTime_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
|
|
QTest::newRow("assignment to int property") << "lowercaseEnumCompileTime.1.qml";
|
|
QTest::newRow("assignment to enum property") << "lowercaseEnumCompileTime.2.qml";
|
|
}
|
|
|
|
void tst_qqmllanguage::lowercaseEnumCompileTime()
|
|
{
|
|
QFETCH(QString, file);
|
|
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
VERIFY_ERRORS(0);
|
|
delete component.create();
|
|
}
|
|
|
|
void tst_qqmllanguage::scopedEnum()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scopedEnum.qml"));
|
|
|
|
QScopedPointer<MyTypeObject> o(qobject_cast<MyTypeObject *>(component.create()));
|
|
QVERIFY(o != nullptr);
|
|
|
|
QCOMPARE(o->scopedEnum(), MyTypeObject::MyScopedEnum::ScopedVal1);
|
|
QCOMPARE(o->intProperty(), (int)MyTypeObject::MyScopedEnum::ScopedVal2);
|
|
QCOMPARE(o->property("listValue").toInt(), (int)MyTypeObject::MyScopedEnum::ScopedVal3);
|
|
QCOMPARE(o->property("noScope").toInt(), (int)MyTypeObject::MyScopedEnum::ScopedVal1);
|
|
|
|
QMetaObject::invokeMethod(o.data(), "assignNewValue");
|
|
QCOMPARE(o->scopedEnum(), MyTypeObject::MyScopedEnum::ScopedVal2);
|
|
QCOMPARE(o->property("noScope").toInt(), (int)MyTypeObject::MyScopedEnum::ScopedVal2);
|
|
}
|
|
|
|
void tst_qqmllanguage::scopedEnumsWithNameClash()
|
|
{
|
|
auto typeId = qmlRegisterUncreatableType<ScopedEnumsWithNameClash>("ScopedEnumsWithNameClashTest", 1, 0, "ScopedEnum", "Dummy reason");
|
|
auto registryGuard = qScopeGuard([typeId]() {
|
|
QQmlMetaType::unregisterType(typeId);
|
|
});
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("scopedEnumsWithNameClash.qml"));
|
|
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Previously registered enum will be overwritten due to name clash: ScopedEnumsWithNameClash.ScopedVal1");
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Previously registered enum will be overwritten due to name clash: ScopedEnumsWithNameClash.ScopedVal2");
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Previously registered enum will be overwritten due to name clash: ScopedEnumsWithNameClash.ScopedVal3");
|
|
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(obj != nullptr);
|
|
QVERIFY(obj->property("success").toBool());
|
|
}
|
|
|
|
void tst_qqmllanguage::scopedEnumsWithResolvedNameClash()
|
|
{
|
|
auto typeId = qmlRegisterUncreatableType<ScopedEnumsWithResolvedNameClash>("ScopedEnumsWithResolvedNameClashTest", 1, 0, "ScopedEnum", "Dummy reason");
|
|
auto registryGuard = qScopeGuard([typeId]() {
|
|
QQmlMetaType::unregisterType(typeId);
|
|
});
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("scopedEnumsWithResolvedNameClash.qml"));
|
|
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(obj != nullptr);
|
|
QVERIFY(obj->property("success").toBool());
|
|
}
|
|
|
|
class ObjectA : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
|
|
enum TestEnum {
|
|
Default = 42,
|
|
TestValue1,
|
|
TestValue2
|
|
};
|
|
Q_ENUM(TestEnum)
|
|
|
|
ObjectA() = default;
|
|
};
|
|
|
|
class ObjectB : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_PROPERTY(ObjectA::TestEnum testEnum READ testEnum WRITE setTestEnum NOTIFY testEnumChanged)
|
|
|
|
public:
|
|
ObjectB() = default;
|
|
ObjectA::TestEnum testEnum() const {return m_testEnum;}
|
|
|
|
public slots:
|
|
void setTestEnum(ObjectA::TestEnum testEnum) {auto old = m_testEnum; m_testEnum = testEnum; if (old != m_testEnum) testEnumChanged(m_testEnum);}
|
|
signals:
|
|
void testEnumChanged(ObjectA::TestEnum testEnum);
|
|
private:
|
|
ObjectA::TestEnum m_testEnum = ObjectA::Default;
|
|
};
|
|
|
|
class ObjectC : public ObjectB
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
ObjectC() = default;
|
|
};
|
|
|
|
void tst_qqmllanguage::enumNoScopeLeak()
|
|
{
|
|
qmlRegisterType<ObjectA>("test", 1, 0, "ObjectA");
|
|
qmlRegisterType<ObjectB>("test", 1, 0, "ObjectB");
|
|
qmlRegisterType<ObjectC>("test", 1, 0, "ObjectC");
|
|
QQmlEngine engine;
|
|
auto url = testFileUrl("enumScopeLeak.qml");
|
|
QQmlComponent component(&engine, url);
|
|
const auto msg = url.toString() + ":5:5: Unable to assign [undefined] to ObjectA::TestEnum";
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, msg.toUtf8().constData());
|
|
QScopedPointer<QObject> root(component.create());
|
|
QVERIFY(root);
|
|
QCOMPARE(root->property("testEnum").toInt(), ObjectA::Default);
|
|
}
|
|
|
|
void tst_qqmllanguage::qmlEnums()
|
|
{
|
|
QQmlEngine engine;
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << testFile("lib"));
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("TypeWithEnum.qml"));
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o);
|
|
QCOMPARE(o->property("enumValue").toInt(), 1);
|
|
QCOMPARE(o->property("enumValue2").toInt(), 2);
|
|
QCOMPARE(o->property("scopedEnumValue").toInt(), 1);
|
|
|
|
QCOMPARE(o->property("otherEnumValue1").toInt(), 24);
|
|
QCOMPARE(o->property("otherEnumValue2").toInt(), 25);
|
|
QCOMPARE(o->property("otherEnumValue3").toInt(), 24);
|
|
QCOMPARE(o->property("otherEnumValue4").toInt(), 25);
|
|
QCOMPARE(o->property("otherEnumValue5").toInt(), 1);
|
|
}
|
|
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("usingTypeWithEnum.qml"));
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o);
|
|
QCOMPARE(o->property("enumValue").toInt(), 1);
|
|
QCOMPARE(o->property("enumValue2").toInt(), 0);
|
|
QCOMPARE(o->property("scopedEnumValue").toInt(), 2);
|
|
QCOMPARE(o->property("enumValueFromSingleton").toInt(), 42);
|
|
// while this next test verifies current duplication behavior, I'm not sure it should be codified
|
|
QCOMPARE(o->property("duplicatedEnumValueFromSingleton").toInt(), 2);
|
|
QCOMPARE(o->property("scopedEnumValueFromSingleton1").toInt(), 43);
|
|
QCOMPARE(o->property("scopedEnumValueFromSingleton2").toInt(), 2);
|
|
QCOMPARE(o->property("scopedEnumValueFromSingleton3").toInt(), 2);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::literals_data()
|
|
{
|
|
QTest::addColumn<QString>("property");
|
|
QTest::addColumn<QVariant>("value");
|
|
|
|
QTest::newRow("hex") << "n1" << QVariant(0xfe32);
|
|
// Octal integer literals are deprecated
|
|
// QTest::newRow("octal") << "n2" << QVariant(015);
|
|
QTest::newRow("fp1") << "n3" << QVariant(-4.2E11);
|
|
QTest::newRow("fp2") << "n4" << QVariant(.1e9);
|
|
QTest::newRow("fp3") << "n5" << QVariant(3e-12);
|
|
QTest::newRow("fp4") << "n6" << QVariant(3e+12);
|
|
QTest::newRow("fp5") << "n7" << QVariant(0.1e9);
|
|
QTest::newRow("large-int1") << "n8" << QVariant((double) 1152921504606846976);
|
|
QTest::newRow("large-int2") << "n9" << QVariant(100000000000000000000.);
|
|
|
|
QTest::newRow("special1") << "c1" << QVariant(QString("\b"));
|
|
QTest::newRow("special2") << "c2" << QVariant(QString("\f"));
|
|
QTest::newRow("special3") << "c3" << QVariant(QString("\n"));
|
|
QTest::newRow("special4") << "c4" << QVariant(QString("\r"));
|
|
QTest::newRow("special5") << "c5" << QVariant(QString("\t"));
|
|
QTest::newRow("special6") << "c6" << QVariant(QString("\v"));
|
|
QTest::newRow("special7") << "c7" << QVariant(QString("\'"));
|
|
QTest::newRow("special8") << "c8" << QVariant(QString("\""));
|
|
QTest::newRow("special9") << "c9" << QVariant(QString("\\"));
|
|
// We don't handle octal escape sequences
|
|
QTest::newRow("special10") << "c10" << QVariant(QString(1, QChar(0xa9)));
|
|
QTest::newRow("special11") << "c11" << QVariant(QString(1, QChar(0x00A9)));
|
|
}
|
|
|
|
void tst_qqmllanguage::literals()
|
|
{
|
|
QFETCH(QString, property);
|
|
QFETCH(QVariant, value);
|
|
|
|
QQmlComponent component(&engine, testFile("literals.qml"));
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property(property.toLatin1()), value);
|
|
}
|
|
|
|
void tst_qqmllanguage::objectDeletionNotify_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
|
|
QTest::newRow("property QtObject") << "objectDeletionNotify.1.qml";
|
|
QTest::newRow("property variant") << "objectDeletionNotify.2.qml";
|
|
QTest::newRow("property var") << "objectDeletionNotify.3.qml";
|
|
QTest::newRow("property var guard removed") << "objectDeletionNotify.4.qml";
|
|
}
|
|
|
|
void tst_qqmllanguage::objectDeletionNotify()
|
|
{
|
|
QFETCH(QString, file);
|
|
|
|
QQmlComponent component(&engine, testFile(file));
|
|
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("success").toBool(), true);
|
|
|
|
QMetaObject::invokeMethod(object.data(), "destroyObject");
|
|
|
|
// Process the deletion event
|
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
|
QCoreApplication::processEvents();
|
|
|
|
QCOMPARE(object->property("success").toBool(), true);
|
|
}
|
|
|
|
void tst_qqmllanguage::scopedProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("scopedProperties.qml"));
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
QVERIFY(o->property("success").toBool());
|
|
}
|
|
|
|
void tst_qqmllanguage::deepProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("deepProperty.qml"));
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
QFont font = qvariant_cast<QFont>(qvariant_cast<QObject*>(o->property("someObject"))->property("font"));
|
|
QCOMPARE(font.family(), QStringLiteral("test"));
|
|
}
|
|
|
|
// Tests that the implicit import has lowest precedence, in the case where
|
|
// there are conflicting types and types only found in the local import.
|
|
// Tests that just check one (or the root) type are in ::importsOrder
|
|
void tst_qqmllanguage::implicitImportsLast()
|
|
{
|
|
if (qmlCheckTypes())
|
|
QSKIP("This test is about maintaining the same choice when type is ambiguous.");
|
|
|
|
if (engine.importPathList() == defaultImportPathList)
|
|
engine.addImportPath(testFile("lib"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl("localOrderTest.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QVERIFY(QString(object->metaObject()->superClass()->superClass()->className())
|
|
.startsWith(QLatin1String("QQuickMouseArea")));
|
|
QObject* object2 = object->property("item").value<QObject*>();
|
|
QVERIFY(object2 != nullptr);
|
|
QCOMPARE(QString(object2->metaObject()->superClass()->className()),
|
|
QLatin1String("QQuickRectangle"));
|
|
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
void tst_qqmllanguage::getSingletonInstance(QQmlEngine& engine, const char* fileName, const char* propertyName, QObject** result /* out */)
|
|
{
|
|
QVERIFY(fileName != nullptr);
|
|
QVERIFY(propertyName != nullptr);
|
|
|
|
if (!fileName || !propertyName)
|
|
return;
|
|
|
|
QQmlComponent component(&engine, testFileUrl(fileName));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
|
|
getSingletonInstance(object.data(), propertyName, result);
|
|
}
|
|
|
|
void tst_qqmllanguage::getSingletonInstance(QObject* o, const char* propertyName, QObject** result /* out */)
|
|
{
|
|
QVERIFY(o != nullptr);
|
|
QVERIFY(propertyName != nullptr);
|
|
|
|
if (!o || !propertyName)
|
|
return;
|
|
|
|
QVariant variant = o->property(propertyName);
|
|
QVERIFY(variant.isValid());
|
|
|
|
QObject *singleton = nullptr;
|
|
if (variant.userType() == qMetaTypeId<QObject *>())
|
|
singleton = variant.value<QObject*>();
|
|
else if (variant.userType() == qMetaTypeId<QJSValue>())
|
|
singleton = variant.value<QJSValue>().toQObject();
|
|
|
|
QVERIFY(singleton != nullptr);
|
|
*result = singleton;
|
|
}
|
|
|
|
void verifyCompositeSingletonPropertyValues(QObject* o, const char* n1, int v1, const char* n2, int v2)
|
|
{
|
|
QCOMPARE(o->property(n1).userType(), (int)QMetaType::Int);
|
|
QCOMPARE(o->property(n1), QVariant(v1));
|
|
|
|
QCOMPARE(o->property(n2).userType(), (int)QVariant::String);
|
|
QString numStr;
|
|
QCOMPARE(o->property(n2), QVariant(QString(QLatin1String("Test value: ")).append(numStr.setNum(v2))));
|
|
}
|
|
|
|
// Reads values from a composite singleton type
|
|
void tst_qqmllanguage::compositeSingletonProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 125, "value2", -55);
|
|
}
|
|
|
|
// Checks that the addresses of the composite singletons used in the same
|
|
// engine are the same.
|
|
void tst_qqmllanguage::compositeSingletonSameEngine()
|
|
{
|
|
QObject* s1 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest2.qml", "singleton1", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
s1->setProperty("testProp2", QVariant(13));
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest3.qml", "singleton2", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
QCOMPARE(s2->property("testProp2"), QVariant(13));
|
|
|
|
QCOMPARE(s1, s2);
|
|
}
|
|
|
|
// Checks that the addresses of the composite singletons used in different
|
|
// engines are different.
|
|
void tst_qqmllanguage::compositeSingletonDifferentEngine()
|
|
{
|
|
QQmlEngine e2;
|
|
|
|
QObject* s1 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest2.qml", "singleton1", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
s1->setProperty("testProp2", QVariant(13));
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(e2, "singletonTest3.qml", "singleton2", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
QCOMPARE(s2->property("testProp2"), QVariant(25));
|
|
|
|
QVERIFY(s1 != s2);
|
|
}
|
|
|
|
// pragma Singleton in a non-type qml file fails
|
|
void tst_qqmllanguage::compositeSingletonNonTypeError()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest4.qml"));
|
|
VERIFY_ERRORS("singletonTest4.error.txt");
|
|
}
|
|
|
|
// Loads the singleton using a namespace qualifier
|
|
void tst_qqmllanguage::compositeSingletonQualifiedNamespace()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest5.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 125, "value2", -55);
|
|
|
|
// lets verify that the singleton instance we are using is the same
|
|
// when loaded through another file (without namespace!)
|
|
QObject *s1 = nullptr;
|
|
getSingletonInstance(o.data(), "singletonInstance", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest5a.qml", "singletonInstance", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
|
|
QCOMPARE(s1, s2);
|
|
}
|
|
|
|
// Loads a singleton from a module
|
|
void tst_qqmllanguage::compositeSingletonModule()
|
|
{
|
|
engine.addImportPath(testFile("singleton/module"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest6.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 125, "value2", -55);
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value3", 125, "value4", -55);
|
|
|
|
// lets verify that the singleton instance we are using is the same
|
|
// when loaded through another file
|
|
QObject *s1 = nullptr;
|
|
getSingletonInstance(o.data(), "singletonInstance", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest6a.qml", "singletonInstance", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
|
|
QCOMPARE(s1, s2);
|
|
}
|
|
|
|
// Loads a singleton from a module with a higher version
|
|
void tst_qqmllanguage::compositeSingletonModuleVersioned()
|
|
{
|
|
engine.addImportPath(testFile("singleton/module"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest7.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 225, "value2", 55);
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value3", 225, "value4", 55);
|
|
|
|
// lets verify that the singleton instance we are using is the same
|
|
// when loaded through another file
|
|
QObject *s1 = nullptr;
|
|
getSingletonInstance(o.data(), "singletonInstance", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest7a.qml", "singletonInstance", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
|
|
QCOMPARE(s1, s2);
|
|
}
|
|
|
|
// Loads a singleton from a module with a qualified namespace
|
|
void tst_qqmllanguage::compositeSingletonModuleQualified()
|
|
{
|
|
engine.addImportPath(testFile("singleton/module"));
|
|
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest8.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 225, "value2", 55);
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value3", 225, "value4", 55);
|
|
|
|
// lets verify that the singleton instance we are using is the same
|
|
// when loaded through another file
|
|
QObject *s1 = nullptr;
|
|
getSingletonInstance(o.data(), "singletonInstance", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
|
|
QObject* s2 = nullptr;
|
|
getSingletonInstance(engine, "singletonTest8a.qml", "singletonInstance", &s2);
|
|
QVERIFY(s2 != nullptr);
|
|
|
|
QCOMPARE(s1, s2);
|
|
}
|
|
|
|
// Tries to instantiate a type with a pragma Singleton and fails
|
|
void tst_qqmllanguage::compositeSingletonInstantiateError()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest9.qml"));
|
|
VERIFY_ERRORS("singletonTest9.error.txt");
|
|
}
|
|
|
|
// Having a composite singleton type as dynamic property type is allowed
|
|
void tst_qqmllanguage::compositeSingletonDynamicPropertyError()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest10.qml"));
|
|
VERIFY_ERRORS(0);
|
|
}
|
|
|
|
void tst_qqmllanguage::compositeSingletonDynamicSignalAndJavaScriptPragma()
|
|
{
|
|
{
|
|
// Having a composite singleton type as dynamic signal parameter succeeds
|
|
// (like C++ singleton)
|
|
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest11.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 99, "value2", -55);
|
|
}
|
|
{
|
|
// Load a composite singleton type and a javascript file that has .pragma library
|
|
// in it. This will make sure that the javascript .pragma does not get mixed with
|
|
// the pragma Singleton changes.
|
|
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest16.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
// The value1 that is read from the SingletonType was changed from 125 to 99
|
|
// above. As the type is a singleton and
|
|
// the engine has not been destroyed, we just retrieve the old instance and
|
|
// the value is still 99.
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 99, "value2", 333);
|
|
}
|
|
}
|
|
|
|
// Use qmlRegisterType to register a qml composite type with pragma Singleton defined in it.
|
|
// This will fail as qmlRegisterType will only instantiate CompositeTypes.
|
|
void tst_qqmllanguage::compositeSingletonQmlRegisterTypeError()
|
|
{
|
|
qmlRegisterType(testFileUrl("singleton/registeredComposite/CompositeType.qml"),
|
|
"CompositeSingletonTest", 1, 0, "RegisteredCompositeType");
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest12.qml"));
|
|
VERIFY_ERRORS("singletonTest12.error.txt");
|
|
}
|
|
|
|
// Qmldir defines a type as a singleton, but the qml file does not have a pragma Singleton.
|
|
void tst_qqmllanguage::compositeSingletonQmldirNoPragmaError()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest13.qml"));
|
|
VERIFY_ERRORS("singletonTest13.error.txt");
|
|
}
|
|
|
|
// Invalid singleton definition in the qmldir file results in an error
|
|
void tst_qqmllanguage::compositeSingletonQmlDirError()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest14.qml"));
|
|
VERIFY_ERRORS("singletonTest14.error.txt");
|
|
}
|
|
|
|
// Load a remote composite singleton type via qmldir that defines the type as a singleton
|
|
void tst_qqmllanguage::compositeSingletonRemote()
|
|
{
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
|
|
QFile f(testFile("singletonTest15.qml"));
|
|
QVERIFY(f.open(QIODevice::ReadOnly));
|
|
QByteArray contents = f.readAll();
|
|
f.close();
|
|
|
|
contents.replace(QByteArrayLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString().toUtf8());
|
|
|
|
QQmlComponent component(&engine);
|
|
component.setData(contents, testFileUrl("singletonTest15.qml"));
|
|
|
|
while (component.isLoading())
|
|
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents, 50);
|
|
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 525, "value2", 355);
|
|
}
|
|
|
|
// Reads values from a Singleton accessed through selectors.
|
|
void tst_qqmllanguage::compositeSingletonSelectors()
|
|
{
|
|
QQmlEngine e2;
|
|
QQmlFileSelector qmlSelector(&e2);
|
|
qmlSelector.setExtraSelectors(QStringList() << "basicSelector");
|
|
QQmlComponent component(&e2, testFileUrl("singletonTest1.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 625, "value2", 455);
|
|
}
|
|
|
|
// Reads values from a Singleton that was registered through the C++ API:
|
|
// qmlRegisterSingletonType.
|
|
void tst_qqmllanguage::compositeSingletonRegistered()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("singletonTest17.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
verifyCompositeSingletonPropertyValues(o.data(), "value1", 925, "value2", 755);
|
|
}
|
|
|
|
void tst_qqmllanguage::compositeSingletonCircular()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("circularSingleton.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QQmlTestMessageHandler messageHandler;
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
// ensure we aren't hitting the recursion warning
|
|
QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString()));
|
|
|
|
QCOMPARE(o->property("value").toInt(), 2);
|
|
}
|
|
|
|
void tst_qqmllanguage::singletonsHaveContextAndEngine()
|
|
{
|
|
QObject *qmlSingleton = nullptr;
|
|
getSingletonInstance(engine, "singletonTest18.qml", "qmlSingleton", &qmlSingleton);
|
|
QVERIFY(qmlContext(qmlSingleton));
|
|
QCOMPARE(qmlEngine(qmlSingleton), &engine);
|
|
|
|
QObject *jsSingleton = nullptr;
|
|
getSingletonInstance(engine, "singletonTest18.qml", "jsSingleton", &jsSingleton);
|
|
QVERIFY(qmlContext(jsSingleton));
|
|
QCOMPARE(qmlEngine(jsSingleton), &engine);
|
|
|
|
QObject *cppSingleton = nullptr;
|
|
getSingletonInstance(engine, "singletonTest18.qml", "cppSingleton", &cppSingleton);
|
|
QVERIFY(qmlContext(cppSingleton));
|
|
QCOMPARE(qmlEngine(cppSingleton), &engine);
|
|
}
|
|
|
|
void tst_qqmllanguage::customParserBindingScopes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customParserBindingScopes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QPointer<QObject> child = qvariant_cast<QObject*>(o->property("child"));
|
|
QVERIFY(!child.isNull());
|
|
QCOMPARE(child->property("testProperty").toInt(), 42);
|
|
}
|
|
|
|
void tst_qqmllanguage::customParserEvaluateEnum()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customParserEvaluateEnum.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::customParserProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customParserProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
SimpleObjectWithCustomParser *testObject = qobject_cast<SimpleObjectWithCustomParser*>(o.data());
|
|
QVERIFY(testObject);
|
|
QCOMPARE(testObject->customBindingsCount(), 0);
|
|
QCOMPARE(testObject->intProperty(), 42);
|
|
QCOMPARE(testObject->property("qmlString").toString(), QStringLiteral("Hello"));
|
|
QVERIFY(!testObject->property("someObject").isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::customParserWithExtendedObject()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("customExtendedParserProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
SimpleObjectWithCustomParser *testObject = qobject_cast<SimpleObjectWithCustomParser*>(o.data());
|
|
QVERIFY(testObject);
|
|
QCOMPARE(testObject->customBindingsCount(), 0);
|
|
QCOMPARE(testObject->intProperty(), 42);
|
|
QCOMPARE(testObject->property("qmlString").toString(), QStringLiteral("Hello"));
|
|
QVERIFY(!testObject->property("someObject").isNull());
|
|
|
|
QVariant returnValue;
|
|
QVERIFY(QMetaObject::invokeMethod(o.data(), "getExtendedProperty", Q_RETURN_ARG(QVariant, returnValue)));
|
|
QCOMPARE(returnValue.toInt(), 1584);
|
|
}
|
|
|
|
void tst_qqmllanguage::nestedCustomParsers()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("nestedCustomParsers.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
SimpleObjectWithCustomParser *testObject = qobject_cast<SimpleObjectWithCustomParser*>(o.data());
|
|
QVERIFY(testObject);
|
|
QCOMPARE(testObject->customBindingsCount(), 1);
|
|
SimpleObjectWithCustomParser *nestedObject = qobject_cast<SimpleObjectWithCustomParser*>(testObject->property("nested").value<QObject*>());
|
|
QVERIFY(nestedObject);
|
|
QCOMPARE(nestedObject->customBindingsCount(), 1);
|
|
}
|
|
|
|
void tst_qqmllanguage::preservePropertyCacheOnGroupObjects()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("preservePropertyCacheOnGroupObjects.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QObject *subObject = qvariant_cast<QObject*>(o->property("subObject"));
|
|
QVERIFY(subObject);
|
|
QCOMPARE(subObject->property("value").toInt(), 42);
|
|
|
|
QQmlData *ddata = QQmlData::get(subObject);
|
|
QVERIFY(ddata);
|
|
QQmlPropertyCache *subCache = ddata->propertyCache;
|
|
QVERIFY(subCache);
|
|
QQmlPropertyData *pd = subCache->property(QStringLiteral("newProperty"), /*object*/nullptr, /*context*/nullptr);
|
|
QVERIFY(pd);
|
|
QCOMPARE(pd->propType(), qMetaTypeId<int>());
|
|
}
|
|
|
|
void tst_qqmllanguage::propertyCacheInSync()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("propertyCacheInSync.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QObject *anchors = qvariant_cast<QObject*>(o->property("anchors"));
|
|
QVERIFY(anchors);
|
|
QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(anchors);
|
|
QVERIFY(vmemo);
|
|
QQmlPropertyCache *vmemoCache = vmemo->propertyCache();
|
|
QVERIFY(vmemoCache);
|
|
QQmlData *ddata = QQmlData::get(anchors);
|
|
QVERIFY(ddata);
|
|
QVERIFY(ddata->propertyCache);
|
|
// Those always have to be in sync and correct.
|
|
QCOMPARE(ddata->propertyCache, vmemoCache);
|
|
QCOMPARE(anchors->property("margins").toInt(), 50);
|
|
}
|
|
|
|
void tst_qqmllanguage::rootObjectInCreationNotForSubObjects()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("rootObjectInCreationNotForSubObjects.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
// QQmlComponent should have set this back to false anyway
|
|
QQmlData *ddata = QQmlData::get(o.data());
|
|
QVERIFY(!ddata->rootObjectInCreation);
|
|
|
|
QObject *subObject = qvariant_cast<QObject*>(o->property("subObject"));
|
|
QVERIFY(!subObject);
|
|
|
|
qmlExecuteDeferred(o.data());
|
|
|
|
subObject = qvariant_cast<QObject*>(o->property("subObject"));
|
|
QVERIFY(subObject);
|
|
|
|
ddata = QQmlData::get(subObject);
|
|
// This should never have been set in the first place as there is no
|
|
// QQmlComponent to set it back to false.
|
|
QVERIFY(!ddata->rootObjectInCreation);
|
|
}
|
|
|
|
// QTBUG-63036
|
|
void tst_qqmllanguage::lazyDeferredSubObject()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("lazyDeferredSubObject.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QObject *subObject = qvariant_cast<QObject *>(object->property("subObject"));
|
|
QVERIFY(subObject);
|
|
|
|
QCOMPARE(object->objectName(), QStringLiteral("custom"));
|
|
QCOMPARE(subObject->objectName(), QStringLiteral("custom"));
|
|
}
|
|
|
|
// QTBUG-63200
|
|
void tst_qqmllanguage::deferredProperties()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("deferredProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QObject *innerObj = object->findChild<QObject *>(QStringLiteral("innerobj"));
|
|
QVERIFY(!innerObj);
|
|
|
|
QObject *outerObj = object->findChild<QObject *>(QStringLiteral("outerobj"));
|
|
QVERIFY(!outerObj);
|
|
|
|
QObject *groupProperty = object->property("groupProperty").value<QObject *>();
|
|
QVERIFY(!groupProperty);
|
|
|
|
QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 0);
|
|
|
|
QQmlData *qmlData = QQmlData::get(object.data());
|
|
QVERIFY(qmlData);
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
|
|
QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
|
|
QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
|
|
|
|
qmlExecuteDeferred(object.data());
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 0);
|
|
|
|
innerObj = object->findChild<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
|
|
QVERIFY(innerObj);
|
|
QCOMPARE(innerObj->property("wasCompleted"), QVariant(true));
|
|
|
|
outerObj = object->findChild<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
|
|
QVERIFY(outerObj);
|
|
QCOMPARE(outerObj->property("wasCompleted"), QVariant(true));
|
|
|
|
groupProperty = object->property("groupProperty").value<QObject *>();
|
|
QCOMPARE(groupProperty, outerObj);
|
|
|
|
listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 4);
|
|
|
|
QCOMPARE(listProperty.at(&listProperty, 0)->objectName(), QStringLiteral("innerlist1")); // MyDeferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 0)->property("wasCompleted"), QVariant(true));
|
|
QCOMPARE(listProperty.at(&listProperty, 1)->objectName(), QStringLiteral("innerlist2")); // MyDeferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true));
|
|
|
|
QCOMPARE(listProperty.at(&listProperty, 2)->objectName(), QStringLiteral("outerlist1")); // deferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 2)->property("wasCompleted"), QVariant(true));
|
|
QCOMPARE(listProperty.at(&listProperty, 3)->objectName(), QStringLiteral("outerlist2")); // deferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 3)->property("wasCompleted"), QVariant(true));
|
|
}
|
|
|
|
static void beginDeferredOnce(QQmlEnginePrivate *enginePriv,
|
|
const QQmlProperty &property, QQmlComponentPrivate::DeferredState *deferredState)
|
|
{
|
|
QObject *object = property.object();
|
|
QQmlData *ddata = QQmlData::get(object);
|
|
Q_ASSERT(!ddata->deferredData.isEmpty());
|
|
|
|
int propertyIndex = property.index();
|
|
|
|
for (auto dit = ddata->deferredData.rbegin(); dit != ddata->deferredData.rend(); ++dit) {
|
|
QQmlData::DeferredData *deferData = *dit;
|
|
|
|
auto range = deferData->bindings.equal_range(propertyIndex);
|
|
if (range.first == deferData->bindings.end())
|
|
continue;
|
|
|
|
QQmlComponentPrivate::ConstructionState *state = new QQmlComponentPrivate::ConstructionState;
|
|
state->completePending = true;
|
|
|
|
state->creator.reset(new QQmlObjectCreator(
|
|
deferData->context->parent(), deferData->compilationUnit,
|
|
QQmlRefPointer<QQmlContextData>()));
|
|
|
|
enginePriv->inProgressCreations++;
|
|
|
|
std::deque<const QV4::CompiledData::Binding *> reversedBindings;
|
|
std::copy(range.first, range.second, std::front_inserter(reversedBindings));
|
|
state->creator->beginPopulateDeferred(deferData->context);
|
|
for (const QV4::CompiledData::Binding *binding: reversedBindings)
|
|
state->creator->populateDeferredBinding(property, deferData->deferredIdx, binding);
|
|
state->creator->finalizePopulateDeferred();
|
|
state->errors << state->creator->errors;
|
|
|
|
deferredState->constructionStates += state;
|
|
|
|
// Cleanup any remaining deferred bindings for this property, also in inner contexts,
|
|
// to avoid executing them later and overriding the property that was just populated.
|
|
while (dit != ddata->deferredData.rend()) {
|
|
(*dit)->bindings.remove(propertyIndex);
|
|
++dit;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void testExecuteDeferredOnce(const QQmlProperty &property)
|
|
{
|
|
QObject *object = property.object();
|
|
QQmlData *data = QQmlData::get(object);
|
|
if (data && !data->deferredData.isEmpty() && !data->wasDeleted(object)) {
|
|
QQmlEnginePrivate *ep = QQmlEnginePrivate::get(data->context->engine());
|
|
|
|
QQmlComponentPrivate::DeferredState state;
|
|
beginDeferredOnce(ep, property, &state);
|
|
|
|
// Release deferred data for those compilation units that no longer have deferred bindings
|
|
data->releaseDeferredData();
|
|
|
|
QQmlComponentPrivate::completeDeferred(ep, &state);
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::executeDeferredPropertiesOnce()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("deferredProperties.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(!object.isNull());
|
|
|
|
QObjectList innerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("innerobj"));
|
|
QVERIFY(innerObjsAtCreation.isEmpty());
|
|
|
|
QObjectList outerObjsAtCreation = object->findChildren<QObject *>(QStringLiteral("outerobj"));
|
|
QVERIFY(outerObjsAtCreation.isEmpty());
|
|
|
|
QObject *groupProperty = object->property("groupProperty").value<QObject *>();
|
|
QVERIFY(!groupProperty);
|
|
|
|
QQmlListProperty<QObject> listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 0);
|
|
|
|
QQmlData *qmlData = QQmlData::get(object.data());
|
|
QVERIFY(qmlData);
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
|
|
QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
|
|
QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
|
|
|
|
// first execution creates the outer object
|
|
testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
|
|
QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
|
|
QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
|
|
|
|
QObjectList innerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
|
|
QVERIFY(innerObjsAfterFirstExecute.isEmpty());
|
|
|
|
QObjectList outerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
|
|
QCOMPARE(outerObjsAfterFirstExecute.count(), 1);
|
|
QCOMPARE(outerObjsAfterFirstExecute.first()->property("wasCompleted"), QVariant(true));
|
|
|
|
groupProperty = object->property("groupProperty").value<QObject *>();
|
|
QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first());
|
|
|
|
listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 0);
|
|
|
|
// re-execution does nothing (to avoid overriding the property)
|
|
testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
|
|
QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
|
|
QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
|
|
|
|
QObjectList innerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
|
|
QVERIFY(innerObjsAfterSecondExecute.isEmpty());
|
|
|
|
QObjectList outerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
|
|
QCOMPARE(outerObjsAfterFirstExecute, outerObjsAfterSecondExecute);
|
|
|
|
groupProperty = object->property("groupProperty").value<QObject *>();
|
|
QCOMPARE(groupProperty, outerObjsAfterFirstExecute.first());
|
|
|
|
listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 0);
|
|
|
|
// execution of a list property should execute all outer list bindings
|
|
testExecuteDeferredOnce(QQmlProperty(object.data(), "listProperty"));
|
|
|
|
QCOMPARE(qmlData->deferredData.count(), 0);
|
|
|
|
listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
|
|
QCOMPARE(listProperty.count(&listProperty), 2);
|
|
|
|
QCOMPARE(listProperty.at(&listProperty, 0)->objectName(), QStringLiteral("outerlist1")); // deferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 0)->property("wasCompleted"), QVariant(true));
|
|
QCOMPARE(listProperty.at(&listProperty, 1)->objectName(), QStringLiteral("outerlist2")); // deferredListProperty.qml
|
|
QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true));
|
|
}
|
|
|
|
void tst_qqmllanguage::noChildEvents()
|
|
{
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQml 2.0; import Test 1.0; MyQmlObject { property QtObject child: QtObject {} }", QUrl());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->childAddedEventCount(), 0);
|
|
}
|
|
|
|
void tst_qqmllanguage::earlyIdObjectAccess()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("earlyIdObjectAccess.qml"));
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QVERIFY(o->property("success").toBool());
|
|
}
|
|
|
|
void tst_qqmllanguage::deleteSingletons()
|
|
{
|
|
QPointer<QObject> singleton;
|
|
{
|
|
QQmlEngine tmpEngine;
|
|
QQmlComponent component(&tmpEngine, testFileUrl("singletonTest5.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
QObject *s1 = nullptr;
|
|
getSingletonInstance(o.data(), "singletonInstance", &s1);
|
|
QVERIFY(s1 != nullptr);
|
|
singleton = s1;
|
|
QVERIFY(singleton.data() != nullptr);
|
|
}
|
|
QVERIFY(singleton.data() == nullptr);
|
|
}
|
|
|
|
void tst_qqmllanguage::arrayBuffer_data()
|
|
{
|
|
QTest::addColumn<QString>("file");
|
|
QTest::newRow("arraybuffer_property_get") << "arraybuffer_property_get.qml";
|
|
QTest::newRow("arraybuffer_property_set") << "arraybuffer_property_set.qml";
|
|
QTest::newRow("arraybuffer_signal_arg") << "arraybuffer_signal_arg.qml";
|
|
QTest::newRow("arraybuffer_method_arg") << "arraybuffer_method_arg.qml";
|
|
QTest::newRow("arraybuffer_method_return") << "arraybuffer_method_return.qml";
|
|
QTest::newRow("arraybuffer_method_overload") << "arraybuffer_method_overload.qml";
|
|
}
|
|
|
|
void tst_qqmllanguage::arrayBuffer()
|
|
{
|
|
QFETCH(QString, file);
|
|
QQmlComponent component(&engine, testFileUrl(file));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> object(component.create());
|
|
QVERIFY(object != nullptr);
|
|
QCOMPARE(object->property("ok").toBool(), true);
|
|
}
|
|
|
|
void tst_qqmllanguage::defaultListProperty()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("defaultListProperty.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
}
|
|
|
|
void tst_qqmllanguage::namespacedPropertyTypes()
|
|
{
|
|
QQmlComponent component(&engine, testFileUrl("namespacedPropertyTypes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::qmlTypeCanBeResolvedByName_data()
|
|
{
|
|
QTest::addColumn<QUrl>("componentUrl");
|
|
|
|
// Built-in C++ types
|
|
QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml");
|
|
QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml");
|
|
|
|
// Composite types with a qmldir
|
|
QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml");
|
|
QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml");
|
|
}
|
|
|
|
void tst_qqmllanguage::qmlTypeCanBeResolvedByName()
|
|
{
|
|
QFETCH(QUrl, componentUrl);
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, componentUrl);
|
|
VERIFY_ERRORS(0);
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "[object Object]"); // a bit crude, but it will do
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
}
|
|
|
|
// Tests for the QML-only extensions of instanceof. Tests for the regular JS
|
|
// instanceof belong in tst_qqmlecmascript!
|
|
void tst_qqmllanguage::instanceof_data()
|
|
{
|
|
QTest::addColumn<QUrl>("documentToTestIn");
|
|
QTest::addColumn<QVariant>("expectedValue");
|
|
|
|
// so the way this works is that the name of the test tag defines the test
|
|
// to run.
|
|
//
|
|
// the expectedValue is either a boolean true or false for whether the two
|
|
// operands are indeed an instanceof each other, or a string for the
|
|
// expected error message.
|
|
|
|
// assert that basic types don't convert to QObject
|
|
QTest::newRow("1 instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("true instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("\"foobar\" instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
|
|
// assert that Managed don't either
|
|
QTest::newRow("new String(\"foobar\") instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("new Object() instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("new Date() instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
|
|
// test that simple QtQml comparisons work
|
|
QTest::newRow("qtobjectInstance instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("qtobjectInstance instanceof Timer")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("timerInstance instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("timerInstance instanceof Timer")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("connectionsInstance instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("connectionsInstance instanceof Timer")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("connectionsInstance instanceof Connections")
|
|
<< testFileUrl("instanceof_qtqml.qml")
|
|
<< QVariant(true);
|
|
|
|
// make sure they still work when imported with a qualifier
|
|
QTest::newRow("qtobjectInstance instanceof QmlImport.QtObject")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("qtobjectInstance instanceof QmlImport.Timer")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("timerInstance instanceof QmlImport.QtObject")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("timerInstance instanceof QmlImport.Timer")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("connectionsInstance instanceof QmlImport.QtObject")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("connectionsInstance instanceof QmlImport.Timer")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("connectionsInstance instanceof QmlImport.Connections")
|
|
<< testFileUrl("instanceof_qtqml_qualified.qml")
|
|
<< QVariant(true);
|
|
|
|
// test that Quick C++ types work ok
|
|
QTest::newRow("itemInstance instanceof QtObject")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("itemInstance instanceof Timer")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("itemInstance instanceof Rectangle")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("rectangleInstance instanceof Item")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("rectangleInstance instanceof Rectangle")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("rectangleInstance instanceof MouseArea")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("mouseAreaInstance instanceof Item")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("mouseAreaInstance instanceof Rectangle")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("mouseAreaInstance instanceof MouseArea")
|
|
<< testFileUrl("instanceof_qtquick.qml")
|
|
<< QVariant(true);
|
|
|
|
// test that unqualified quick composite types work ok
|
|
QTest::newRow("rectangleInstance instanceof CustomRectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("customRectangleInstance instanceof Rectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleInstance instanceof Item")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleWithPropInstance instanceof CustomRectangleWithProp")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleWithPropInstance instanceof CustomRectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(false); // ### XXX: QTBUG-58477
|
|
QTest::newRow("customRectangleWithPropInstance instanceof Rectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleInstance instanceof MouseArea")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("customMouseAreaInstance instanceof MouseArea")
|
|
<< testFileUrl("instanceof_qtquick_composite.qml")
|
|
<< QVariant(true);
|
|
|
|
// test that they still work when qualified
|
|
QTest::newRow("rectangleInstance instanceof CustomImport.CustomRectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("customRectangleInstance instanceof QuickImport.Rectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleInstance instanceof QuickImport.Item")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleWithPropInstance instanceof CustomImport.CustomRectangleWithProp")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleWithPropInstance instanceof CustomImport.CustomRectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(false); // ### XXX: QTBUG-58477
|
|
QTest::newRow("customRectangleWithPropInstance instanceof QuickImport.Rectangle")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(true);
|
|
QTest::newRow("customRectangleInstance instanceof QuickImport.MouseArea")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(false);
|
|
QTest::newRow("customMouseAreaInstance instanceof QuickImport.MouseArea")
|
|
<< testFileUrl("instanceof_qtquick_composite_qualified.qml")
|
|
<< QVariant(true);
|
|
}
|
|
|
|
void tst_qqmllanguage::instanceof()
|
|
{
|
|
QFETCH(QUrl, documentToTestIn);
|
|
QFETCH(QVariant, expectedValue);
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, documentToTestIn);
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o != nullptr);
|
|
|
|
QQmlExpression expr(engine.contextForObject(o.data()), nullptr, QString::fromLatin1(QTest::currentDataTag()));
|
|
QVariant ret = expr.evaluate();
|
|
|
|
if (expectedValue.type() == QVariant::Bool) {
|
|
// no error expected
|
|
QVERIFY2(!expr.hasError(), qPrintable(expr.error().description()));
|
|
bool returnValue = ret.toBool();
|
|
|
|
if (QTest::currentDataTag() == QLatin1String("customRectangleWithPropInstance instanceof CustomRectangle") ||
|
|
QTest::currentDataTag() == QLatin1String("customRectangleWithPropInstance instanceof CustomImport.CustomRectangle"))
|
|
QCOMPARE(returnValue, expectedValue.toBool());
|
|
} else {
|
|
QVERIFY(expr.hasError());
|
|
QCOMPARE(expr.error().description(), expectedValue.toString());
|
|
}
|
|
}
|
|
|
|
void tst_qqmllanguage::concurrentLoadQmlDir()
|
|
{
|
|
ThreadedTestHTTPServer server(dataDirectory());
|
|
QString serverdir = server.urlString("/lib/");
|
|
engine.setImportPathList(QStringList(defaultImportPathList) << serverdir);
|
|
|
|
QQmlComponent component(&engine, testFileUrl("concurrentLoad_main.qml"));
|
|
QTRY_VERIFY(component.isReady());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
engine.setImportPathList(defaultImportPathList);
|
|
}
|
|
|
|
// Test that deleting an object and then accessing it doesn't crash.
|
|
// QTBUG-44153
|
|
class ObjectCreator : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public slots:
|
|
QObject *create() { return (new ObjectCreator); }
|
|
void del() { delete this; }
|
|
};
|
|
|
|
void tst_qqmllanguage::accessDeletedObject()
|
|
{
|
|
QQmlEngine engine;
|
|
|
|
QScopedPointer<ObjectCreator> creator(new ObjectCreator);
|
|
engine.rootContext()->setContextProperty("objectCreator", creator.get());
|
|
QQmlComponent component(&engine, testFileUrl("accessDeletedObject.qml"));
|
|
VERIFY_ERRORS(0);
|
|
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::lowercaseTypeNames()
|
|
{
|
|
QCOMPARE(qmlRegisterType<QObject>("Test", 1, 0, "lowerCaseTypeName"), -1);
|
|
QCOMPARE(qmlRegisterSingletonType<QObject>("Test", 1, 0, "lowerCaseTypeName", nullptr), -1);
|
|
}
|
|
|
|
void tst_qqmllanguage::thisInQmlScope()
|
|
{
|
|
QQmlEngine engine;
|
|
|
|
QQmlComponent component(&engine, testFileUrl("thisInQmlScope.qml"));
|
|
QTRY_VERIFY(component.isReady());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QCOMPARE(o->property("x"), QVariant(42));
|
|
QCOMPARE(o->property("y"), QVariant(42));
|
|
QCOMPARE(o->property("a"), QVariant(42));
|
|
QCOMPARE(o->property("b"), QVariant(42));
|
|
}
|
|
|
|
void tst_qqmllanguage::valueTypeGroupPropertiesInBehavior()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("groupPropertyInPropertyValueSource.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
QObject *animation = qmlContext(o.data())->contextProperty("animation").value<QObject*>();
|
|
QVERIFY(animation);
|
|
|
|
QCOMPARE(animation->property("easing").value<QEasingCurve>().type(), QEasingCurve::InOutQuad);
|
|
}
|
|
|
|
void tst_qqmllanguage::retrieveQmlTypeId()
|
|
{
|
|
// Register in reverse order to provoke wrong minor version matching.
|
|
int id2 = qmlRegisterType<QObject>("Test", 2, 3, "SomeTestType");
|
|
int id1 = qmlRegisterType<QObject>("Test", 2, 1, "SomeTestType");
|
|
QCOMPARE(qmlTypeId("Test", 2, 1, "SomeTestType"), id1);
|
|
QCOMPARE(qmlTypeId("Test", 2, 2, "SomeTestType"), id1);
|
|
QCOMPARE(qmlTypeId("Test", 2, 3, "SomeTestType"), id2);
|
|
|
|
// Error cases
|
|
QCOMPARE(qmlTypeId("Test", 2, 0, "SomeTestType"), -1);
|
|
QCOMPARE(qmlTypeId("Test", 2, 3, "DoesNotExist"), -1);
|
|
QCOMPARE(qmlTypeId("DoesNotExist", 2, 3, "SomeTestType"), -1);
|
|
|
|
// Must also work for other types (defined in testtpes.cpp)
|
|
QVERIFY(qmlTypeId("Test", 1, 0, "MyExtendedUncreateableBaseClass") >= 0);
|
|
QVERIFY(qmlTypeId("Test", 1, 0, "MyUncreateableBaseClass") >= 0);
|
|
QVERIFY(qmlTypeId("Test", 1, 0, "MyTypeObjectSingleton") >= 0);
|
|
}
|
|
|
|
void tst_qqmllanguage::polymorphicFunctionLookup()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("polymorphicFunctionLookup.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
QVERIFY(o->property("ok").toBool());
|
|
}
|
|
|
|
void tst_qqmllanguage::anchorsToParentInPropertyChanges()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("anchorsToParentInPropertyChagnes.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QTRY_COMPARE(o->property("edgeWidth").toInt(), 200);
|
|
}
|
|
|
|
void tst_qqmllanguage::typeWrapperToVariant()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("typeWrapperToVariant.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
QObject *connections = qvariant_cast<QObject *>(o->property("connections"));
|
|
QVERIFY(connections);
|
|
QObject *target = qvariant_cast<QObject *>(connections->property("target"));
|
|
QVERIFY(target);
|
|
}
|
|
|
|
void tst_qqmllanguage::extendedForeignTypes()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("foreignExtended.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
QCOMPARE(o->property("extendedBase").toInt(), 43);
|
|
QCOMPARE(o->property("extendedExtension").toInt(), 42);
|
|
QCOMPARE(o->property("foreignExtendedExtension").toInt(), 42);
|
|
QCOMPARE(o->property("foreignObjectName").toString(), QLatin1String("foreign"));
|
|
QCOMPARE(o->property("foreignExtendedObjectName").toString(), QLatin1String("foreignExtended"));
|
|
}
|
|
|
|
void tst_qqmllanguage::selfReference()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("SelfReference.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
|
|
QQmlComponentPrivate *componentPrivate = QQmlComponentPrivate::get(&component);
|
|
auto compilationUnit = componentPrivate->compilationUnit;
|
|
QVERIFY(compilationUnit);
|
|
|
|
const QMetaObject *metaObject = o->metaObject();
|
|
QMetaProperty selfProperty = metaObject->property(metaObject->indexOfProperty("self"));
|
|
QCOMPARE(selfProperty.userType(), compilationUnit->metaTypeId.id());
|
|
|
|
QByteArray typeName = selfProperty.typeName();
|
|
QVERIFY(typeName.endsWith('*'));
|
|
typeName = typeName.chopped(1);
|
|
QCOMPARE(typeName, metaObject->className());
|
|
|
|
QMetaMethod selfFunction = metaObject->method(metaObject->indexOfMethod("returnSelf()"));
|
|
QVERIFY(selfFunction.isValid());
|
|
QCOMPARE(selfFunction.returnType(), compilationUnit->metaTypeId.id());
|
|
|
|
QMetaMethod selfSignal;
|
|
|
|
for (int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i) {
|
|
QMetaMethod method = metaObject->method(i);
|
|
if (method.isValid() && method.name().startsWith("blah")) {
|
|
selfSignal = method;
|
|
break;
|
|
}
|
|
}
|
|
|
|
QVERIFY(selfSignal.isValid());
|
|
QCOMPARE(selfSignal.parameterCount(), 1);
|
|
QCOMPARE(selfSignal.parameterType(0), compilationUnit->metaTypeId.id());
|
|
}
|
|
|
|
void tst_qqmllanguage::selfReferencingSingleton()
|
|
{
|
|
QQmlEngine engine;
|
|
engine.addImportPath(dataDirectory());
|
|
|
|
QPointer<QObject> singletonPointer;
|
|
{
|
|
QQmlComponent component(&engine);
|
|
component.setData(QByteArray(R"(import QtQml 2.0
|
|
import selfreferencingsingletonmodule 1.0
|
|
QtObject {
|
|
property SelfReferencingSingleton singletonPointer: SelfReferencingSingleton
|
|
})"), QUrl());
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
singletonPointer = o->property("singletonPointer").value<QObject*>();
|
|
}
|
|
|
|
QVERIFY(!singletonPointer.isNull());
|
|
QCOMPARE(singletonPointer->property("dummy").toInt(), 42);
|
|
}
|
|
|
|
void tst_qqmllanguage::listContainingDeletedObject()
|
|
{
|
|
QQmlEngine engine;
|
|
auto url = testFileUrl("listContainingDeleted.qml");
|
|
const QString message = url.toString() + ":24: TypeError: Cannot read property 'enabled' of null";
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, message.toUtf8().data());
|
|
QQmlComponent comp(&engine, url);
|
|
QScopedPointer<QObject> root(comp.create());
|
|
QVERIFY(root);
|
|
|
|
auto cmp = root->property("a").value<QQmlComponent*>();
|
|
auto o = cmp->create();
|
|
|
|
QMetaObject::invokeMethod(root.get(), "doAssign", Q_ARG(QVariant, QVariant::fromValue(o)));
|
|
delete o;
|
|
QMetaObject::invokeMethod(root.get(), "use");
|
|
|
|
}
|
|
|
|
void tst_qqmllanguage::overrideSingleton()
|
|
{
|
|
auto check = [](const QString &name, const QByteArray &singletonElement) {
|
|
const QByteArray testQml = "import Test 1.0\n"
|
|
"import QtQml 2.0\n"
|
|
"QtObject { objectName: " + singletonElement + ".objectName }";
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, nullptr);
|
|
component.setData(testQml, QUrl("singleton.qml"));
|
|
QVERIFY(component.isReady());
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QCOMPARE(obj->objectName(), name);
|
|
};
|
|
|
|
check("statically registered", "BareSingleton");
|
|
|
|
BareSingleton singleton;
|
|
singleton.setObjectName("dynamically registered");
|
|
qmlRegisterSingletonInstance("Test", 1, 0, "BareSingleton", &singleton);
|
|
|
|
check("dynamically registered", "BareSingleton");
|
|
|
|
QTest::ignoreMessage(
|
|
QtWarningMsg,
|
|
"singleton.qml:3: TypeError: Cannot read property 'objectName' of undefined");
|
|
check("", "UncreatableSingleton");
|
|
|
|
qmlRegisterSingletonInstance("Test", 1, 0, "UncreatableSingleton",
|
|
UncreatableSingleton::instance());
|
|
check("uncreatable", "UncreatableSingleton");
|
|
}
|
|
|
|
class AttachedObject;
|
|
class InnerObject : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_PROPERTY(bool revisionedProperty READ revisionedProperty WRITE setRevisionedProperty
|
|
NOTIFY revisionedPropertyChanged REVISION 2)
|
|
|
|
public:
|
|
InnerObject(QObject *parent = nullptr) : QObject(parent) {}
|
|
|
|
bool revisionedProperty() const { return m_revisionedProperty; }
|
|
void setRevisionedProperty(bool revisionedProperty)
|
|
{
|
|
if (revisionedProperty != m_revisionedProperty) {
|
|
m_revisionedProperty = revisionedProperty;
|
|
emit revisionedPropertyChanged();
|
|
}
|
|
}
|
|
|
|
static AttachedObject *qmlAttachedProperties(QObject *object);
|
|
|
|
signals:
|
|
Q_REVISION(2) void revisionedPropertyChanged();
|
|
|
|
private:
|
|
bool m_revisionedProperty = false;
|
|
};
|
|
|
|
class AttachedObject : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_PROPERTY(InnerObject *attached READ attached CONSTANT)
|
|
|
|
public:
|
|
explicit AttachedObject(QObject *parent = nullptr) :
|
|
QObject(parent),
|
|
m_attached(new InnerObject(this))
|
|
{}
|
|
|
|
InnerObject *attached() const { return m_attached; }
|
|
|
|
private:
|
|
InnerObject *m_attached;
|
|
};
|
|
|
|
class OuterObject : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit OuterObject(QObject *parent = nullptr) : QObject(parent) {}
|
|
};
|
|
|
|
AttachedObject *InnerObject::qmlAttachedProperties(QObject *object)
|
|
{
|
|
return new AttachedObject(object);
|
|
}
|
|
|
|
QML_DECLARE_TYPE(InnerObject)
|
|
QML_DECLARE_TYPEINFO(InnerObject, QML_HAS_ATTACHED_PROPERTIES)
|
|
|
|
void tst_qqmllanguage::revisionedPropertyOfAttachedObjectProperty()
|
|
{
|
|
qmlRegisterAnonymousType<AttachedObject>("foo", 2);
|
|
qmlRegisterType<InnerObject>("foo", 2, 0, "InnerObject");
|
|
qmlRegisterType<InnerObject, 2>("foo", 2, 2, "InnerObject");
|
|
qmlRegisterType<OuterObject>("foo", 2, 2, "OuterObject");
|
|
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.setData("import foo 2.2\n"
|
|
"OuterObject {\n"
|
|
" InnerObject.attached.revisionedProperty: true\n"
|
|
"}", QUrl());
|
|
|
|
QVERIFY(component.isReady());
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(!obj.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponent()
|
|
{
|
|
QFETCH(QUrl, componentUrl);
|
|
QFETCH(QColor, color);
|
|
QFETCH(int, width);
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, componentUrl);
|
|
QScopedPointer<QObject> o(component.create());
|
|
if (component.isError()) {
|
|
qDebug() << component.errorString();
|
|
}
|
|
QVERIFY(!o.isNull());
|
|
auto icInstance = o->findChild<QObject *>("icInstance");
|
|
QVERIFY(icInstance);
|
|
QCOMPARE(icInstance->property("color").value<QColor>(),color);
|
|
QCOMPARE(icInstance->property("width").value<qreal>(), width);
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponent_data()
|
|
{
|
|
QTest::addColumn<QUrl>("componentUrl");
|
|
QTest::addColumn<QColor>("color");
|
|
QTest::addColumn<int>("width");
|
|
|
|
QTest::newRow("Usage from other component") << testFileUrl("inlineComponentUser1.qml") << QColorConstants::Blue << 24;
|
|
QTest::newRow("Reexport") << testFileUrl("inlineComponentUser2.qml") << QColorConstants::Svg::green << 24;
|
|
QTest::newRow("Usage in same component") << testFileUrl("inlineComponentUser3.qml") << QColorConstants::Blue << 24;
|
|
|
|
QTest::newRow("Resolution happens at instantiation") << testFileUrl("inlineComponentUser4.qml") << QColorConstants::Blue << 24;
|
|
QTest::newRow("Non-toplevel IC is found") << testFileUrl("inlineComponentUser5.qml") << QColorConstants::Svg::red << 24;
|
|
|
|
QTest::newRow("Resolved in correct order") << testFileUrl("inlineComponentOrder.qml") << QColorConstants::Blue << 200;
|
|
|
|
QTest::newRow("ID resolves correctly") << testFileUrl("inlineComponentWithId.qml") << QColorConstants::Svg::red << 42;
|
|
QTest::newRow("Alias resolves correctly") << testFileUrl("inlineComponentWithAlias.qml") << QColorConstants::Svg::lime << 42;
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentReferenceCycle_data()
|
|
{
|
|
QTest::addColumn<QUrl>("componentUrl");
|
|
|
|
QTest::newRow("Simple cycle") << testFileUrl("icSimpleCycle.qml");
|
|
QTest::newRow("Via property") << testFileUrl("icCycleViaProperty.qml");
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentReferenceCycle()
|
|
{
|
|
QFETCH(QUrl, componentUrl);
|
|
QQmlEngine engine;
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
QQmlComponent component(&engine, componentUrl);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o.isNull());
|
|
QVERIFY(component.isError());
|
|
QCOMPARE(component.errorString(), componentUrl.toString() + QLatin1String(":-1 Inline components form a cycle!\n"));
|
|
}
|
|
|
|
void tst_qqmllanguage::nestedInlineComponentNotAllowed()
|
|
{
|
|
QQmlEngine engine;
|
|
auto url = testFileUrl("nestedIC.qml");
|
|
QQmlComponent component(&engine, url);
|
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QQmlComponent: Component is not ready");
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(component.isError());
|
|
QCOMPARE(component.errorString(), QLatin1String("%1:%2").arg(url.toString(), QLatin1String("5 Nested inline components are not supported\n")));
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentStaticTypeResolution()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("InlineComponentChild.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(o);
|
|
QCOMPARE(o->property("i").toInt(), 42);
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentInSingleton()
|
|
{
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, testFileUrl("singletonICTest.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<QObject> o(component.create());
|
|
QVERIFY(!o.isNull());
|
|
auto untyped = o->property("singleton1");
|
|
QVERIFY(untyped.isValid());
|
|
auto singleton1 = untyped.value<QObject*>();
|
|
QVERIFY(singleton1);
|
|
QCOMPARE(singleton1->property("iProp").value<int>(), 42);
|
|
QCOMPARE(singleton1->property("sProp").value<QString>(), QString::fromLatin1("Hello, world"));
|
|
QVERIFY(!o.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::nonExistingInlineComponent_data()
|
|
{
|
|
QTest::addColumn<QUrl>("componentUrl");
|
|
QTest::addColumn<QString>("errorMessage");
|
|
QTest::addColumn<int>("line");
|
|
QTest::addColumn<int>("column");
|
|
|
|
QTest::newRow("Property type") << testFileUrl("nonExistingICUser1.qml") << QString("Type InlineComponentProvider has no inline component type called NonExisting") << 4 << 5;
|
|
QTest::newRow("Instantiation") << testFileUrl("nonExistingICUser2.qml") << QString("Type InlineComponentProvider has no inline component type called NotExisting") << 4 << 5;
|
|
QTest::newRow("Inheritance") << testFileUrl("nonExistingICUser3.qml") << QString("Type InlineComponentProvider has no inline component type called NotExisting") << 3 << 1;
|
|
QTest::newRow("From singleton") << testFileUrl("nonExistingICUser4.qml") << QString("Type MySingleton.SingletonTypeWithIC has no inline component type called NonExisting") << 5 << 5;
|
|
|
|
QTest::newRow("Cannot access parent inline components from child") << testFileUrl("nonExistingICUser5.qml") << QString("Type InlineComponentProviderChild has no inline component type called StyledRectangle") << 4 << 5;
|
|
}
|
|
|
|
void tst_qqmllanguage::nonExistingInlineComponent()
|
|
{
|
|
QFETCH(QUrl, componentUrl);
|
|
QFETCH(QString, errorMessage);
|
|
QFETCH(int, line);
|
|
QFETCH(int, column);
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine, componentUrl);
|
|
auto errors = component.errors();
|
|
QCOMPARE(errors.size(), 1);
|
|
const auto &error = errors.first();
|
|
QCOMPARE(error.description(), errorMessage);
|
|
QCOMPARE(error.line(), line);
|
|
QCOMPARE(error.column(), column);
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentFoundBeforeOtherImports()
|
|
{
|
|
QQmlEngine engine;
|
|
QUrl url = testFileUrl("inlineComponentFoundBeforeOtherImports.qml");
|
|
QQmlComponent component(&engine, url);
|
|
|
|
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "Created");
|
|
QScopedPointer<QObject> root {component.create()};
|
|
}
|
|
|
|
void tst_qqmllanguage::inlineComponentDuplicateNameError()
|
|
{
|
|
QQmlEngine engine;
|
|
QUrl url = testFileUrl("inlineComponentDuplicateName.qml");
|
|
QQmlComponent component(&engine, url);
|
|
|
|
QString message = QLatin1String("%1:5 Inline component names must be unique per file\n").arg(url.toString());
|
|
QScopedPointer<QObject> root {component.create()};
|
|
QVERIFY(root.isNull());
|
|
QVERIFY(component.isError());
|
|
QCOMPARE(component.errorString(), message);
|
|
}
|
|
|
|
struct QJSValueConvertible {
|
|
|
|
Q_GADGET
|
|
|
|
public:
|
|
QString msg;
|
|
};
|
|
|
|
bool operator==(const QJSValueConvertible &lhs, const QJSValueConvertible &rhs) {
|
|
return lhs.msg == rhs.msg;
|
|
}
|
|
|
|
class TestItem : public QObject
|
|
{
|
|
Q_OBJECT
|
|
Q_PROPERTY( QVector<QPointF> positions MEMBER m_points )
|
|
Q_PROPERTY( QSet<QByteArray> barrays MEMBER m_barrays )
|
|
Q_PROPERTY( QVector<QJSValueConvertible> convertibles MEMBER m_convertibles)
|
|
|
|
public:
|
|
TestItem() = default;
|
|
QVector< QPointF > m_points;
|
|
QSet<QByteArray> m_barrays;
|
|
QVector<QJSValueConvertible> m_convertibles;
|
|
};
|
|
|
|
|
|
Q_DECLARE_METATYPE(QVector<QPointF>);
|
|
Q_DECLARE_METATYPE(QSet<QByteArray>);
|
|
Q_DECLARE_METATYPE(QJSValueConvertible);
|
|
Q_DECLARE_METATYPE(QVector<QJSValueConvertible>);
|
|
|
|
void tst_qqmllanguage::arrayToContainer()
|
|
{
|
|
QMetaType::registerConverter< QJSValue, QJSValueConvertible >(
|
|
|
|
[](const QJSValue& value)
|
|
{
|
|
return QJSValueConvertible{value.toString()};
|
|
}
|
|
);
|
|
QQmlEngine engine;
|
|
qmlRegisterType<TestItem>("qt.test", 1, 0, "TestItem");
|
|
QVector<QPointF> points { QPointF (2.0, 3.0) };
|
|
QSet<QByteArray> barrays { QByteArray("hello"), QByteArray("world") };
|
|
engine.rootContext()->setContextProperty("test", QVariant::fromValue(points));
|
|
QQmlComponent component(&engine, testFileUrl("arrayToContainer.qml"));
|
|
VERIFY_ERRORS(0);
|
|
QScopedPointer<TestItem> root(qobject_cast<TestItem *>(component.createWithInitialProperties( {{"vector", QVariant::fromValue(points)}, {"myset", QVariant::fromValue(barrays)} } )));
|
|
QVERIFY(root);
|
|
QCOMPARE(root->m_points.at(0), QPointF (2.0, 3.0) );
|
|
QVERIFY(root->m_barrays.contains("hello"));
|
|
QVERIFY(root->m_barrays.contains("world"));
|
|
QCOMPARE(root->m_convertibles.at(0).msg, QLatin1String("hello"));
|
|
QCOMPARE(root->m_convertibles.at(1).msg, QLatin1String("world"));
|
|
}
|
|
|
|
class EnumTester : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
enum Types
|
|
{
|
|
FIRST = 0,
|
|
SECOND,
|
|
THIRD
|
|
};
|
|
Q_ENUM(Types)
|
|
};
|
|
|
|
void tst_qqmllanguage::qualifiedScopeInCustomParser()
|
|
{
|
|
qmlRegisterUncreatableType<EnumTester>("scoped.custom.test", 1, 0, "EnumTester",
|
|
"Object only creatable in C++");
|
|
QQmlEngine engine;
|
|
QQmlComponent component(&engine);
|
|
component.setData("import QtQml.Models 2.12\n"
|
|
"import scoped.custom.test 1.0 as BACKEND\n"
|
|
"ListModel {\n"
|
|
" ListElement { text: \"a\"; type: BACKEND.EnumTester.FIRST }\n"
|
|
"}\n", QUrl());
|
|
QVERIFY(component.isReady());
|
|
QScopedPointer<QObject> obj(component.create());
|
|
QVERIFY(!obj.isNull());
|
|
}
|
|
|
|
void tst_qqmllanguage::checkUncreatableNoReason()
|
|
{
|
|
qmlRegisterTypesAndRevisions<UncreatableElementNoReason>("qt.uncreatable.noreason", 1, 0);
|
|
QQmlEngine engine;
|
|
QString qml = QString("import QtQuick 2.0\nimport qt.uncreatable.noreason 1.0\nUncreatableElementNoReason {}");
|
|
QQmlComponent c(&engine);
|
|
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
|
|
QCOMPARE(c.errors().count(), 1);
|
|
QCOMPARE(c.errors().first().description(), QString("Type cannot be created in QML."));
|
|
}
|
|
|
|
QTEST_MAIN(tst_qqmllanguage)
|
|
|
|
#include "tst_qqmllanguage.moc"
|