| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| |
| #include <QtTest/QtTest> |
| |
| #include <private/qqmldata_p.h> |
| #include <qjsengine.h> |
| #include <qjsvalueiterator.h> |
| #include <qgraphicsitem.h> |
| #include <qstandarditemmodel.h> |
| #include <QtCore/qnumeric.h> |
| #include <qqmlengine.h> |
| #include <qqmlcomponent.h> |
| #include <stdlib.h> |
| #include <private/qv4alloca_p.h> |
| #include <private/qjsvalue_p.h> |
| #include <QScopeGuard> |
| |
| #ifdef Q_CC_MSVC |
| #define NO_INLINE __declspec(noinline) |
| #else |
| #define NO_INLINE __attribute__((noinline)) |
| #endif |
| |
| Q_DECLARE_METATYPE(QList<int>) |
| Q_DECLARE_METATYPE(QObjectList) |
| |
| class tst_QJSEngine : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| tst_QJSEngine(); |
| virtual ~tst_QJSEngine(); |
| |
| private slots: |
| void callQObjectSlot(); |
| void constructWithParent(); |
| void newObject(); |
| void newArray(); |
| void newArray_HooliganTask218092(); |
| void newArray_HooliganTask233836(); |
| void toScriptValue_data(); |
| void toScriptValue(); |
| void toScriptValuenotroundtripped_data(); |
| void toScriptValuenotroundtripped(); |
| void newVariant(); |
| void newVariant_valueOfToString(); |
| void newVariant_valueOfEnum(); |
| void newRegExp(); |
| void jsRegExp(); |
| void newDate(); |
| void jsParseDate(); |
| void newQObject(); |
| void newQObjectRace(); |
| void newQObject_ownership(); |
| void newQObject_deletedEngine(); |
| void newQObjectPropertyCache(); |
| void newQMetaObject(); |
| void exceptionInSlot(); |
| void globalObjectProperties(); |
| void globalObjectEquals(); |
| void globalObjectProperties_enumerate(); |
| void createGlobalObjectProperty(); |
| void globalObjectWithCustomPrototype(); |
| void builtinFunctionNames_data(); |
| void builtinFunctionNames(); |
| void evaluate_data(); |
| void evaluate(); |
| void errorMessage_QT679(); |
| void valueConversion_basic(); |
| void valueConversion_QVariant(); |
| void valueConversion_basic2(); |
| void valueConversion_dateTime(); |
| void valueConversion_regExp(); |
| void valueConversion_RegularExpression(); |
| void castWithMultipleInheritance(); |
| void collectGarbage(); |
| void gcWithNestedDataStructure(); |
| void stacktrace(); |
| void numberParsing_data(); |
| void numberParsing(); |
| void automaticSemicolonInsertion(); |
| void errorConstructors(); |
| void argumentsProperty_globalContext(); |
| void argumentsProperty_JS(); |
| void jsNumberClass(); |
| void jsForInStatement_simple(); |
| void jsForInStatement_prototypeProperties(); |
| void jsForInStatement_mutateWhileIterating(); |
| void jsForInStatement_arrays(); |
| void jsForInStatement_constant(); |
| void with_constant(); |
| void stringObjects(); |
| void jsStringPrototypeReplaceBugs(); |
| void getterSetterThisObject_global(); |
| void getterSetterThisObject_plain(); |
| void getterSetterThisObject_prototypeChain(); |
| void jsContinueInSwitch(); |
| void jsShadowReadOnlyPrototypeProperty(); |
| void jsReservedWords_data(); |
| void jsReservedWords(); |
| void jsFutureReservedWords_data(); |
| void jsFutureReservedWords(); |
| void jsThrowInsideWithStatement(); |
| void reentrancy_globalObjectProperties(); |
| void reentrancy_Array(); |
| void reentrancy_objectCreation(); |
| void jsIncDecNonObjectProperty(); |
| void JSONparse(); |
| void arraySort(); |
| void lookupOnDisappearingProperty(); |
| void arrayConcat(); |
| void recursiveBoundFunctions(); |
| |
| void qRegExpInport_data(); |
| void qRegExpInport(); |
| void qRegularExpressionImport_data(); |
| void qRegularExpressionImport(); |
| void dateRoundtripJSQtJS(); |
| void dateRoundtripQtJSQt(); |
| void dateConversionJSQt(); |
| void dateConversionQtJS(); |
| void functionPrototypeExtensions(); |
| void threadedEngine(); |
| |
| void functionDeclarationsInConditionals(); |
| |
| void arrayPop_QTBUG_35979(); |
| void array_unshift_QTBUG_52065(); |
| void array_join_QTBUG_53672(); |
| |
| void regexpLastMatch(); |
| void regexpLastIndex(); |
| void indexedAccesses(); |
| |
| void prototypeChainGc(); |
| void prototypeChainGc_QTBUG38299(); |
| |
| void dynamicProperties(); |
| |
| void scopeOfEvaluate(); |
| |
| void callConstants(); |
| |
| void installTranslationFunctions(); |
| void translateScript_data(); |
| void translateScript(); |
| void translateScript_crossScript(); |
| void translateScript_trNoOp(); |
| void translateScript_callQsTrFromCpp(); |
| void translateWithInvalidArgs_data(); |
| void translateWithInvalidArgs(); |
| void translationContext_data(); |
| void translationContext(); |
| void translateScriptIdBased(); |
| void translateScriptUnicode_data(); |
| void translateScriptUnicode(); |
| void translateScriptUnicodeIdBased_data(); |
| void translateScriptUnicodeIdBased(); |
| void translateFromBuiltinCallback(); |
| void translationFilePath_data(); |
| void translationFilePath(); |
| |
| void installConsoleFunctions(); |
| void logging(); |
| void tracing(); |
| void asserts(); |
| void exceptions(); |
| |
| void installGarbageCollectionFunctions(); |
| |
| void installAllExtensions(); |
| |
| void privateMethods(); |
| |
| void engineForObject(); |
| void intConversion_QTBUG43309(); |
| #ifdef QT_DEPRECATED |
| void toFixed(); |
| #endif |
| |
| void argumentEvaluationOrder(); |
| |
| void v4FunctionWithoutQML(); |
| |
| void withNoContext(); |
| void holeInPropertyData(); |
| |
| void basicBlockMergeAfterLoopPeeling(); |
| |
| void modulusCrash(); |
| void malformedExpression(); |
| |
| void scriptScopes(); |
| |
| void binaryNumbers(); |
| void octalNumbers(); |
| |
| void incrementAfterNewline(); |
| |
| void deleteInsideForIn(); |
| |
| void functionToString_data(); |
| void functionToString(); |
| |
| void stringReplace(); |
| |
| void protoChanges_QTBUG68369(); |
| void multilineStrings(); |
| |
| void throwError(); |
| void throwErrorObject(); |
| void returnError(); |
| void mathMinMax(); |
| |
| void importModule(); |
| void importModuleRelative(); |
| void importModuleWithLexicallyScopedVars(); |
| void importExportErrors(); |
| |
| void equality(); |
| void aggressiveGc(); |
| void noAccumulatorInTemplateLiteral(); |
| |
| void interrupt_data(); |
| void interrupt(); |
| |
| void triggerBackwardJumpWithDestructuring(); |
| void arrayConcatOnSparseArray(); |
| void sortSparseArray(); |
| void compileBrokenRegexp(); |
| void sortNonStringArray(); |
| void iterateInvalidProxy(); |
| void applyOnHugeArray(); |
| |
| void tostringRecursionCheck(); |
| void arrayIncludesWithLargeArray(); |
| void printCircularArray(); |
| void typedArraySet(); |
| void dataViewCtor(); |
| |
| void uiLanguage(); |
| |
| public: |
| Q_INVOKABLE QJSValue throwingCppMethod1(); |
| Q_INVOKABLE void throwingCppMethod2(); |
| Q_INVOKABLE QJSValue throwingCppMethod3(); |
| |
| signals: |
| void testSignal(); |
| }; |
| |
| tst_QJSEngine::tst_QJSEngine() |
| { |
| } |
| |
| tst_QJSEngine::~tst_QJSEngine() |
| { |
| } |
| |
| Q_DECLARE_METATYPE(Qt::KeyboardModifier) |
| Q_DECLARE_METATYPE(Qt::KeyboardModifiers) |
| |
| class OverloadedSlots : public QObject |
| { |
| Q_OBJECT |
| public: |
| OverloadedSlots() |
| { |
| } |
| |
| signals: |
| void slotWithoutArgCalled(); |
| void slotWithSingleArgCalled(const QString &arg); |
| void slotWithArgumentsCalled(const QString &arg1, const QString &arg2, const QString &arg3); |
| void slotWithOverloadedArgumentsCalled(const QString &arg, Qt::KeyboardModifier modifier, Qt::KeyboardModifiers moreModifiers); |
| void slotWithTwoOverloadedArgumentsCalled(const QString &arg, Qt::KeyboardModifiers moreModifiers, Qt::KeyboardModifier modifier); |
| |
| public slots: |
| void slotToCall() { emit slotWithoutArgCalled(); } |
| void slotToCall(const QString &arg) { emit slotWithSingleArgCalled(arg); } |
| void slotToCall(const QString &arg, const QString &arg2, const QString &arg3 = QString()) |
| { |
| slotWithArgumentsCalled(arg, arg2, arg3); |
| } |
| void slotToCall(const QString &arg, Qt::KeyboardModifier modifier, Qt::KeyboardModifiers blah = Qt::ShiftModifier) |
| { |
| emit slotWithOverloadedArgumentsCalled(arg, modifier, blah); |
| } |
| void slotToCallTwoDefault(const QString &arg, Qt::KeyboardModifiers modifiers = Qt::ShiftModifier | Qt::ControlModifier, Qt::KeyboardModifier modifier = Qt::AltModifier) |
| { |
| emit slotWithTwoOverloadedArgumentsCalled(arg, modifiers, modifier); |
| } |
| }; |
| |
| void tst_QJSEngine::callQObjectSlot() |
| { |
| OverloadedSlots dummy; |
| QJSEngine eng; |
| eng.globalObject().setProperty("dummy", eng.newQObject(&dummy)); |
| QQmlEngine::setObjectOwnership(&dummy, QQmlEngine::CppOwnership); |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithoutArgCalled())); |
| eng.evaluate("dummy.slotToCall();"); |
| QCOMPARE(spy.count(), 1); |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithSingleArgCalled(QString))); |
| eng.evaluate("dummy.slotToCall('arg');"); |
| |
| QCOMPARE(spy.count(), 1); |
| const QList<QVariant> arguments = spy.takeFirst(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithArgumentsCalled(QString, QString, QString))); |
| eng.evaluate("dummy.slotToCall('arg', 'arg2');"); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.takeFirst(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(arguments.at(1).toString(), QString("arg2")); |
| QCOMPARE(arguments.at(2).toString(), QString()); |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithArgumentsCalled(QString, QString, QString))); |
| eng.evaluate("dummy.slotToCall('arg', 'arg2', 'arg3');"); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.takeFirst(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(arguments.at(1).toString(), QString("arg2")); |
| QCOMPARE(arguments.at(2).toString(), QString("arg3")); |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithOverloadedArgumentsCalled(QString, Qt::KeyboardModifier, Qt::KeyboardModifiers))); |
| eng.evaluate(QStringLiteral("dummy.slotToCall('arg', %1);").arg(QString::number(Qt::ControlModifier))); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.first(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(arguments.at(1).toInt(), int(Qt::ControlModifier)); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(2))), int(Qt::ShiftModifier)); |
| |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithTwoOverloadedArgumentsCalled(QString, Qt::KeyboardModifiers, Qt::KeyboardModifier))); |
| QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCallTwoDefault('arg', %1);").arg(QString::number(Qt::MetaModifier | Qt::KeypadModifier))); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.first(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(1))), int(Qt::MetaModifier | Qt::KeypadModifier)); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifier>(arguments.at(2))), int(Qt::AltModifier)); |
| } |
| |
| QJSValue jsArray = eng.newArray(); |
| jsArray.setProperty(QStringLiteral("MetaModifier"), QJSValue(Qt::MetaModifier)); |
| jsArray.setProperty(QStringLiteral("ShiftModifier"), QJSValue(Qt::ShiftModifier)); |
| jsArray.setProperty(QStringLiteral("ControlModifier"), QJSValue(Qt::ControlModifier)); |
| jsArray.setProperty(QStringLiteral("KeypadModifier"), QJSValue(Qt::KeypadModifier)); |
| |
| QJSValue value = eng.newQObject(new QObject); |
| value.setPrototype(jsArray); |
| eng.globalObject().setProperty(QStringLiteral("Qt"), value); |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithOverloadedArgumentsCalled(QString, Qt::KeyboardModifier, Qt::KeyboardModifiers))); |
| QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCall('arg', Qt.ControlModifier);")); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.first(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(arguments.at(1).toInt(), int(Qt::ControlModifier)); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(2))), int(Qt::ShiftModifier)); |
| } |
| |
| { |
| QSignalSpy spy(&dummy, SIGNAL(slotWithTwoOverloadedArgumentsCalled(QString, Qt::KeyboardModifiers, Qt::KeyboardModifier))); |
| QJSValue v = eng.evaluate(QStringLiteral("dummy.slotToCallTwoDefault('arg', Qt.MetaModifier | Qt.KeypadModifier);")); |
| QCOMPARE(spy.count(), 1); |
| |
| const QList<QVariant> arguments = spy.first(); |
| QCOMPARE(arguments.at(0).toString(), QString("arg")); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifiers>(arguments.at(1))), int(Qt::MetaModifier | Qt::KeypadModifier)); |
| QCOMPARE(int(qvariant_cast<Qt::KeyboardModifier>(arguments.at(2))), int(Qt::AltModifier)); |
| } |
| |
| } |
| |
| void tst_QJSEngine::constructWithParent() |
| { |
| QPointer<QJSEngine> ptr; |
| { |
| QObject obj; |
| QJSEngine *engine = new QJSEngine(&obj); |
| ptr = engine; |
| } |
| QVERIFY(ptr.isNull()); |
| } |
| |
| void tst_QJSEngine::newObject() |
| { |
| QJSEngine eng; |
| QJSValue object = eng.newObject(); |
| QVERIFY(!object.isUndefined()); |
| QCOMPARE(object.isObject(), true); |
| QCOMPARE(object.isCallable(), false); |
| // prototype should be Object.prototype |
| QVERIFY(!object.prototype().isUndefined()); |
| QCOMPARE(object.prototype().isObject(), true); |
| QCOMPARE(object.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true); |
| } |
| |
| void tst_QJSEngine::newArray() |
| { |
| QJSEngine eng; |
| QJSValue array = eng.newArray(); |
| QVERIFY(!array.isUndefined()); |
| QCOMPARE(array.isArray(), true); |
| QCOMPARE(array.isObject(), true); |
| QVERIFY(!array.isCallable()); |
| // prototype should be Array.prototype |
| QVERIFY(!array.prototype().isUndefined()); |
| QCOMPARE(array.prototype().isArray(), true); |
| QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true); |
| } |
| |
| void tst_QJSEngine::newArray_HooliganTask218092() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("[].splice(0, 0, 'a')"); |
| QVERIFY(ret.isArray()); |
| QCOMPARE(ret.property("length").toInt(), 0); |
| } |
| { |
| QJSValue ret = eng.evaluate("['a'].splice(0, 1, 'b')"); |
| QVERIFY(ret.isArray()); |
| QCOMPARE(ret.property("length").toInt(), 1); |
| } |
| { |
| QJSValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')"); |
| QVERIFY(ret.isArray()); |
| QCOMPARE(ret.property("length").toInt(), 1); |
| } |
| { |
| QJSValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')"); |
| QVERIFY(ret.isArray()); |
| QCOMPARE(ret.property("length").toInt(), 2); |
| } |
| { |
| QJSValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')"); |
| QVERIFY(ret.isArray()); |
| QCOMPARE(ret.property("length").toInt(), 2); |
| } |
| } |
| |
| void tst_QJSEngine::newArray_HooliganTask233836() |
| { |
| QJSEngine eng; |
| { |
| // According to ECMA-262, this should cause a RangeError. |
| QJSValue ret = eng.evaluate("a = new Array(4294967295); a.push('foo')"); |
| QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError"))); |
| } |
| { |
| QJSValue ret = eng.newArray(0xFFFFFFFF); |
| QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF)); |
| ret.setProperty(0xFFFFFFFF, 123); |
| QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF)); |
| QVERIFY(ret.property(0xFFFFFFFF).isNumber()); |
| QCOMPARE(ret.property(0xFFFFFFFF).toInt(), 123); |
| ret.setProperty(123, 456); |
| QCOMPARE(ret.property("length").toUInt(), uint(0xFFFFFFFF)); |
| QVERIFY(ret.property(123).isNumber()); |
| QCOMPARE(ret.property(123).toInt(), 456); |
| } |
| } |
| |
| void tst_QJSEngine::toScriptValue_data() |
| { |
| QTest::addColumn<QVariant>("input"); |
| |
| QTest::newRow("UnknownType") << QVariant(int(QMetaType::UnknownType), nullptr); |
| QTest::newRow("Nullptr") << QVariant(int(QMetaType::Nullptr), nullptr); |
| QTest::newRow("true") << QVariant(true); |
| QTest::newRow("false") << QVariant(false); |
| QTest::newRow("int") << QVariant(int(42)); |
| QTest::newRow("uint") << QVariant(uint(42)); |
| QTest::newRow("longlong") << QVariant(qlonglong(4242)); |
| QTest::newRow("ulonglong") << QVariant(qulonglong(4242)); |
| QTest::newRow("double") << QVariant(double(42.42)); |
| QTest::newRow("float") << QVariant(float(42.42)); |
| QTest::newRow("qstring") << QVariant(QString::fromLatin1("hello")); |
| QTest::newRow("qbytearray") << QVariant(QByteArray("hello")); |
| QTest::newRow("short") << QVariant(short('r')); |
| QTest::newRow("ushort") << QVariant(short('b')); |
| QTest::newRow("char") << QVariant(char('r')); |
| QTest::newRow("uchar") << QVariant(uchar('b')); |
| QTest::newRow("qchar") << QVariant(QString::fromUtf8("Ã¥").at(0)); |
| QTest::newRow("qdate") << QVariant(QDate(1925, 5, 8)); |
| QTest::newRow("qtime") << QVariant(QTime(4, 5, 6)); |
| QTest::newRow("qregularexpression") << QVariant(QRegularExpression(".*")); |
| QTest::newRow("qpointf") << QVariant(QPointF(42, 24)); |
| QTest::newRow("qvariantlist") << QVariant(QVariantList() << 42.24 << 5 << "hello"); |
| QTest::newRow("qvariantlist_point") << QVariant(QVariantList() << 42.24 << QPointF(42.24, 24.42) << QPointF(24.42, 42.24)); |
| QVariantMap vm; vm.insert("test", 55); vm.insert("abc", 42.42);; |
| QTest::newRow("qvariantmap") << QVariant(vm); |
| vm.clear(); vm.insert("point1", QPointF(42.24, 24.42)); vm.insert("point2", QPointF(42.24, 24.42)); |
| QTest::newRow("qvariantmap_point") << QVariant(vm); |
| QTest::newRow("qvariant") << QVariant(QVariant(42)); |
| QTest::newRow("QList<int>") << QVariant::fromValue(QList<int>() << 1 << 2 << 3 << 4); |
| QTest::newRow("QVector<int>") << QVariant::fromValue(QVector<int>() << 1 << 2 << 3 << 4); |
| QTest::newRow("QList<QString>") << QVariant::fromValue(QVector<QString>() << "1" << "2" << "3" << "4"); |
| QTest::newRow("QStringList") << QVariant::fromValue(QStringList() << "1" << "2" << "3" << "4"); |
| QTest::newRow("QMap<QString, QString>") << QVariant::fromValue(QMap<QString, QString>{{ "1", "2" }, { "3", "4" }}); |
| QTest::newRow("QHash<QString, QString>") << QVariant::fromValue(QHash<QString, QString>{{ "1", "2" }, { "3", "4" }}); |
| QTest::newRow("QMap<QString, QPointF>") << QVariant::fromValue(QMap<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }}); |
| QTest::newRow("QHash<QString, QPointF>") << QVariant::fromValue(QHash<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }}); |
| } |
| |
| void tst_QJSEngine::toScriptValue() |
| { |
| QFETCH(QVariant, input); |
| |
| QJSEngine engine; |
| QJSValue outputJS = engine.toScriptValue(input); |
| QVariant output = engine.fromScriptValue<QVariant>(outputJS); |
| |
| QCOMPARE(input, output); |
| } |
| |
| void tst_QJSEngine::toScriptValuenotroundtripped_data() |
| { |
| QTest::addColumn<QVariant>("input"); |
| QTest::addColumn<QVariant>("output"); |
| |
| QTest::newRow("QList<QObject*>") << QVariant::fromValue(QList<QObject*>() << this) << QVariant(QVariantList() << QVariant::fromValue(this)); |
| QTest::newRow("QObjectList") << QVariant::fromValue(QObjectList() << this) << QVariant(QVariantList() << QVariant::fromValue(this)); |
| QTest::newRow("QList<QPoint>") << QVariant::fromValue(QList<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); |
| QTest::newRow("QVector<QPoint>") << QVariant::fromValue(QVector<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); |
| QTest::newRow("VoidStar") << QVariant(int(QMetaType::VoidStar), nullptr) << QVariant(int(QMetaType::Nullptr), nullptr); |
| QTest::newRow("qregex") << QVariant(QRegExp(".*", Qt::CaseSensitive, QRegExp::RegExp2)) << QVariant(QRegularExpression(".*")); |
| } |
| |
| // This is almost the same as toScriptValue, but the inputs don't roundtrip to |
| // exactly the same value. |
| void tst_QJSEngine::toScriptValuenotroundtripped() |
| { |
| QFETCH(QVariant, input); |
| QFETCH(QVariant, output); |
| |
| QJSEngine engine; |
| QJSValue outputJS = engine.toScriptValue(input); |
| QVariant actualOutput = engine.fromScriptValue<QVariant>(outputJS); |
| |
| QCOMPARE(actualOutput, output); |
| } |
| |
| void tst_QJSEngine::newVariant() |
| { |
| QJSEngine eng; |
| { |
| QJSValue opaque = eng.toScriptValue(QVariant(QPoint(1, 2))); |
| QVERIFY(!opaque.isUndefined()); |
| QCOMPARE(opaque.isVariant(), false); |
| QVERIFY(!opaque.isCallable()); |
| QCOMPARE(opaque.isObject(), true); |
| QVERIFY(!opaque.prototype().isUndefined()); |
| QCOMPARE(opaque.prototype().isVariant(), false); |
| QVERIFY(opaque.property("valueOf").callWithInstance(opaque).equals(opaque)); |
| } |
| } |
| |
| void tst_QJSEngine::newVariant_valueOfToString() |
| { |
| // valueOf() and toString() |
| QJSEngine eng; |
| { |
| QJSValue object = eng.toScriptValue(QVariant(QPoint(10, 20))); |
| QJSValue value = object.property("valueOf").callWithInstance(object); |
| QVERIFY(value.isObject()); |
| QVERIFY(value.strictlyEquals(object)); |
| QCOMPARE(object.toString(), QString::fromLatin1("QPoint(10, 20)")); |
| } |
| } |
| |
| void tst_QJSEngine::newVariant_valueOfEnum() |
| { |
| QJSEngine eng; |
| { |
| QJSValue object = eng.toScriptValue(QVariant::fromValue(Qt::ControlModifier)); |
| QJSValue value = object.property("valueOf").callWithInstance(object); |
| QVERIFY(value.isNumber()); |
| QCOMPARE(value.toInt(), static_cast<qint32>(Qt::ControlModifier)); |
| } |
| } |
| |
| void tst_QJSEngine::newRegExp() |
| { |
| QJSEngine eng; |
| QJSValue rexps[] = { |
| eng.toScriptValue(QRegularExpression("foo")), |
| eng.toScriptValue(QRegExp("foo")) |
| }; |
| for (const auto &rexp : rexps) { |
| QVERIFY(!rexp.isUndefined()); |
| QCOMPARE(rexp.isRegExp(), true); |
| QCOMPARE(rexp.isObject(), true); |
| QCOMPARE(rexp.isCallable(), false); |
| // prototype should be RegExp.prototype |
| QVERIFY(!rexp.prototype().isUndefined()); |
| QCOMPARE(rexp.prototype().isObject(), true); |
| // Get [[Class]] internal property of RegExp Prototype Object. |
| // See ECMA-262 Section 8.6.2, "Object Internal Properties and Methods". |
| // See ECMA-262 Section 15.10.6, "Properties of the RegExp Prototype Object". |
| QJSValue r = eng.evaluate("Object.prototype.toString.call(RegExp.prototype)"); |
| QCOMPARE(r.toString(), QString::fromLatin1("[object Object]")); |
| QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true); |
| |
| QCOMPARE(qjsvalue_cast<QRegExp>(rexp).pattern(), QRegExp("foo").pattern()); |
| } |
| } |
| |
| void tst_QJSEngine::jsRegExp() |
| { |
| // See ECMA-262 Section 15.10, "RegExp Objects". |
| // These should really be JS-only tests, as they test the implementation's |
| // ECMA-compliance, not the C++ API. Compliance should already be covered |
| // by the Mozilla tests (qscriptjstestsuite). |
| // We can consider updating the expected results of this test if the |
| // RegExp implementation changes. |
| |
| QJSEngine eng; |
| QJSValue r = eng.evaluate("/foo/gim"); |
| QVERIFY(r.isRegExp()); |
| QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim")); |
| |
| QJSValue rxCtor = eng.globalObject().property("RegExp"); |
| QJSValue r2 = rxCtor.call(QJSValueList() << r); |
| QVERIFY(r2.isRegExp()); |
| QVERIFY(r2.strictlyEquals(r)); |
| |
| QJSValue r3 = rxCtor.call(QJSValueList() << r << "gim"); |
| QVERIFY(!r3.isError()); |
| |
| QJSValue r4 = rxCtor.call(QJSValueList() << "foo" << "gim"); |
| QVERIFY(r4.isRegExp()); |
| |
| QJSValue r5 = rxCtor.callAsConstructor(QJSValueList() << r); |
| QVERIFY(r5.isRegExp()); |
| QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim")); |
| QVERIFY(r5.strictlyEquals(r)); |
| |
| // See ECMA-262 Section 15.10.4.1, "new RegExp(pattern, flags)". |
| QJSValue r6 = rxCtor.callAsConstructor(QJSValueList() << "foo" << "bar"); |
| QVERIFY(r6.isError()); |
| QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag |
| |
| QJSValue r7 = eng.evaluate("/foo/gimp"); |
| QVERIFY(r7.isError()); |
| QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag |
| |
| QJSValue r8 = eng.evaluate("/foo/migmigmig"); |
| QVERIFY(r8.isError()); |
| QVERIFY(r8.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag |
| |
| QJSValue r9 = rxCtor.callAsConstructor(); |
| QVERIFY(r9.isRegExp()); |
| QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/")); |
| |
| QJSValue r10 = rxCtor.callAsConstructor(QJSValueList() << "" << "gim"); |
| QVERIFY(r10.isRegExp()); |
| QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim")); |
| |
| QJSValue r11 = rxCtor.callAsConstructor(QJSValueList() << "{1.*}" << "g"); |
| QVERIFY(r11.isRegExp()); |
| QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g")); |
| } |
| |
| void tst_QJSEngine::newDate() |
| { |
| QJSEngine eng; |
| |
| { |
| QJSValue date = eng.evaluate("new Date(0)"); |
| QVERIFY(!date.isUndefined()); |
| QCOMPARE(date.isDate(), true); |
| QCOMPARE(date.isObject(), true); |
| QVERIFY(!date.isCallable()); |
| } |
| |
| { |
| QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime); |
| QJSValue date = eng.toScriptValue(dt); |
| QVERIFY(!date.isUndefined()); |
| QCOMPARE(date.isDate(), true); |
| QCOMPARE(date.isObject(), true); |
| |
| QCOMPARE(date.toDateTime(), dt); |
| } |
| |
| { |
| QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC); |
| QJSValue date = eng.toScriptValue(dt); |
| // toDateTime() result should be in local time |
| QCOMPARE(date.toDateTime(), dt.toLocalTime()); |
| } |
| } |
| |
| void tst_QJSEngine::jsParseDate() |
| { |
| QJSEngine eng; |
| // Date.parse() should return NaN when it fails |
| { |
| QJSValue ret = eng.evaluate("Date.parse()"); |
| QVERIFY(ret.isNumber()); |
| QVERIFY(qIsNaN(ret.toNumber())); |
| } |
| |
| // Date.parse() should be able to parse the output of Date().toString() |
| { |
| QJSValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), true); |
| } |
| } |
| |
| void tst_QJSEngine::newQObject() |
| { |
| QJSEngine eng; |
| QObject temp; |
| |
| { |
| QJSValue qobject = eng.newQObject(nullptr); |
| QCOMPARE(qobject.isNull(), true); |
| QCOMPARE(qobject.isObject(), false); |
| QCOMPARE(qobject.toQObject(), (QObject *)nullptr); |
| } |
| { |
| QJSValue qobject = eng.newQObject(&temp); |
| QVERIFY(!qobject.isUndefined()); |
| QCOMPARE(qobject.isQObject(), true); |
| QCOMPARE(qobject.isObject(), true); |
| QCOMPARE(qobject.toQObject(), (QObject *)&temp); |
| QVERIFY(!qobject.isCallable()); |
| // prototype should be QObject.prototype |
| QCOMPARE(qobject.prototype().isObject(), true); |
| QEXPECT_FAIL("", "FIXME: newly created QObject's prototype is an JS Object", Continue); |
| QCOMPARE(qobject.prototype().isQObject(), true); |
| } |
| } |
| |
| void tst_QJSEngine::newQObjectRace() |
| { |
| class Thread : public QThread |
| { |
| void run() override |
| { |
| for (int i=0;i<100;++i) |
| { |
| QJSEngine e; |
| auto obj = e.newQObject(new QObject); |
| } |
| } |
| }; |
| |
| |
| Thread threads[8]; |
| for (auto& t : threads) |
| t.start(); // should not crash |
| for (auto& t : threads) |
| t.wait(); |
| } |
| |
| void tst_QJSEngine::newQObject_ownership() |
| { |
| QJSEngine eng; |
| { |
| QPointer<QObject> ptr = new QObject(); |
| QVERIFY(ptr != nullptr); |
| { |
| QJSValue v = eng.newQObject(ptr); |
| } |
| eng.collectGarbage(); |
| if (ptr) |
| QGuiApplication::sendPostedEvents(ptr, QEvent::DeferredDelete); |
| QVERIFY(ptr.isNull()); |
| } |
| { |
| QPointer<QObject> ptr = new QObject(this); |
| QVERIFY(ptr != nullptr); |
| { |
| QJSValue v = eng.newQObject(ptr); |
| } |
| QObject *before = ptr; |
| eng.collectGarbage(); |
| QCOMPARE(ptr.data(), before); |
| delete ptr; |
| } |
| { |
| QObject *parent = new QObject(); |
| QObject *child = new QObject(parent); |
| QJSValue v = eng.newQObject(child); |
| QCOMPARE(v.toQObject(), child); |
| delete parent; |
| QCOMPARE(v.toQObject(), (QObject *)nullptr); |
| } |
| { |
| QPointer<QObject> ptr = new QObject(); |
| QVERIFY(ptr != nullptr); |
| { |
| QJSValue v = eng.newQObject(ptr); |
| } |
| eng.collectGarbage(); |
| // no parent, so it should be like ScriptOwnership |
| if (ptr) |
| QGuiApplication::sendPostedEvents(ptr, QEvent::DeferredDelete); |
| QVERIFY(ptr.isNull()); |
| } |
| { |
| QObject *parent = new QObject(); |
| QPointer<QObject> child = new QObject(parent); |
| QVERIFY(child != nullptr); |
| { |
| QJSValue v = eng.newQObject(child); |
| } |
| eng.collectGarbage(); |
| // has parent, so it should be like QtOwnership |
| QVERIFY(child != nullptr); |
| delete parent; |
| } |
| { |
| QPointer<QObject> ptr = new QObject(); |
| QVERIFY(ptr != nullptr); |
| { |
| QQmlEngine::setObjectOwnership(ptr.data(), QQmlEngine::CppOwnership); |
| QJSValue v = eng.newQObject(ptr); |
| } |
| eng.collectGarbage(); |
| if (ptr) |
| QGuiApplication::sendPostedEvents(ptr, QEvent::DeferredDelete); |
| QVERIFY(!ptr.isNull()); |
| delete ptr.data(); |
| } |
| } |
| |
| void tst_QJSEngine::newQObject_deletedEngine() |
| { |
| QJSValue object; |
| QObject *ptr = new QObject(); |
| QSignalSpy spy(ptr, SIGNAL(destroyed())); |
| { |
| QJSEngine engine; |
| object = engine.newQObject(ptr); |
| engine.globalObject().setProperty("obj", object); |
| } |
| QTRY_VERIFY(spy.count()); |
| } |
| |
| class TestQMetaObject : public QObject { |
| Q_OBJECT |
| Q_PROPERTY(int called READ called) |
| public: |
| enum Enum1 { |
| Zero = 0, |
| One, |
| Two |
| }; |
| enum Enum2 { |
| A = 0, |
| B, |
| C |
| }; |
| Q_ENUMS(Enum1 Enum2) |
| |
| Q_INVOKABLE TestQMetaObject() {} |
| Q_INVOKABLE TestQMetaObject(int) |
| : m_called(2) {} |
| Q_INVOKABLE TestQMetaObject(QString) |
| : m_called(3) {} |
| Q_INVOKABLE TestQMetaObject(QString, int) |
| : m_called(4) {} |
| int called() const { |
| return m_called; |
| } |
| private: |
| int m_called = 1; |
| }; |
| |
| void tst_QJSEngine::newQObjectPropertyCache() |
| { |
| QScopedPointer<QObject> obj(new QObject); |
| QQmlEngine::setObjectOwnership(obj.data(), QQmlEngine::CppOwnership); |
| |
| { |
| QJSEngine engine; |
| engine.newQObject(obj.data()); |
| QVERIFY(QQmlData::get(obj.data())->propertyCache); |
| } |
| QVERIFY(!QQmlData::get(obj.data())->propertyCache); |
| } |
| |
| void tst_QJSEngine::newQMetaObject() { |
| { |
| QJSEngine engine; |
| QJSValue metaObject = engine.newQMetaObject(&TestQMetaObject::staticMetaObject); |
| QCOMPARE(metaObject.isNull(), false); |
| QCOMPARE(metaObject.isObject(), true); |
| QCOMPARE(metaObject.isQObject(), false); |
| QCOMPARE(metaObject.isCallable(), true); |
| QCOMPARE(metaObject.isQMetaObject(), true); |
| |
| QCOMPARE(metaObject.toQMetaObject(), &TestQMetaObject::staticMetaObject); |
| |
| QVERIFY(metaObject.strictlyEquals(engine.newQMetaObject<TestQMetaObject>())); |
| |
| |
| { |
| auto result = metaObject.callAsConstructor(); |
| if (result.isError()) |
| qDebug() << result.toString(); |
| QCOMPARE(result.isError(), false); |
| QCOMPARE(result.isNull(), false); |
| QCOMPARE(result.isObject(), true); |
| QCOMPARE(result.isQObject(), true); |
| QVERIFY(result.property("constructor").strictlyEquals(metaObject)); |
| QVERIFY(result.prototype().strictlyEquals(metaObject)); |
| |
| |
| QCOMPARE(result.property("called").toInt(), 1); |
| |
| } |
| |
| QJSValue integer(42); |
| QJSValue string("foo"); |
| |
| { |
| auto result = metaObject.callAsConstructor({integer}); |
| QCOMPARE(result.property("called").toInt(), 2); |
| } |
| |
| { |
| auto result = metaObject.callAsConstructor({string}); |
| QCOMPARE(result.property("called").toInt(), 3); |
| } |
| |
| { |
| auto result = metaObject.callAsConstructor({string, integer}); |
| QCOMPARE(result.property("called").toInt(), 4); |
| } |
| } |
| |
| { |
| QJSEngine engine; |
| QJSValue metaObject = engine.newQMetaObject(&TestQMetaObject::staticMetaObject); |
| QCOMPARE(metaObject.property("Zero").toInt(), 0); |
| QCOMPARE(metaObject.property("One").toInt(), 1); |
| QCOMPARE(metaObject.property("Two").toInt(), 2); |
| QCOMPARE(metaObject.property("A").toInt(), 0); |
| QCOMPARE(metaObject.property("B").toInt(), 1); |
| QCOMPARE(metaObject.property("C").toInt(), 2); |
| } |
| |
| } |
| |
| void tst_QJSEngine::exceptionInSlot() |
| { |
| QJSEngine engine; |
| QJSValue wrappedThis = engine.newQObject(this); |
| QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); |
| engine.globalObject().setProperty("testCase", wrappedThis); |
| engine.evaluate( |
| "var called = false\n" |
| "function throwingSlot() {\n" |
| " called = true\n" |
| " throw 42;\n" |
| "}\n" |
| "testCase.testSignal.connect(throwingSlot)\n" |
| ); |
| QCOMPARE(engine.globalObject().property("called").toBool(), false); |
| emit testSignal(); |
| QCOMPARE(engine.globalObject().property("called").toBool(), true); |
| } |
| |
| void tst_QJSEngine::globalObjectProperties() |
| { |
| // See ECMA-262 Section 15.1, "The Global Object". |
| |
| QJSEngine eng; |
| QJSValue global = eng.globalObject(); |
| |
| QVERIFY(global.property("NaN").isNumber()); |
| QVERIFY(qIsNaN(global.property("NaN").toNumber())); |
| |
| QVERIFY(global.property("Infinity").isNumber()); |
| QVERIFY(qIsInf(global.property("Infinity").toNumber())); |
| |
| QVERIFY(global.property("undefined").isUndefined()); |
| |
| QVERIFY(global.property("eval").isCallable()); |
| |
| QVERIFY(global.property("parseInt").isCallable()); |
| |
| QVERIFY(global.property("parseFloat").isCallable()); |
| |
| QVERIFY(global.property("isNaN").isCallable()); |
| |
| QVERIFY(global.property("isFinite").isCallable()); |
| |
| QVERIFY(global.property("decodeURI").isCallable()); |
| |
| QVERIFY(global.property("decodeURIComponent").isCallable()); |
| |
| QVERIFY(global.property("encodeURI").isCallable()); |
| |
| QVERIFY(global.property("encodeURIComponent").isCallable()); |
| |
| QVERIFY(global.property("Object").isCallable()); |
| QVERIFY(global.property("Function").isCallable()); |
| QVERIFY(global.property("Array").isCallable()); |
| QVERIFY(global.property("String").isCallable()); |
| QVERIFY(global.property("Boolean").isCallable()); |
| QVERIFY(global.property("Number").isCallable()); |
| QVERIFY(global.property("Date").isCallable()); |
| QVERIFY(global.property("RegExp").isCallable()); |
| QVERIFY(global.property("Error").isCallable()); |
| QVERIFY(global.property("EvalError").isCallable()); |
| QVERIFY(global.property("RangeError").isCallable()); |
| QVERIFY(global.property("ReferenceError").isCallable()); |
| QVERIFY(global.property("SyntaxError").isCallable()); |
| QVERIFY(global.property("TypeError").isCallable()); |
| QVERIFY(global.property("URIError").isCallable()); |
| QVERIFY(global.property("Math").isObject()); |
| QVERIFY(!global.property("Math").isCallable()); |
| } |
| |
| void tst_QJSEngine::globalObjectEquals() |
| { |
| QJSEngine eng; |
| QJSValue o = eng.globalObject(); |
| QVERIFY(o.strictlyEquals(eng.globalObject())); |
| QVERIFY(o.equals(eng.globalObject())); |
| } |
| |
| void tst_QJSEngine::globalObjectProperties_enumerate() |
| { |
| QJSEngine eng; |
| QJSValue global = eng.globalObject(); |
| |
| QSet<QString> expectedNames; |
| expectedNames |
| << "isNaN" |
| << "parseFloat" |
| << "String" |
| << "EvalError" |
| << "URIError" |
| << "Math" |
| << "encodeURIComponent" |
| << "RangeError" |
| << "eval" |
| << "isFinite" |
| << "ReferenceError" |
| << "Infinity" |
| << "Function" |
| << "RegExp" |
| << "Number" |
| << "parseInt" |
| << "Object" |
| << "decodeURI" |
| << "TypeError" |
| << "Boolean" |
| << "encodeURI" |
| << "NaN" |
| << "Error" |
| << "decodeURIComponent" |
| << "Date" |
| << "Array" |
| << "Symbol" |
| << "escape" |
| << "unescape" |
| << "SyntaxError" |
| << "undefined" |
| << "JSON" |
| << "ArrayBuffer" |
| << "SharedArrayBuffer" |
| << "DataView" |
| << "Int8Array" |
| << "Uint8Array" |
| << "Uint8ClampedArray" |
| << "Int16Array" |
| << "Uint16Array" |
| << "Int32Array" |
| << "Uint32Array" |
| << "Float32Array" |
| << "Float64Array" |
| << "WeakSet" |
| << "Set" |
| << "WeakMap" |
| << "Map" |
| << "Reflect" |
| << "Proxy" |
| << "Atomics" |
| << "Promise" |
| ; |
| QSet<QString> actualNames; |
| { |
| QJSValueIterator it(global); |
| while (it.hasNext()) { |
| it.next(); |
| actualNames.insert(it.name()); |
| } |
| } |
| |
| QSet<QString> remainingNames = actualNames; |
| { |
| QSet<QString>::const_iterator it; |
| for (it = expectedNames.constBegin(); it != expectedNames.constEnd(); ++it) { |
| QString name = *it; |
| QVERIFY(actualNames.contains(name)); |
| remainingNames.remove(name); |
| } |
| } |
| QVERIFY(remainingNames.isEmpty()); |
| } |
| |
| void tst_QJSEngine::createGlobalObjectProperty() |
| { |
| QJSEngine eng; |
| QJSValue global = eng.globalObject(); |
| // create property with no attributes |
| { |
| QString name = QString::fromLatin1("foo"); |
| QVERIFY(global.property(name).isUndefined()); |
| QJSValue val(123); |
| global.setProperty(name, val); |
| QVERIFY(global.property(name).equals(val)); |
| global.deleteProperty(name); |
| QVERIFY(global.property(name).isUndefined()); |
| } |
| } |
| |
| void tst_QJSEngine::globalObjectWithCustomPrototype() |
| { |
| QJSEngine engine; |
| QJSValue proto = engine.newObject(); |
| proto.setProperty("protoProperty", 123); |
| QJSValue global = engine.globalObject(); |
| QJSValue originalProto = global.prototype(); |
| global.setPrototype(proto); |
| { |
| QJSValue ret = engine.evaluate("protoProperty"); |
| QVERIFY(ret.isNumber()); |
| QVERIFY(ret.strictlyEquals(global.property("protoProperty"))); |
| } |
| { |
| QJSValue ret = engine.evaluate("this.protoProperty"); |
| QVERIFY(ret.isNumber()); |
| QVERIFY(ret.strictlyEquals(global.property("protoProperty"))); |
| } |
| { |
| QJSValue ret = engine.evaluate("this.hasOwnProperty('protoProperty')"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(!ret.toBool()); |
| } |
| |
| // Custom prototype set from JS |
| { |
| QJSValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a"); |
| QVERIFY(ret.isNumber()); |
| QVERIFY(ret.strictlyEquals(global.property("a"))); |
| } |
| } |
| |
| void tst_QJSEngine::builtinFunctionNames_data() |
| { |
| QTest::addColumn<QString>("expression"); |
| QTest::addColumn<QString>("expectedName"); |
| |
| // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects". |
| |
| QTest::newRow("parseInt") << QString("parseInt") << QString("parseInt"); |
| QTest::newRow("parseFloat") << QString("parseFloat") << QString("parseFloat"); |
| QTest::newRow("isNaN") << QString("isNaN") << QString("isNaN"); |
| QTest::newRow("isFinite") << QString("isFinite") << QString("isFinite"); |
| QTest::newRow("decodeURI") << QString("decodeURI") << QString("decodeURI"); |
| QTest::newRow("decodeURIComponent") << QString("decodeURIComponent") << QString("decodeURIComponent"); |
| QTest::newRow("encodeURI") << QString("encodeURI") << QString("encodeURI"); |
| QTest::newRow("encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent"); |
| QTest::newRow("escape") << QString("escape") << QString("escape"); |
| QTest::newRow("unescape") << QString("unescape") << QString("unescape"); |
| |
| QTest::newRow("Array") << QString("Array") << QString("Array"); |
| QTest::newRow("Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString"); |
| QTest::newRow("Array.prototype.toLocaleString") << QString("Array.prototype.toLocaleString") << QString("toLocaleString"); |
| QTest::newRow("Array.prototype.concat") << QString("Array.prototype.concat") << QString("concat"); |
| QTest::newRow("Array.prototype.find") << QString("Array.prototype.find") << QString("find"); |
| QTest::newRow("Array.prototype.findIndex") << QString("Array.prototype.findIndex") << QString("findIndex"); |
| QTest::newRow("Array.prototype.join") << QString("Array.prototype.join") << QString("join"); |
| QTest::newRow("Array.prototype.pop") << QString("Array.prototype.pop") << QString("pop"); |
| QTest::newRow("Array.prototype.push") << QString("Array.prototype.push") << QString("push"); |
| QTest::newRow("Array.prototype.reverse") << QString("Array.prototype.reverse") << QString("reverse"); |
| QTest::newRow("Array.prototype.shift") << QString("Array.prototype.shift") << QString("shift"); |
| QTest::newRow("Array.prototype.slice") << QString("Array.prototype.slice") << QString("slice"); |
| QTest::newRow("Array.prototype.sort") << QString("Array.prototype.sort") << QString("sort"); |
| QTest::newRow("Array.prototype.splice") << QString("Array.prototype.splice") << QString("splice"); |
| QTest::newRow("Array.prototype.unshift") << QString("Array.prototype.unshift") << QString("unshift"); |
| |
| QTest::newRow("Boolean") << QString("Boolean") << QString("Boolean"); |
| QTest::newRow("Boolean.prototype.toString") << QString("Boolean.prototype.toString") << QString("toString"); |
| |
| QTest::newRow("Date") << QString("Date") << QString("Date"); |
| QTest::newRow("Date.prototype.toString") << QString("Date.prototype.toString") << QString("toString"); |
| QTest::newRow("Date.prototype.toDateString") << QString("Date.prototype.toDateString") << QString("toDateString"); |
| QTest::newRow("Date.prototype.toTimeString") << QString("Date.prototype.toTimeString") << QString("toTimeString"); |
| QTest::newRow("Date.prototype.toLocaleString") << QString("Date.prototype.toLocaleString") << QString("toLocaleString"); |
| QTest::newRow("Date.prototype.toLocaleDateString") << QString("Date.prototype.toLocaleDateString") << QString("toLocaleDateString"); |
| QTest::newRow("Date.prototype.toLocaleTimeString") << QString("Date.prototype.toLocaleTimeString") << QString("toLocaleTimeString"); |
| QTest::newRow("Date.prototype.valueOf") << QString("Date.prototype.valueOf") << QString("valueOf"); |
| QTest::newRow("Date.prototype.getTime") << QString("Date.prototype.getTime") << QString("getTime"); |
| QTest::newRow("Date.prototype.getYear") << QString("Date.prototype.getYear") << QString("getYear"); |
| QTest::newRow("Date.prototype.getFullYear") << QString("Date.prototype.getFullYear") << QString("getFullYear"); |
| QTest::newRow("Date.prototype.getUTCFullYear") << QString("Date.prototype.getUTCFullYear") << QString("getUTCFullYear"); |
| QTest::newRow("Date.prototype.getMonth") << QString("Date.prototype.getMonth") << QString("getMonth"); |
| QTest::newRow("Date.prototype.getUTCMonth") << QString("Date.prototype.getUTCMonth") << QString("getUTCMonth"); |
| QTest::newRow("Date.prototype.getDate") << QString("Date.prototype.getDate") << QString("getDate"); |
| QTest::newRow("Date.prototype.getUTCDate") << QString("Date.prototype.getUTCDate") << QString("getUTCDate"); |
| QTest::newRow("Date.prototype.getDay") << QString("Date.prototype.getDay") << QString("getDay"); |
| QTest::newRow("Date.prototype.getUTCDay") << QString("Date.prototype.getUTCDay") << QString("getUTCDay"); |
| QTest::newRow("Date.prototype.getHours") << QString("Date.prototype.getHours") << QString("getHours"); |
| QTest::newRow("Date.prototype.getUTCHours") << QString("Date.prototype.getUTCHours") << QString("getUTCHours"); |
| QTest::newRow("Date.prototype.getMinutes") << QString("Date.prototype.getMinutes") << QString("getMinutes"); |
| QTest::newRow("Date.prototype.getUTCMinutes") << QString("Date.prototype.getUTCMinutes") << QString("getUTCMinutes"); |
| QTest::newRow("Date.prototype.getSeconds") << QString("Date.prototype.getSeconds") << QString("getSeconds"); |
| QTest::newRow("Date.prototype.getUTCSeconds") << QString("Date.prototype.getUTCSeconds") << QString("getUTCSeconds"); |
| QTest::newRow("Date.prototype.getMilliseconds") << QString("Date.prototype.getMilliseconds") << QString("getMilliseconds"); |
| QTest::newRow("Date.prototype.getUTCMilliseconds") << QString("Date.prototype.getUTCMilliseconds") << QString("getUTCMilliseconds"); |
| QTest::newRow("Date.prototype.getTimezoneOffset") << QString("Date.prototype.getTimezoneOffset") << QString("getTimezoneOffset"); |
| QTest::newRow("Date.prototype.setTime") << QString("Date.prototype.setTime") << QString("setTime"); |
| QTest::newRow("Date.prototype.setMilliseconds") << QString("Date.prototype.setMilliseconds") << QString("setMilliseconds"); |
| QTest::newRow("Date.prototype.setUTCMilliseconds") << QString("Date.prototype.setUTCMilliseconds") << QString("setUTCMilliseconds"); |
| QTest::newRow("Date.prototype.setSeconds") << QString("Date.prototype.setSeconds") << QString("setSeconds"); |
| QTest::newRow("Date.prototype.setUTCSeconds") << QString("Date.prototype.setUTCSeconds") << QString("setUTCSeconds"); |
| QTest::newRow("Date.prototype.setMinutes") << QString("Date.prototype.setMinutes") << QString("setMinutes"); |
| QTest::newRow("Date.prototype.setUTCMinutes") << QString("Date.prototype.setUTCMinutes") << QString("setUTCMinutes"); |
| QTest::newRow("Date.prototype.setHours") << QString("Date.prototype.setHours") << QString("setHours"); |
| QTest::newRow("Date.prototype.setUTCHours") << QString("Date.prototype.setUTCHours") << QString("setUTCHours"); |
| QTest::newRow("Date.prototype.setDate") << QString("Date.prototype.setDate") << QString("setDate"); |
| QTest::newRow("Date.prototype.setUTCDate") << QString("Date.prototype.setUTCDate") << QString("setUTCDate"); |
| QTest::newRow("Date.prototype.setMonth") << QString("Date.prototype.setMonth") << QString("setMonth"); |
| QTest::newRow("Date.prototype.setUTCMonth") << QString("Date.prototype.setUTCMonth") << QString("setUTCMonth"); |
| QTest::newRow("Date.prototype.setYear") << QString("Date.prototype.setYear") << QString("setYear"); |
| QTest::newRow("Date.prototype.setFullYear") << QString("Date.prototype.setFullYear") << QString("setFullYear"); |
| QTest::newRow("Date.prototype.setUTCFullYear") << QString("Date.prototype.setUTCFullYear") << QString("setUTCFullYear"); |
| QTest::newRow("Date.prototype.toUTCString") << QString("Date.prototype.toUTCString") << QString("toUTCString"); |
| QTest::newRow("Date.prototype.toGMTString") << QString("Date.prototype.toGMTString") << QString("toUTCString"); // yes, this is per spec |
| |
| QTest::newRow("Error") << QString("Error") << QString("Error"); |
| // QTest::newRow("Error.prototype.backtrace") << QString("Error.prototype.backtrace") << QString("backtrace"); |
| QTest::newRow("Error.prototype.toString") << QString("Error.prototype.toString") << QString("toString"); |
| |
| QTest::newRow("EvalError") << QString("EvalError") << QString("EvalError"); |
| QTest::newRow("RangeError") << QString("RangeError") << QString("RangeError"); |
| QTest::newRow("ReferenceError") << QString("ReferenceError") << QString("ReferenceError"); |
| QTest::newRow("SyntaxError") << QString("SyntaxError") << QString("SyntaxError"); |
| QTest::newRow("TypeError") << QString("TypeError") << QString("TypeError"); |
| QTest::newRow("URIError") << QString("URIError") << QString("URIError"); |
| |
| QTest::newRow("Function") << QString("Function") << QString("Function"); |
| QTest::newRow("Function.prototype.toString") << QString("Function.prototype.toString") << QString("toString"); |
| QTest::newRow("Function.prototype.apply") << QString("Function.prototype.apply") << QString("apply"); |
| QTest::newRow("Function.prototype.call") << QString("Function.prototype.call") << QString("call"); |
| /* In V8, those function are only there for signals |
| QTest::newRow("Function.prototype.connect") << QString("Function.prototype.connect") << QString("connect"); |
| QTest::newRow("Function.prototype.disconnect") << QString("Function.prototype.disconnect") << QString("disconnect");*/ |
| |
| QTest::newRow("Math.abs") << QString("Math.abs") << QString("abs"); |
| QTest::newRow("Math.acos") << QString("Math.acos") << QString("acos"); |
| QTest::newRow("Math.asin") << QString("Math.asin") << QString("asin"); |
| QTest::newRow("Math.atan") << QString("Math.atan") << QString("atan"); |
| QTest::newRow("Math.atan2") << QString("Math.atan2") << QString("atan2"); |
| QTest::newRow("Math.ceil") << QString("Math.ceil") << QString("ceil"); |
| QTest::newRow("Math.cos") << QString("Math.cos") << QString("cos"); |
| QTest::newRow("Math.exp") << QString("Math.exp") << QString("exp"); |
| QTest::newRow("Math.floor") << QString("Math.floor") << QString("floor"); |
| QTest::newRow("Math.log") << QString("Math.log") << QString("log"); |
| QTest::newRow("Math.max") << QString("Math.max") << QString("max"); |
| QTest::newRow("Math.min") << QString("Math.min") << QString("min"); |
| QTest::newRow("Math.pow") << QString("Math.pow") << QString("pow"); |
| QTest::newRow("Math.random") << QString("Math.random") << QString("random"); |
| QTest::newRow("Math.round") << QString("Math.round") << QString("round"); |
| QTest::newRow("Math.sign") << QString("Math.sign") << QString("sign"); |
| QTest::newRow("Math.sin") << QString("Math.sin") << QString("sin"); |
| QTest::newRow("Math.sqrt") << QString("Math.sqrt") << QString("sqrt"); |
| QTest::newRow("Math.tan") << QString("Math.tan") << QString("tan"); |
| |
| QTest::newRow("Number") << QString("Number") << QString("Number"); |
| QTest::newRow("Number.isFinite") << QString("Number.isFinite") << QString("isFinite"); |
| QTest::newRow("Number.isNaN") << QString("Number.isNaN") << QString("isNaN"); |
| QTest::newRow("Number.prototype.toString") << QString("Number.prototype.toString") << QString("toString"); |
| QTest::newRow("Number.prototype.toLocaleString") << QString("Number.prototype.toLocaleString") << QString("toLocaleString"); |
| QTest::newRow("Number.prototype.valueOf") << QString("Number.prototype.valueOf") << QString("valueOf"); |
| QTest::newRow("Number.prototype.toFixed") << QString("Number.prototype.toFixed") << QString("toFixed"); |
| QTest::newRow("Number.prototype.toExponential") << QString("Number.prototype.toExponential") << QString("toExponential"); |
| QTest::newRow("Number.prototype.toPrecision") << QString("Number.prototype.toPrecision") << QString("toPrecision"); |
| |
| QTest::newRow("Object") << QString("Object") << QString("Object"); |
| QTest::newRow("Object.prototype.toString") << QString("Object.prototype.toString") << QString("toString"); |
| QTest::newRow("Object.prototype.toLocaleString") << QString("Object.prototype.toLocaleString") << QString("toLocaleString"); |
| QTest::newRow("Object.prototype.valueOf") << QString("Object.prototype.valueOf") << QString("valueOf"); |
| QTest::newRow("Object.prototype.hasOwnProperty") << QString("Object.prototype.hasOwnProperty") << QString("hasOwnProperty"); |
| QTest::newRow("Object.prototype.isPrototypeOf") << QString("Object.prototype.isPrototypeOf") << QString("isPrototypeOf"); |
| QTest::newRow("Object.prototype.propertyIsEnumerable") << QString("Object.prototype.propertyIsEnumerable") << QString("propertyIsEnumerable"); |
| QTest::newRow("Object.prototype.__defineGetter__") << QString("Object.prototype.__defineGetter__") << QString("__defineGetter__"); |
| QTest::newRow("Object.prototype.__defineSetter__") << QString("Object.prototype.__defineSetter__") << QString("__defineSetter__"); |
| |
| QTest::newRow("RegExp") << QString("RegExp") << QString("RegExp"); |
| QTest::newRow("RegExp.prototype.exec") << QString("RegExp.prototype.exec") << QString("exec"); |
| QTest::newRow("RegExp.prototype.test") << QString("RegExp.prototype.test") << QString("test"); |
| QTest::newRow("RegExp.prototype.toString") << QString("RegExp.prototype.toString") << QString("toString"); |
| |
| QTest::newRow("String") << QString("String") << QString("String"); |
| QTest::newRow("String.prototype.toString") << QString("String.prototype.toString") << QString("toString"); |
| QTest::newRow("String.prototype.valueOf") << QString("String.prototype.valueOf") << QString("valueOf"); |
| QTest::newRow("String.prototype.charAt") << QString("String.prototype.charAt") << QString("charAt"); |
| QTest::newRow("String.prototype.charCodeAt") << QString("String.prototype.charCodeAt") << QString("charCodeAt"); |
| QTest::newRow("String.prototype.concat") << QString("String.prototype.concat") << QString("concat"); |
| QTest::newRow("String.prototype.endsWith") << QString("String.prototype.endsWith") << QString("endsWith"); |
| QTest::newRow("String.prototype.includes") << QString("String.prototype.includes") << QString("includes"); |
| QTest::newRow("String.prototype.indexOf") << QString("String.prototype.indexOf") << QString("indexOf"); |
| QTest::newRow("String.prototype.lastIndexOf") << QString("String.prototype.lastIndexOf") << QString("lastIndexOf"); |
| QTest::newRow("String.prototype.localeCompare") << QString("String.prototype.localeCompare") << QString("localeCompare"); |
| QTest::newRow("String.prototype.match") << QString("String.prototype.match") << QString("match"); |
| QTest::newRow("String.prototype.repeat") << QString("String.prototype.repeat") << QString("repeat"); |
| QTest::newRow("String.prototype.replace") << QString("String.prototype.replace") << QString("replace"); |
| QTest::newRow("String.prototype.search") << QString("String.prototype.search") << QString("search"); |
| QTest::newRow("String.prototype.slice") << QString("String.prototype.slice") << QString("slice"); |
| QTest::newRow("String.prototype.split") << QString("String.prototype.split") << QString("split"); |
| QTest::newRow("String.prototype.startsWith") << QString("String.prototype.startsWith") << QString("startsWith"); |
| QTest::newRow("String.prototype.substring") << QString("String.prototype.substring") << QString("substring"); |
| QTest::newRow("String.prototype.toLowerCase") << QString("String.prototype.toLowerCase") << QString("toLowerCase"); |
| QTest::newRow("String.prototype.toLocaleLowerCase") << QString("String.prototype.toLocaleLowerCase") << QString("toLocaleLowerCase"); |
| QTest::newRow("String.prototype.toUpperCase") << QString("String.prototype.toUpperCase") << QString("toUpperCase"); |
| QTest::newRow("String.prototype.toLocaleUpperCase") << QString("String.prototype.toLocaleUpperCase") << QString("toLocaleUpperCase"); |
| } |
| |
| void tst_QJSEngine::builtinFunctionNames() |
| { |
| QFETCH(QString, expression); |
| QFETCH(QString, expectedName); |
| QJSEngine eng; |
| // The "name" property is actually non-standard, but JSC supports it. |
| QJSValue ret = eng.evaluate(QString::fromLatin1("%0.name").arg(expression)); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), expectedName); |
| } |
| |
| void tst_QJSEngine::evaluate_data() |
| { |
| QTest::addColumn<QString>("code"); |
| QTest::addColumn<int>("lineNumber"); |
| QTest::addColumn<bool>("expectHadError"); |
| QTest::addColumn<int>("expectErrorLineNumber"); |
| |
| QTest::newRow("(newline)") << QString("\n") << -1 << false << -1; |
| QTest::newRow("0 //") << QString("0 //") << -1 << false << -1; |
| QTest::newRow("/* */") << QString("/* */") << -1 << false << -1; |
| QTest::newRow("//") << QString("//") << -1 << false << -1; |
| QTest::newRow("(spaces)") << QString(" ") << -1 << false << -1; |
| QTest::newRow("(empty)") << QString("") << -1 << false << -1; |
| QTest::newRow("0") << QString("0") << -1 << false << -1; |
| QTest::newRow("0=1") << QString("\n0=1;\n") << -1 << true << 2; |
| QTest::newRow("a=1") << QString("a=1\n") << -1 << false << -1; |
| QTest::newRow("a=1;K") << QString("a=1;\nK") << -1 << true << 2; |
| |
| QTest::newRow("f()") << QString("function f()\n" |
| "{\n" |
| " var a;\n" |
| " var b=\";\n" // here's the error |
| "}\n" |
| "f();\n") |
| << -1 << true << 4; |
| |
| QTest::newRow("0") << QString("0") << 10 << false << -1; |
| QTest::newRow("0=1") << QString("\n\n0=1\n") << 10 << true << 12; |
| QTest::newRow("a=1") << QString("a=1\n") << 10 << false << -1; |
| QTest::newRow("a=1;K") << QString("a=1;\n\nK") << 10 << true << 12; |
| |
| QTest::newRow("f()") << QString("function f()\n" |
| "{\n" |
| " var a;\n" |
| "\n\n" |
| " var b=\";\n" // here's the error |
| "}\n" |
| "f();\n") |
| << 10 << true << 15; |
| QTest::newRow("functionThatDoesntExist()") |
| << QString(";\n;\n;\nfunctionThatDoesntExist()") |
| << -1 << true << 4; |
| QTest::newRow("for (var p in this) { continue labelThatDoesntExist; }") |
| << QString("for (var p in this) {\ncontinue labelThatDoesntExist; }") |
| << 4 << true << 5; |
| QTest::newRow("duplicateLabel: { duplicateLabel: ; }") |
| << QString("duplicateLabel: { duplicateLabel: ; }") |
| << 12 << true << 12; |
| |
| QTest::newRow("/=/") << QString("/=/") << -1 << false << -1; |
| QTest::newRow("/=/g") << QString("/=/g") << -1 << false << -1; |
| QTest::newRow("/a/") << QString("/a/") << -1 << false << -1; |
| QTest::newRow("/a/g") << QString("/a/g") << -1 << false << -1; |
| QTest::newRow("/a/gim") << QString("/a/gim") << -1 << false << -1; |
| QTest::newRow("/a/gimp") << QString("/a/gimp") << 1 << true << 1; |
| QTest::newRow("empty-array-concat") << QString("var a = []; var b = [1]; var c = a.concat(b); ") << 1 << false << -1; |
| QTest::newRow("object-literal") << QString("var a = {\"0\":\"#\",\"2\":\"#\",\"5\":\"#\",\"8\":\"#\",\"6\":\"#\",\"12\":\"#\",\"13\":\"#\",\"16\":\"#\",\"18\":\"#\",\"39\":\"#\",\"40\":\"#\"}") << 1 << false << -1; |
| } |
| |
| void tst_QJSEngine::evaluate() |
| { |
| QFETCH(QString, code); |
| QFETCH(int, lineNumber); |
| QFETCH(bool, expectHadError); |
| QFETCH(int, expectErrorLineNumber); |
| |
| QJSEngine eng; |
| QJSValue ret; |
| if (lineNumber != -1) |
| ret = eng.evaluate(code, /*fileName =*/QString(), lineNumber); |
| else |
| ret = eng.evaluate(code); |
| QCOMPARE(ret.isError(), expectHadError); |
| if (ret.isError()) { |
| QVERIFY(ret.property("lineNumber").strictlyEquals(eng.toScriptValue(expectErrorLineNumber))); |
| } |
| } |
| |
| void tst_QJSEngine::errorMessage_QT679() |
| { |
| QJSEngine engine; |
| engine.globalObject().setProperty("foo", 15); |
| QJSValue error = engine.evaluate("'hello world';\nfoo.bar.blah"); |
| QVERIFY(error.isError()); |
| QVERIFY(error.toString().startsWith(QString::fromLatin1("TypeError: "))); |
| } |
| |
| struct Foo { |
| public: |
| int x = -1, y = -1; |
| Foo() {} |
| }; |
| |
| Q_DECLARE_METATYPE(Foo) |
| Q_DECLARE_METATYPE(Foo*) |
| |
| Q_DECLARE_METATYPE(QList<Foo>) |
| Q_DECLARE_METATYPE(QVector<QChar>) |
| Q_DECLARE_METATYPE(QStack<int>) |
| Q_DECLARE_METATYPE(QQueue<char>) |
| |
| void tst_QJSEngine::valueConversion_basic() |
| { |
| QJSEngine eng; |
| { |
| QJSValue num = eng.toScriptValue(123); |
| QCOMPARE(num.isNumber(), true); |
| QCOMPARE(num.strictlyEquals(eng.toScriptValue(123)), true); |
| |
| int inum = eng.fromScriptValue<int>(num); |
| QCOMPARE(inum, 123); |
| |
| QString snum = eng.fromScriptValue<QString>(num); |
| QCOMPARE(snum, QLatin1String("123")); |
| } |
| { |
| QJSValue num = eng.toScriptValue(123); |
| QCOMPARE(num.isNumber(), true); |
| QCOMPARE(num.strictlyEquals(eng.toScriptValue(123)), true); |
| |
| int inum = eng.fromScriptValue<int>(num); |
| QCOMPARE(inum, 123); |
| |
| QString snum = eng.fromScriptValue<QString>(num); |
| QCOMPARE(snum, QLatin1String("123")); |
| } |
| { |
| QJSValue num = eng.toScriptValue(123); |
| QCOMPARE(eng.fromScriptValue<char>(num), char(123)); |
| QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123)); |
| QCOMPARE(eng.fromScriptValue<short>(num), short(123)); |
| QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123)); |
| QCOMPARE(eng.fromScriptValue<float>(num), float(123)); |
| QCOMPARE(eng.fromScriptValue<double>(num), double(123)); |
| QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123)); |
| QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123)); |
| } |
| { |
| QJSValue num(123); |
| QCOMPARE(eng.fromScriptValue<char>(num), char(123)); |
| QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123)); |
| QCOMPARE(eng.fromScriptValue<short>(num), short(123)); |
| QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123)); |
| QCOMPARE(eng.fromScriptValue<float>(num), float(123)); |
| QCOMPARE(eng.fromScriptValue<double>(num), double(123)); |
| QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123)); |
| QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123)); |
| } |
| |
| { |
| QJSValue num = eng.toScriptValue(Q_INT64_C(0x100000000)); |
| QCOMPARE(eng.fromScriptValue<qlonglong>(num), Q_INT64_C(0x100000000)); |
| QCOMPARE(eng.fromScriptValue<qulonglong>(num), Q_UINT64_C(0x100000000)); |
| } |
| |
| { |
| QChar c = QLatin1Char('c'); |
| QJSValue str = eng.toScriptValue(QString::fromLatin1("ciao")); |
| QCOMPARE(eng.fromScriptValue<QChar>(str), c); |
| QJSValue code = eng.toScriptValue(c.unicode()); |
| QCOMPARE(eng.fromScriptValue<QChar>(code), c); |
| QCOMPARE(eng.fromScriptValue<QChar>(eng.toScriptValue(c)), c); |
| } |
| |
| QVERIFY(eng.toScriptValue(static_cast<void *>(nullptr)).isNull()); |
| } |
| |
| void tst_QJSEngine::valueConversion_QVariant() |
| { |
| QJSEngine eng; |
| // qScriptValueFromValue() should be "smart" when the argument is a QVariant |
| { |
| QJSValue val = eng.toScriptValue(QVariant()); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isUndefined()); |
| } |
| // Checking nested QVariants |
| { |
| QVariant tmp1; |
| QVariant tmp2(QMetaType::QVariant, &tmp1); |
| QCOMPARE(QMetaType::Type(tmp2.userType()), QMetaType::QVariant); |
| |
| QJSValue val1 = eng.toScriptValue(tmp1); |
| QJSValue val2 = eng.toScriptValue(tmp2); |
| QVERIFY(val1.isUndefined()); |
| QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue); |
| QVERIFY(!val2.isUndefined()); |
| QVERIFY(!val1.isVariant()); |
| QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue); |
| QVERIFY(val2.isVariant()); |
| } |
| { |
| QVariant tmp1(123); |
| QVariant tmp2(QMetaType::QVariant, &tmp1); |
| QVariant tmp3(QMetaType::QVariant, &tmp2); |
| QCOMPARE(QMetaType::Type(tmp1.userType()), QMetaType::Int); |
| QCOMPARE(QMetaType::Type(tmp2.userType()), QMetaType::QVariant); |
| QCOMPARE(QMetaType::Type(tmp3.userType()), QMetaType::QVariant); |
| |
| QJSValue val1 = eng.toScriptValue(tmp2); |
| QJSValue val2 = eng.toScriptValue(tmp3); |
| QVERIFY(!val1.isUndefined()); |
| QVERIFY(!val2.isUndefined()); |
| QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue); |
| QVERIFY(val1.isVariant()); |
| QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue); |
| QVERIFY(val2.isVariant()); |
| QCOMPARE(val1.toVariant().toInt(), 123); |
| QCOMPARE(eng.toScriptValue(val2.toVariant()).toVariant().toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(QVariant(true)); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isBool()); |
| QCOMPARE(val.toBool(), true); |
| } |
| { |
| QJSValue val = eng.toScriptValue(QVariant(int(123))); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toNumber(), qreal(123)); |
| } |
| { |
| QJSValue val = eng.toScriptValue(QVariant(qreal(1.25))); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toNumber(), qreal(1.25)); |
| } |
| { |
| QString str = QString::fromLatin1("ciao"); |
| QJSValue val = eng.toScriptValue(QVariant(str)); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), str); |
| } |
| { |
| QJSValue val = eng.toScriptValue(QVariant::fromValue((QObject*)this)); |
| QVERIFY(!val.isVariant()); |
| QVERIFY(val.isQObject()); |
| QCOMPARE(val.toQObject(), (QObject*)this); |
| } |
| { |
| QVariant var = QVariant::fromValue(QPoint(123, 456)); |
| QJSValue val = eng.toScriptValue(var); |
| QVERIFY(!val.isVariant()); |
| QCOMPARE(val.toVariant(), var); |
| } |
| |
| QCOMPARE(qjsvalue_cast<QVariant>(QJSValue(123)), QVariant(123)); |
| |
| QVERIFY(eng.toScriptValue(QVariant(QMetaType::VoidStar, nullptr)).isNull()); |
| QVERIFY(eng.toScriptValue(QVariant::fromValue(nullptr)).isNull()); |
| |
| { |
| QVariantMap map; |
| map.insert("42", "the answer to life the universe and everything"); |
| QJSValue val = eng.toScriptValue(map); |
| QVERIFY(val.isObject()); |
| QCOMPARE(val.property(42).toString(), map.value(QStringLiteral("42")).toString()); |
| } |
| } |
| |
| void tst_QJSEngine::valueConversion_basic2() |
| { |
| QJSEngine eng; |
| // more built-in types |
| { |
| QJSValue val = eng.toScriptValue(uint(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(qulonglong(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(float(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(short(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(ushort(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(char(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| { |
| QJSValue val = eng.toScriptValue(uchar(123)); |
| QVERIFY(val.isNumber()); |
| QCOMPARE(val.toInt(), 123); |
| } |
| } |
| |
| void tst_QJSEngine::valueConversion_dateTime() |
| { |
| QJSEngine eng; |
| { |
| QDateTime in = QDateTime::currentDateTime(); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isDate()); |
| QCOMPARE(val.toDateTime(), in); |
| } |
| { |
| QDate in = QDate::currentDate(); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isDate()); |
| QCOMPARE(val.toDateTime().date(), in); |
| } |
| } |
| |
| void tst_QJSEngine::valueConversion_regExp() |
| { |
| QJSEngine eng; |
| { |
| QRegExp in = QRegExp("foo"); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isRegExp()); |
| QRegExp out = qjsvalue_cast<QRegExp>(val); |
| QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::patternSyntax (always uses RegExp2)", Continue); |
| QCOMPARE(out.patternSyntax(), in.patternSyntax()); |
| QCOMPARE(out.pattern(), in.pattern()); |
| QCOMPARE(out.caseSensitivity(), in.caseSensitivity()); |
| QCOMPARE(out.isMinimal(), in.isMinimal()); |
| } |
| { |
| QRegExp in = QRegExp("foo", Qt::CaseSensitive, QRegExp::RegExp2); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isRegExp()); |
| QCOMPARE(qjsvalue_cast<QRegExp>(val), in); |
| } |
| { |
| QRegExp in = QRegExp("foo"); |
| in.setMinimal(true); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isRegExp()); |
| QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue); |
| QCOMPARE(qjsvalue_cast<QRegExp>(val).isMinimal(), in.isMinimal()); |
| } |
| } |
| |
| void tst_QJSEngine::valueConversion_RegularExpression() |
| { |
| QJSEngine eng; |
| { |
| QRegularExpression in = QRegularExpression("foo"); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isRegExp()); |
| QRegularExpression out = qjsvalue_cast<QRegularExpression>(val); |
| QCOMPARE(out.pattern(), in.pattern()); |
| QCOMPARE(out.patternOptions(), in.patternOptions()); |
| } |
| { |
| QRegularExpression in = QRegularExpression("foo", |
| QRegularExpression::CaseInsensitiveOption); |
| QJSValue val = eng.toScriptValue(in); |
| QVERIFY(val.isRegExp()); |
| QCOMPARE(qjsvalue_cast<QRegularExpression>(val), in); |
| QRegularExpression out = qjsvalue_cast<QRegularExpression>(val); |
| QCOMPARE(out.patternOptions(), in.patternOptions()); |
| } |
| } |
| |
| Q_DECLARE_METATYPE(QGradient) |
| Q_DECLARE_METATYPE(QGradient*) |
| Q_DECLARE_METATYPE(QLinearGradient) |
| |
| class Klazz : public QWidget, |
| public QStandardItem, |
| public QGraphicsItem |
| { |
| Q_INTERFACES(QGraphicsItem) |
| Q_OBJECT |
| public: |
| Klazz(QWidget *parent = nullptr) : QWidget(parent) { } |
| virtual QRectF boundingRect() const { return QRectF(); } |
| virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { } |
| }; |
| |
| Q_DECLARE_METATYPE(Klazz*) |
| Q_DECLARE_METATYPE(QStandardItem*) |
| |
| void tst_QJSEngine::castWithMultipleInheritance() |
| { |
| QJSEngine eng; |
| Klazz klz; |
| QJSValue v = eng.newQObject(&klz); |
| |
| QCOMPARE(qjsvalue_cast<Klazz*>(v), &klz); |
| QCOMPARE(qjsvalue_cast<QWidget*>(v), (QWidget *)&klz); |
| QCOMPARE(qjsvalue_cast<QObject*>(v), (QObject *)&klz); |
| QCOMPARE(qjsvalue_cast<QStandardItem*>(v), (QStandardItem *)&klz); |
| QCOMPARE(qjsvalue_cast<QGraphicsItem*>(v), (QGraphicsItem *)&klz); |
| } |
| |
| void tst_QJSEngine::collectGarbage() |
| { |
| QJSEngine eng; |
| eng.evaluate("a = new Object(); a = new Object(); a = new Object()"); |
| QJSValue a = eng.newObject(); |
| a = eng.newObject(); |
| a = eng.newObject(); |
| QPointer<QObject> ptr = new QObject(); |
| QVERIFY(ptr != nullptr); |
| (void)eng.newQObject(ptr); |
| eng.collectGarbage(); |
| if (ptr) |
| QGuiApplication::sendPostedEvents(ptr, QEvent::DeferredDelete); |
| QVERIFY(ptr.isNull()); |
| } |
| |
| void tst_QJSEngine::gcWithNestedDataStructure() |
| { |
| // The GC must be able to traverse deeply nested objects, otherwise this |
| // test would crash. |
| QJSEngine eng; |
| eng.installExtensions(QJSEngine::GarbageCollectionExtension); |
| |
| QJSValue ret = eng.evaluate( |
| "function makeList(size)" |
| "{" |
| " var head = { };" |
| " var l = head;" |
| " for (var i = 0; i < size; ++i) {" |
| " l.data = i + \"\";" |
| " l.next = { }; l = l.next;" |
| " }" |
| " l.next = null;" |
| " return head;" |
| "}"); |
| QVERIFY(!ret.isError()); |
| const int size = 200; |
| QJSValue head = eng.evaluate(QString::fromLatin1("makeList(%0)").arg(size)); |
| QVERIFY(!head.isError()); |
| for (int x = 0; x < 2; ++x) { |
| if (x == 1) |
| eng.evaluate("gc()"); |
| QJSValue l = head; |
| // Make sure all the nodes are still alive. |
| for (int i = 0; i < 200; ++i) { |
| QCOMPARE(l.property("data").toString(), QString::number(i)); |
| l = l.property("next"); |
| } |
| } |
| } |
| |
| void tst_QJSEngine::stacktrace() |
| { |
| QString script = QString::fromLatin1( |
| "function foo(counter) {\n" |
| " switch (counter) {\n" |
| " case 0: foo(counter+1); break;\n" |
| " case 1: foo(counter+1); break;\n" |
| " case 2: foo(counter+1); break;\n" |
| " case 3: foo(counter+1); break;\n" |
| " case 4: foo(counter+1); break;\n" |
| " default:\n" |
| " throw new Error('blah');\n" |
| " }\n" |
| "}\n" |
| "foo(0);"); |
| |
| const QString fileName("testfile"); |
| |
| QStringList backtrace; |
| backtrace << "foo(5)@testfile:9" |
| << "foo(4)@testfile:7" |
| << "foo(3)@testfile:6" |
| << "foo(2)@testfile:5" |
| << "foo(1)@testfile:4" |
| << "foo(0)@testfile:3" |
| << "<global>()@testfile:12"; |
| |
| QJSEngine eng; |
| QJSValue result = eng.evaluate(script, fileName); |
| QVERIFY(result.isError()); |
| |
| QJSValue stack = result.property("stack"); |
| |
| QJSValueIterator it(stack); |
| int counter = 5; |
| while (it.hasNext()) { |
| it.next(); |
| QJSValue obj = it.value(); |
| QJSValue frame = obj.property("frame"); |
| |
| QCOMPARE(obj.property("fileName").toString(), fileName); |
| if (counter >= 0) { |
| QJSValue callee = frame.property("arguments").property("callee"); |
| QVERIFY(callee.strictlyEquals(eng.globalObject().property("foo"))); |
| QCOMPARE(obj.property("functionName").toString(), QString("foo")); |
| int line = obj.property("lineNumber").toInt(); |
| if (counter == 5) |
| QCOMPARE(line, 9); |
| else |
| QCOMPARE(line, 3 + counter); |
| } else { |
| QVERIFY(frame.strictlyEquals(eng.globalObject())); |
| QVERIFY(obj.property("functionName").toString().isEmpty()); |
| } |
| |
| --counter; |
| } |
| |
| // throw something that isn't an Error object |
| // ###FIXME: No uncaughtExceptionBacktrace: QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty()); |
| QString script2 = QString::fromLatin1( |
| "function foo(counter) {\n" |
| " switch (counter) {\n" |
| " case 0: foo(counter+1); break;\n" |
| " case 1: foo(counter+1); break;\n" |
| " case 2: foo(counter+1); break;\n" |
| " case 3: foo(counter+1); break;\n" |
| " case 4: foo(counter+1); break;\n" |
| " default:\n" |
| " throw 'just a string';\n" |
| " }\n" |
| "}\n" |
| "foo(0);"); |
| |
| QJSValue result2 = eng.evaluate(script2, fileName); |
| QVERIFY(!result2.isError()); |
| QVERIFY(result2.isString()); |
| |
| { |
| QString script3 = QString::fromLatin1( |
| "'use strict'\n" |
| "function throwUp() { throw new Error('up') }\n" |
| "function indirectlyThrow() { return throwUp() }\n" |
| "indirectlyThrow()\n" |
| ); |
| QJSValue result3 = eng.evaluate(script3); |
| QVERIFY(result3.isError()); |
| QJSValue stack = result3.property("stack"); |
| QVERIFY(stack.isString()); |
| QString stackTrace = stack.toString(); |
| QVERIFY(!stackTrace.contains(QStringLiteral("indirectlyThrow"))); |
| QVERIFY(stackTrace.contains(QStringLiteral("elide"))); |
| } |
| } |
| |
| void tst_QJSEngine::numberParsing_data() |
| { |
| QTest::addColumn<QString>("string"); |
| QTest::addColumn<qreal>("expect"); |
| |
| QTest::newRow("decimal 0") << QString("0") << qreal(0); |
| // QTest::newRow("octal 0") << QString("00") << qreal(00); |
| QTest::newRow("hex 0") << QString("0x0") << qreal(0x0); |
| QTest::newRow("decimal 100") << QString("100") << qreal(100); |
| QTest::newRow("hex 100") << QString("0x100") << qreal(0x100); |
| // QTest::newRow("octal 100") << QString("0100") << qreal(0100); |
| QTest::newRow("decimal 4G") << QString("4294967296") << qreal(Q_UINT64_C(4294967296)); |
| QTest::newRow("hex 4G") << QString("0x100000000") << qreal(Q_UINT64_C(0x100000000)); |
| // QTest::newRow("octal 4G") << QString("040000000000") << qreal(Q_UINT64_C(040000000000)); |
| QTest::newRow("0.5") << QString("0.5") << qreal(0.5); |
| QTest::newRow("1.5") << QString("1.5") << qreal(1.5); |
| QTest::newRow("1e2") << QString("1e2") << qreal(100); |
| } |
| |
| void tst_QJSEngine::numberParsing() |
| { |
| QFETCH(QString, string); |
| QFETCH(qreal, expect); |
| |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate(string); |
| QVERIFY(ret.isNumber()); |
| qreal actual = ret.toNumber(); |
| QCOMPARE(actual, expect); |
| } |
| |
| // see ECMA-262, section 7.9 |
| // This is testing ECMA compliance, not our C++ API, but it's important that |
| // the back-end is conformant in this regard. |
| void tst_QJSEngine::automaticSemicolonInsertion() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("{ 1 2 } 3"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains("SyntaxError")); |
| } |
| { |
| QJSValue ret = eng.evaluate("{ 1\n2 } 3"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| { |
| QJSValue ret = eng.evaluate("for (a; b\n)"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains("SyntaxError")); |
| } |
| { |
| QJSValue ret = eng.evaluate("(function() { return\n1 + 2 })()"); |
| QVERIFY(ret.isUndefined()); |
| } |
| { |
| eng.evaluate("c = 2; b = 1"); |
| QJSValue ret = eng.evaluate("a = b\n++c"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| { |
| QJSValue ret = eng.evaluate("if (a > b)\nelse c = d"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains("SyntaxError")); |
| } |
| { |
| eng.evaluate("function c() { return { foo: function() { return 5; } } }"); |
| eng.evaluate("b = 1; d = 2; e = 3"); |
| QJSValue ret = eng.evaluate("a = b + c\n(d + e).foo()"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 6); |
| } |
| { |
| QJSValue ret = eng.evaluate("throw\n1"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains("SyntaxError")); |
| } |
| { |
| QJSValue ret = eng.evaluate("a = Number(1)\n++a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 2); |
| } |
| |
| // "a semicolon is never inserted automatically if the semicolon |
| // would then be parsed as an empty statement" |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if (0)\n ++a; a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if (0)\n --a; a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if ((0))\n ++a; a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if ((0))\n --a; a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if (0\n)\n ++a; a"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if (0\n ++a; a"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| eng.evaluate("a = 123"); |
| QJSValue ret = eng.evaluate("if (0))\n ++a; a"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 0; for (i = 0; i < 10; ++i)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 30; for (i = 0; i < 10; ++i)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 20); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 0; for (var i = 0; i < 10; ++i)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 30; for (var i = 0; i < 10; ++i)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 20); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 0; i = 0; while (i++ < 10)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 30; i = 0; while (i++ < 10)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 20); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (i in o)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (i in o)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 6); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (var i in o)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (var i in o)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 6); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { n: 3 }; n = 5; with (o)\n ++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 5); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { n: 3 }; n = 10; with (o)\n --n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 5; i = 0; do\n ++n; while (++i < 10); n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 15); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 20; i = 0; do\n --n; while (++i < 10); n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| |
| { |
| QJSValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n++n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 2); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n--n; n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 0); |
| } |
| |
| { |
| QJSValue ret = eng.evaluate("if (0)"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("n = 0; if (1) --n;else\n ++n;\n n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), -1); |
| } |
| { |
| QJSValue ret = eng.evaluate("while (0)"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("for (;;)"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("for (p in this)"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("with (this)"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("do"); |
| QVERIFY(ret.isError()); |
| } |
| } |
| |
| void tst_QJSEngine::errorConstructors() |
| { |
| QJSEngine eng; |
| QStringList prefixes; |
| prefixes << "" << "Eval" << "Range" << "Reference" << "Syntax" << "Type" << "URI"; |
| for (int x = 0; x < 3; ++x) { |
| for (int i = 0; i < prefixes.size(); ++i) { |
| QString name = prefixes.at(i) + QLatin1String("Error"); |
| QString code = QString(i+1, QLatin1Char('\n')); |
| if (x == 0) |
| code += QLatin1String("throw "); |
| else if (x == 1) |
| code += QLatin1String("new "); |
| code += name + QLatin1String("()"); |
| QJSValue ret = eng.evaluate(code); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().startsWith(name)); |
| qDebug() << ret.property("stack").toString(); |
| QCOMPARE(ret.property("lineNumber").toInt(), i+2); |
| } |
| } |
| } |
| |
| void tst_QJSEngine::argumentsProperty_globalContext() |
| { |
| QJSEngine eng; |
| { |
| // Unlike function calls, the global context doesn't have an |
| // arguments property. |
| QJSValue ret = eng.evaluate("arguments"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); |
| } |
| eng.evaluate("arguments = 10"); |
| { |
| QJSValue ret = eng.evaluate("arguments"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 10); |
| } |
| QVERIFY(eng.evaluate("delete arguments").toBool()); |
| QVERIFY(eng.globalObject().property("arguments").isUndefined()); |
| } |
| |
| void tst_QJSEngine::argumentsProperty_JS() |
| { |
| { |
| QJSEngine eng; |
| eng.evaluate("o = { arguments: 123 }"); |
| QJSValue ret = eng.evaluate("with (o) { arguments; }"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| QJSEngine eng; |
| QVERIFY(eng.globalObject().property("arguments").isUndefined()); |
| // This is testing ECMA-262 compliance. In function calls, "arguments" |
| // appears like a local variable, and it can be replaced. |
| QJSValue ret = eng.evaluate("(function() { arguments = 456; return arguments; })()"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 456); |
| QVERIFY(eng.globalObject().property("arguments").isUndefined()); |
| } |
| } |
| |
| void tst_QJSEngine::jsNumberClass() |
| { |
| // See ECMA-262 Section 15.7, "Number Objects". |
| |
| QJSEngine eng; |
| |
| QJSValue ctor = eng.globalObject().property("Number"); |
| QVERIFY(ctor.property("length").isNumber()); |
| QCOMPARE(ctor.property("length").toNumber(), qreal(1)); |
| QJSValue proto = ctor.property("prototype"); |
| QVERIFY(proto.isObject()); |
| { |
| QVERIFY(ctor.property("MAX_VALUE").isNumber()); |
| QVERIFY(ctor.property("MIN_VALUE").isNumber()); |
| QVERIFY(ctor.property("NaN").isNumber()); |
| QVERIFY(ctor.property("NEGATIVE_INFINITY").isNumber()); |
| QVERIFY(ctor.property("POSITIVE_INFINITY").isNumber()); |
| QVERIFY(ctor.property("EPSILON").isNumber()); |
| } |
| QCOMPARE(proto.toNumber(), qreal(0)); |
| QVERIFY(proto.property("constructor").strictlyEquals(ctor)); |
| |
| { |
| QJSValue ret = eng.evaluate("Number()"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qreal(0)); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number(123)"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qreal(123)); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number('456')"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qreal(456)); |
| } |
| { |
| QJSValue ret = eng.evaluate("new Number()"); |
| QVERIFY(!ret.isNumber()); |
| QVERIFY(ret.isObject()); |
| QCOMPARE(ret.toNumber(), qreal(0)); |
| } |
| { |
| QJSValue ret = eng.evaluate("new Number(123)"); |
| QVERIFY(!ret.isNumber()); |
| QVERIFY(ret.isObject()); |
| QCOMPARE(ret.toNumber(), qreal(123)); |
| } |
| { |
| QJSValue ret = eng.evaluate("new Number('456')"); |
| QVERIFY(!ret.isNumber()); |
| QVERIFY(ret.isObject()); |
| QCOMPARE(ret.toNumber(), qreal(456)); |
| } |
| |
| QVERIFY(ctor.property("isFinite").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("Number.isFinite()"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isFinite(NaN)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isFinite(Infinity)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isFinite(-Infinity)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isFinite(123)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), true); |
| } |
| |
| QVERIFY(ctor.property("isNaN").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("Number.isNaN()"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isNaN(NaN)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), true); |
| } |
| { |
| QJSValue ret = eng.evaluate("Number.isNaN(123)"); |
| QVERIFY(ret.isBool()); |
| QCOMPARE(ret.toBool(), false); |
| } |
| |
| QVERIFY(proto.property("toString").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toString()"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123")); |
| } |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toString(8)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("173")); |
| } |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toString(16)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("7b")); |
| } |
| QVERIFY(proto.property("toLocaleString").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toLocaleString()"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123")); |
| } |
| QVERIFY(proto.property("valueOf").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).valueOf()"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qreal(123)); |
| } |
| QVERIFY(proto.property("toExponential").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toExponential()"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2")); |
| ret = eng.evaluate("new Number(123).toExponential(1)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("1.2e+2")); |
| ret = eng.evaluate("new Number(123).toExponential(2)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2")); |
| ret = eng.evaluate("new Number(123).toExponential(3)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("1.230e+2")); |
| } |
| QVERIFY(proto.property("toFixed").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toFixed()"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123")); |
| ret = eng.evaluate("new Number(123).toFixed(1)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123.0")); |
| ret = eng.evaluate("new Number(123).toFixed(2)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123.00")); |
| } |
| QVERIFY(proto.property("toPrecision").isCallable()); |
| { |
| QJSValue ret = eng.evaluate("new Number(123).toPrecision()"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("123")); |
| ret = eng.evaluate("new Number(42).toPrecision(1)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("4e+1")); |
| ret = eng.evaluate("new Number(42).toPrecision(2)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("42")); |
| ret = eng.evaluate("new Number(42).toPrecision(3)"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("42.0")); |
| } |
| } |
| |
| void tst_QJSEngine::jsForInStatement_simple() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("o = { }; r = []; for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QVERIFY(lst.isEmpty()); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];" |
| "for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 1); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];" |
| "for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 2); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| QCOMPARE(lst.at(1), QString::fromLatin1("q")); |
| } |
| } |
| |
| void tst_QJSEngine::jsForInStatement_prototypeProperties() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("o = { }; o.__proto__ = { p: 123 }; r = [];" |
| "for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 1); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { q: 456 }; r = [];" |
| "for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 2); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| QCOMPARE(lst.at(1), QString::fromLatin1("q")); |
| } |
| { |
| // shadowed property |
| QJSValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { p: 456 }; r = [];" |
| "for (var p in o) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 1); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| |
| } |
| |
| void tst_QJSEngine::jsForInStatement_mutateWhileIterating() |
| { |
| QJSEngine eng; |
| // deleting property during enumeration |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];" |
| "for (var p in o) { r[r.length] = p; delete r[p]; } r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 1); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];" |
| "for (var p in o) { r[r.length] = p; delete o.q; } r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 1); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| |
| // adding property during enumeration |
| { |
| QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];" |
| "for (var p in o) { r[r.length] = p; o.q = 456; } r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 2); |
| QCOMPARE(lst.at(0), QString::fromLatin1("p")); |
| } |
| |
| } |
| |
| void tst_QJSEngine::jsForInStatement_arrays() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("a = [123, 456]; r = [];" |
| "for (var p in a) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 2); |
| QCOMPARE(lst.at(0), QString::fromLatin1("0")); |
| QCOMPARE(lst.at(1), QString::fromLatin1("1")); |
| } |
| { |
| QJSValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar'; r = [];" |
| "for (var p in a) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 3); |
| QCOMPARE(lst.at(0), QString::fromLatin1("0")); |
| QCOMPARE(lst.at(1), QString::fromLatin1("1")); |
| QCOMPARE(lst.at(2), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar';" |
| "b = [111, 222, 333]; b.bar = 'baz';" |
| "a.__proto__ = b; r = [];" |
| "for (var p in a) r[r.length] = p; r"); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), 5); |
| QCOMPARE(lst.at(0), QString::fromLatin1("0")); |
| QCOMPARE(lst.at(1), QString::fromLatin1("1")); |
| QCOMPARE(lst.at(2), QString::fromLatin1("foo")); |
| QCOMPARE(lst.at(3), QString::fromLatin1("2")); |
| QCOMPARE(lst.at(4), QString::fromLatin1("bar")); |
| } |
| } |
| |
| void tst_QJSEngine::jsForInStatement_constant() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("r = true; for (var p in undefined) r = false; r"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(ret.toBool()); |
| } |
| { |
| QJSValue ret = eng.evaluate("r = true; for (var p in null) r = false; r"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(ret.toBool()); |
| } |
| { |
| QJSValue ret = eng.evaluate("r = false; for (var p in 1) r = true; r"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(!ret.toBool()); |
| } |
| { |
| QJSValue ret = eng.evaluate("r = false; for (var p in 'abc') r = true; r"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(ret.toBool()); |
| } |
| } |
| |
| void tst_QJSEngine::with_constant() |
| { |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("r = false; with(null) { r= true; } r"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("r = false; with(undefined) { r= true; } r"); |
| QVERIFY(ret.isError()); |
| } |
| { |
| QJSValue ret = eng.evaluate("r = false; with(1) { r= true; } r"); |
| QVERIFY(ret.isBool()); |
| QVERIFY(ret.toBool()); |
| } |
| } |
| |
| void tst_QJSEngine::stringObjects() |
| { |
| // See ECMA-262 Section 15.5, "String Objects". |
| |
| QJSEngine eng; |
| QString str("ciao"); |
| // in C++ |
| { |
| QJSValue obj = eng.evaluate(QString::fromLatin1("new String('%0')").arg(str)); |
| QCOMPARE(obj.property("length").toInt(), str.length()); |
| for (int i = 0; i < str.length(); ++i) { |
| QString pname = QString::number(i); |
| QVERIFY(obj.property(pname).isString()); |
| QCOMPARE(obj.property(pname).toString(), QString(str.at(i))); |
| QVERIFY(!obj.deleteProperty(pname)); |
| obj.setProperty(pname, 123); |
| QVERIFY(obj.property(pname).isString()); |
| QCOMPARE(obj.property(pname).toString(), QString(str.at(i))); |
| } |
| QVERIFY(obj.property("-1").isUndefined()); |
| QVERIFY(obj.property(QString::number(str.length())).isUndefined()); |
| |
| QJSValue val = eng.toScriptValue(123); |
| obj.setProperty("-1", val); |
| QVERIFY(obj.property("-1").strictlyEquals(val)); |
| obj.setProperty("100", val); |
| QVERIFY(obj.property("100").strictlyEquals(val)); |
| } |
| |
| { |
| QJSValue ret = eng.evaluate("s = new String('ciao'); r = []; for (var p in s) r.push(p); r"); |
| QVERIFY(ret.isArray()); |
| QStringList lst = qjsvalue_cast<QStringList>(ret); |
| QCOMPARE(lst.size(), str.length()); |
| for (int i = 0; i < str.length(); ++i) |
| QCOMPARE(lst.at(i), QString::number(i)); |
| |
| QJSValue ret2 = eng.evaluate("s[0] = 123; s[0]"); |
| QVERIFY(ret2.isString()); |
| QCOMPARE(ret2.toString().length(), 1); |
| QCOMPARE(ret2.toString().at(0), str.at(0)); |
| |
| QJSValue ret3 = eng.evaluate("s[-1] = 123; s[-1]"); |
| QVERIFY(ret3.isNumber()); |
| QCOMPARE(ret3.toInt(), 123); |
| |
| QJSValue ret4 = eng.evaluate("s[s.length] = 456; s[s.length]"); |
| QVERIFY(ret4.isNumber()); |
| QCOMPARE(ret4.toInt(), 456); |
| |
| QJSValue ret5 = eng.evaluate("delete s[0]"); |
| QVERIFY(ret5.isBool()); |
| QVERIFY(!ret5.toBool()); |
| |
| QJSValue ret6 = eng.evaluate("delete s[-1]"); |
| QVERIFY(ret6.isBool()); |
| QVERIFY(ret6.toBool()); |
| |
| QJSValue ret7 = eng.evaluate("delete s[s.length]"); |
| QVERIFY(ret7.isBool()); |
| QVERIFY(ret7.toBool()); |
| } |
| } |
| |
| void tst_QJSEngine::jsStringPrototypeReplaceBugs() |
| { |
| QJSEngine eng; |
| // task 212440 |
| { |
| QJSValue ret = eng.evaluate("replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args"); |
| QVERIFY(ret.isArray()); |
| int len = ret.property("length").toInt(); |
| QCOMPARE(len, 3); |
| for (int i = 0; i < len; ++i) { |
| QJSValue args = ret.property(i); |
| QCOMPARE(args.property("length").toInt(), 4); |
| QCOMPARE(args.property(0).toString(), QString::fromLatin1("a")); // matched string |
| QCOMPARE(args.property(1).toString(), QString::fromLatin1("a")); // capture |
| QVERIFY(args.property(2).isNumber()); |
| QCOMPARE(args.property(2).toInt(), i*2); // index of match |
| QCOMPARE(args.property(3).toString(), QString::fromLatin1("a a a")); |
| } |
| } |
| // task 212501 |
| { |
| QJSValue ret = eng.evaluate("\"foo\".replace(/a/g, function() {})"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| } |
| |
| void tst_QJSEngine::getterSetterThisObject_global() |
| { |
| { |
| QJSEngine eng; |
| // read |
| eng.evaluate("__defineGetter__('x', function() { return this; });"); |
| { |
| QJSValue ret = eng.evaluate("x"); |
| QVERIFY(ret.equals(eng.globalObject())); |
| } |
| { |
| QJSValue ret = eng.evaluate("(function() { return x; })()"); |
| QVERIFY(ret.equals(eng.globalObject())); |
| } |
| { |
| QJSValue ret = eng.evaluate("with (this) x"); |
| QVERIFY(ret.equals(eng.globalObject())); |
| } |
| { |
| QJSValue ret = eng.evaluate("with ({}) x"); |
| QVERIFY(ret.equals(eng.globalObject())); |
| } |
| { |
| QJSValue ret = eng.evaluate("(function() { with ({}) return x; })()"); |
| QVERIFY(ret.equals(eng.globalObject())); |
| } |
| // write |
| eng.evaluate("__defineSetter__('x', function() { return this; });"); |
| { |
| QJSValue ret = eng.evaluate("x = 'foo'"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("(function() { return x = 'foo'; })()"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("with (this) x = 'foo'"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("with ({}) x = 'foo'"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("(function() { with ({}) return x = 'foo'; })()"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| } |
| } |
| |
| void tst_QJSEngine::getterSetterThisObject_plain() |
| { |
| { |
| QJSEngine eng; |
| eng.evaluate("o = {}"); |
| // read |
| eng.evaluate("o.__defineGetter__('x', function() { return this; })"); |
| QVERIFY(eng.evaluate("o.x === o").toBool()); |
| QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o"))); |
| QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBool()); |
| eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o")); |
| // write |
| eng.evaluate("o.__defineSetter__('x', function() { return this; });"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBool()); |
| QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo")); |
| QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo")); |
| } |
| } |
| |
| void tst_QJSEngine::getterSetterThisObject_prototypeChain() |
| { |
| { |
| QJSEngine eng; |
| eng.evaluate("o = {}; p = {}; o.__proto__ = p"); |
| // read |
| eng.evaluate("p.__defineGetter__('x', function() { return this; })"); |
| QVERIFY(eng.evaluate("o.x === o").toBool()); |
| QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o"))); |
| QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBool()); |
| eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o")); |
| eng.evaluate("with (q) with (o) x").equals(eng.evaluate("o")); |
| // write |
| eng.evaluate("o.__defineSetter__('x', function() { return this; });"); |
| // SpiderMonkey says setter return value, JSC says RHS. |
| QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBool()); |
| QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo")); |
| QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo")); |
| } |
| } |
| |
| void tst_QJSEngine::jsContinueInSwitch() |
| { |
| // This is testing ECMA-262 compliance, not C++ API. |
| |
| QJSEngine eng; |
| // switch - continue |
| { |
| QJSValue ret = eng.evaluate("switch (1) { default: continue; }"); |
| QVERIFY(ret.isError()); |
| } |
| // for - switch - case - continue |
| { |
| QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n" |
| " switch (i) {\n" |
| " case 1: ++j; continue;\n" |
| " case 100: ++j; continue;\n" |
| " case 1000: ++j; continue;\n" |
| " }\n" |
| "}; j"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| // for - switch - case - default - continue |
| { |
| QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n" |
| " switch (i) {\n" |
| " case 1: ++j; continue;\n" |
| " case 100: ++j; continue;\n" |
| " case 1000: ++j; continue;\n" |
| " default: if (i < 50000) break; else continue;\n" |
| " }\n" |
| "}; j"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| // switch - for - continue |
| { |
| QJSValue ret = eng.evaluate("j = 123; switch (j) {\n" |
| " case 123:\n" |
| " for (i = 0; i < 100000; ++i) {\n" |
| " continue;\n" |
| " }\n" |
| "}; i\n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 100000); |
| } |
| // switch - switch - continue |
| { |
| QJSValue ret = eng.evaluate("i = 1; switch (i) { default: switch (i) { case 1: continue; } }"); |
| QVERIFY(ret.isError()); |
| } |
| // for - switch - switch - continue |
| { |
| QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n" |
| " switch (i) {\n" |
| " case 1:\n" |
| " switch (i) {\n" |
| " case 1: ++j; continue;\n" |
| " }\n" |
| " }\n" |
| "}; j"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 1); |
| } |
| // switch - for - switch - continue |
| { |
| QJSValue ret = eng.evaluate("j = 123; switch (j) {\n" |
| " case 123:\n" |
| " for (i = 0; i < 100000; ++i) {\n" |
| " switch (i) {\n" |
| " case 1:\n" |
| " ++j; continue;\n" |
| " }\n" |
| " }\n" |
| "}; i\n"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 100000); |
| } |
| } |
| |
| void tst_QJSEngine::jsShadowReadOnlyPrototypeProperty() |
| { |
| QJSEngine eng; |
| QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber()); |
| QVERIFY(eng.evaluate("o.length = 123; o.length").toInt() != 123); |
| QVERIFY(!eng.evaluate("o.hasOwnProperty('length')").toBool()); |
| } |
| |
| void tst_QJSEngine::jsReservedWords_data() |
| { |
| QTest::addColumn<QString>("word"); |
| QTest::newRow("break") << QString("break"); |
| QTest::newRow("case") << QString("case"); |
| QTest::newRow("catch") << QString("catch"); |
| QTest::newRow("continue") << QString("continue"); |
| QTest::newRow("default") << QString("default"); |
| QTest::newRow("delete") << QString("delete"); |
| QTest::newRow("do") << QString("do"); |
| QTest::newRow("else") << QString("else"); |
| QTest::newRow("false") << QString("false"); |
| QTest::newRow("finally") << QString("finally"); |
| QTest::newRow("for") << QString("for"); |
| QTest::newRow("function") << QString("function"); |
| QTest::newRow("if") << QString("if"); |
| QTest::newRow("in") << QString("in"); |
| QTest::newRow("instanceof") << QString("instanceof"); |
| QTest::newRow("new") << QString("new"); |
| QTest::newRow("null") << QString("null"); |
| QTest::newRow("return") << QString("return"); |
| QTest::newRow("switch") << QString("switch"); |
| QTest::newRow("this") << QString("this"); |
| QTest::newRow("throw") << QString("throw"); |
| QTest::newRow("true") << QString("true"); |
| QTest::newRow("try") << QString("try"); |
| QTest::newRow("typeof") << QString("typeof"); |
| QTest::newRow("var") << QString("var"); |
| QTest::newRow("void") << QString("void"); |
| QTest::newRow("while") << QString("while"); |
| QTest::newRow("with") << QString("with"); |
| } |
| |
| void tst_QJSEngine::jsReservedWords() |
| { |
| // See ECMA-262 Section 7.6.1, "Reserved Words". |
| // We prefer that the implementation is less strict than the spec; e.g. |
| // it's good to allow reserved words as identifiers in object literals, |
| // and when accessing properties using dot notation. |
| |
| QFETCH(QString, word); |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate(word + " = 123"); |
| QVERIFY(ret.isError()); |
| QString str = ret.toString(); |
| QVERIFY(str.startsWith("SyntaxError") || str.startsWith("ReferenceError")); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("var " + word + " = 123"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().startsWith("SyntaxError")); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("o = {}; o." + word + " = 123"); |
| // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC |
| QVERIFY(!ret.isError()); |
| QVERIFY(ret.strictlyEquals(eng.evaluate("o." + word))); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("o = { " + word + ": 123 }"); |
| // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC |
| QVERIFY(!ret.isError()); |
| QVERIFY(ret.property(word).isNumber()); |
| } |
| { |
| // SpiderMonkey allows this, but we don't |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("function " + word + "() {}"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().startsWith("SyntaxError")); |
| } |
| } |
| |
| void tst_QJSEngine::jsFutureReservedWords_data() |
| { |
| QTest::addColumn<QString>("word"); |
| QTest::addColumn<bool>("allowed"); |
| QTest::newRow("abstract") << QString("abstract") << true; |
| QTest::newRow("boolean") << QString("boolean") << true; |
| QTest::newRow("byte") << QString("byte") << true; |
| QTest::newRow("char") << QString("char") << true; |
| QTest::newRow("class") << QString("class") << false; |
| QTest::newRow("const") << QString("const") << false; |
| QTest::newRow("debugger") << QString("debugger") << false; |
| QTest::newRow("double") << QString("double") << true; |
| QTest::newRow("enum") << QString("enum") << false; |
| QTest::newRow("export") << QString("export") << false; |
| QTest::newRow("extends") << QString("extends") << false; |
| QTest::newRow("final") << QString("final") << true; |
| QTest::newRow("float") << QString("float") << true; |
| QTest::newRow("goto") << QString("goto") << true; |
| QTest::newRow("implements") << QString("implements") << true; |
| QTest::newRow("import") << QString("import") << false; |
| QTest::newRow("int") << QString("int") << true; |
| QTest::newRow("interface") << QString("interface") << true; |
| QTest::newRow("long") << QString("long") << true; |
| QTest::newRow("native") << QString("native") << true; |
| QTest::newRow("package") << QString("package") << true; |
| QTest::newRow("private") << QString("private") << true; |
| QTest::newRow("protected") << QString("protected") << true; |
| QTest::newRow("public") << QString("public") << true; |
| QTest::newRow("short") << QString("short") << true; |
| QTest::newRow("static") << QString("static") << true; |
| QTest::newRow("super") << QString("super") << false; |
| QTest::newRow("synchronized") << QString("synchronized") << true; |
| QTest::newRow("throws") << QString("throws") << true; |
| QTest::newRow("transient") << QString("transient") << true; |
| QTest::newRow("volatile") << QString("volatile") << true; |
| } |
| |
| void tst_QJSEngine::jsFutureReservedWords() |
| { |
| // See ECMA-262 Section 7.6.1.2, "Future Reserved Words". |
| // In real-world implementations, most of these words are |
| // actually allowed as normal identifiers. |
| |
| QFETCH(QString, word); |
| QFETCH(bool, allowed); |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate(word + " = 123"); |
| QCOMPARE(!ret.isError(), allowed); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("var " + word + " = 123"); |
| QCOMPARE(!ret.isError(), allowed); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("o = {}; o." + word + " = 123"); |
| |
| QCOMPARE(ret.isNumber(), true); |
| QCOMPARE(!ret.isError(), true); |
| } |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("o = { " + word + ": 123 }"); |
| QCOMPARE(!ret.isError(), true); |
| } |
| } |
| |
| void tst_QJSEngine::jsThrowInsideWithStatement() |
| { |
| // This is testing ECMA-262 compliance, not C++ API. |
| |
| // task 209988 |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate( |
| "try {" |
| " o = { bad : \"bug\" };" |
| " with (o) {" |
| " throw 123;" |
| " }" |
| "} catch (e) {" |
| " bad;" |
| "}"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate( |
| "try {" |
| " o = { bad : \"bug\" };" |
| " with (o) {" |
| " throw 123;" |
| " }" |
| "} finally {" |
| " bad;" |
| "}"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate( |
| "o = { bug : \"no bug\" };" |
| "with (o) {" |
| " try {" |
| " throw 123;" |
| " } finally {" |
| " bug;" |
| " }" |
| "}"); |
| QVERIFY(!ret.isError()); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 123); |
| } |
| { |
| QJSValue ret = eng.evaluate( |
| "o = { bug : \"no bug\" };" |
| "with (o) {" |
| " throw 123;" |
| "}"); |
| QVERIFY(ret.isNumber()); |
| QJSValue ret2 = eng.evaluate("bug"); |
| QVERIFY(ret2.isError()); |
| QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError"))); |
| } |
| } |
| |
| void tst_QJSEngine::reentrancy_globalObjectProperties() |
| { |
| QJSEngine eng1; |
| QJSEngine eng2; |
| QVERIFY(eng2.globalObject().property("a").isUndefined()); |
| eng1.evaluate("a = 10"); |
| QVERIFY(eng1.globalObject().property("a").isNumber()); |
| QVERIFY(eng2.globalObject().property("a").isUndefined()); |
| eng2.evaluate("a = 20"); |
| QVERIFY(eng2.globalObject().property("a").isNumber()); |
| QCOMPARE(eng1.globalObject().property("a").toInt(), 10); |
| } |
| |
| void tst_QJSEngine::reentrancy_Array() |
| { |
| // weird bug with JSC backend |
| { |
| QJSEngine eng; |
| QCOMPARE(eng.evaluate("Array()").toString(), QString()); |
| eng.evaluate("Array.prototype.toString"); |
| QCOMPARE(eng.evaluate("Array()").toString(), QString()); |
| } |
| { |
| QJSEngine eng; |
| QCOMPARE(eng.evaluate("Array()").toString(), QString()); |
| } |
| } |
| |
| void tst_QJSEngine::reentrancy_objectCreation() |
| { |
| // Owned by JS engine, as newQObject() sets JS ownership explicitly |
| QObject *temp = new QObject(); |
| |
| QJSEngine eng1; |
| QJSEngine eng2; |
| { |
| QDateTime dt = QDateTime::currentDateTime(); |
| QJSValue d1 = eng1.toScriptValue(dt); |
| QJSValue d2 = eng2.toScriptValue(dt); |
| QCOMPARE(d1.toDateTime(), d2.toDateTime()); |
| QCOMPARE(d2.toDateTime(), d1.toDateTime()); |
| } |
| { |
| QJSValue r1 = eng1.evaluate("new RegExp('foo', 'gim')"); |
| QJSValue r2 = eng2.evaluate("new RegExp('foo', 'gim')"); |
| QCOMPARE(qjsvalue_cast<QRegExp>(r1), qjsvalue_cast<QRegExp>(r2)); |
| QCOMPARE(qjsvalue_cast<QRegExp>(r2), qjsvalue_cast<QRegExp>(r1)); |
| QCOMPARE(qjsvalue_cast<QRegularExpression>(r1), qjsvalue_cast<QRegularExpression>(r2)); |
| QCOMPARE(qjsvalue_cast<QRegularExpression>(r2), qjsvalue_cast<QRegularExpression>(r1)); |
| } |
| { |
| QJSValue o1 = eng1.newQObject(temp); |
| QJSValue o2 = eng2.newQObject(temp); |
| QCOMPARE(o1.toQObject(), o2.toQObject()); |
| QCOMPARE(o2.toQObject(), o1.toQObject()); |
| } |
| } |
| |
| void tst_QJSEngine::jsIncDecNonObjectProperty() |
| { |
| // This is testing ECMA-262 compliance, not C++ API. |
| |
| QJSEngine eng; |
| { |
| QJSValue ret = eng.evaluate("var a; a.n++"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a; a.n--"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = null; a.n++"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = null; a.n--"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a; ++a.n"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a; --a.n"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a; a.n += 1"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a; a.n -= 1"); |
| QVERIFY(ret.isError()); |
| QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError"))); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = 'ciao'; a.length++"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 4); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = 'ciao'; a.length--"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 4); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = 'ciao'; ++a.length"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 5); |
| } |
| { |
| QJSValue ret = eng.evaluate("var a = 'ciao'; --a.length"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt(), 3); |
| } |
| } |
| |
| void tst_QJSEngine::JSONparse() |
| { |
| QJSEngine eng; |
| QJSValue ret = eng.evaluate("var json=\"{\\\"1\\\": null}\"; JSON.parse(json);"); |
| QVERIFY(ret.isObject()); |
| } |
| |
| void tst_QJSEngine::arraySort() |
| { |
| // tests that calling Array.sort with a bad sort function doesn't cause issues |
| // Using std::sort is e.g. not safe when used with a bad sort function and causes |
| // crashes |
| QJSEngine eng; |
| eng.evaluate("function crashMe() {" |
| " var data = [];" |
| " for (var i = 0; i < 50; i++) {" |
| " data[i] = 'whatever';" |
| " }" |
| " data.sort(function(a, b) {" |
| " return -1;" |
| " });" |
| "}" |
| "crashMe();"); |
| } |
| |
| void tst_QJSEngine::lookupOnDisappearingProperty() |
| { |
| QJSEngine eng; |
| QJSValue func = eng.evaluate("(function(){\"use strict\"; return eval(\"(function(obj) { return obj.someProperty; })\")})()"); |
| QVERIFY(func.isCallable()); |
| |
| QJSValue o = eng.newObject(); |
| o.setProperty(QStringLiteral("someProperty"), 42); |
| |
| QCOMPARE(func.call(QJSValueList()<< o).toInt(), 42); |
| |
| o = eng.newObject(); |
| QVERIFY(func.call(QJSValueList()<< o).isUndefined()); |
| QVERIFY(func.call(QJSValueList()<< o).isUndefined()); |
| } |
| |
| void tst_QJSEngine::arrayConcat() |
| { |
| QJSEngine eng; |
| QJSValue v = eng.evaluate("var x = [1, 2, 3, 4, 5, 6];" |
| "var y = [];" |
| "for (var i = 0; i < 5; ++i)" |
| " x.shift();" |
| "for (var i = 10; i < 13; ++i)" |
| " x.push(i);" |
| "x.toString();"); |
| QCOMPARE(v.toString(), QString::fromLatin1("6,10,11,12")); |
| } |
| |
| void tst_QJSEngine::recursiveBoundFunctions() |
| { |
| |
| QJSEngine eng; |
| QJSValue v = eng.evaluate("function foo(x, y, z)" |
| "{ return this + x + y + z; }" |
| "var bar = foo.bind(-1, 10);" |
| "var baz = bar.bind(-2, 20);" |
| "baz(30)"); |
| QCOMPARE(v.toInt(), 59); |
| } |
| |
| static QRegExp minimal(QRegExp r) { r.setMinimal(true); return r; } |
| |
| void tst_QJSEngine::qRegExpInport_data() |
| { |
| QTest::addColumn<QRegExp>("rx"); |
| QTest::addColumn<QString>("string"); |
| QTest::addColumn<QString>("matched"); |
| |
| QTest::newRow("normal") << QRegExp("(test|foo)") << "test _ foo _ test _ Foo"; |
| QTest::newRow("normal2") << QRegExp("(Test|Foo)") << "test _ foo _ test _ Foo"; |
| QTest::newRow("case insensitive)") << QRegExp("(test|foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo"; |
| QTest::newRow("case insensitive2)") << QRegExp("(Test|Foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo"; |
| QTest::newRow("b(a*)(b*)") << QRegExp("b(a*)(b*)", Qt::CaseInsensitive) << "aaabbBbaAabaAaababaaabbaaab"; |
| QTest::newRow("greedy") << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp2) << "aaaabaaba"; |
| QTest::newRow("willcard") << QRegExp("*.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "file.txt"; |
| QTest::newRow("willcard 2") << QRegExp("a?b.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "ab.txt abb.rtc acb.txt"; |
| QTest::newRow("slash") << QRegExp("g/.*/s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string/string/string"; |
| QTest::newRow("slash2") << QRegExp("g / .* / s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string / string / string"; |
| QTest::newRow("fixed") << QRegExp("a*aa.a(ba)*a\\ba", Qt::CaseInsensitive, QRegExp::FixedString) << "aa*aa.a(ba)*a\\ba"; |
| QTest::newRow("fixed insensitive") << QRegExp("A*A", Qt::CaseInsensitive, QRegExp::FixedString) << "a*A A*a A*A a*a"; |
| QTest::newRow("fixed sensitive") << QRegExp("A*A", Qt::CaseSensitive, QRegExp::FixedString) << "a*A A*a A*A a*a"; |
| QTest::newRow("html") << QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2) << "<b>bold</b><i>italic</i><b>bold</b>"; |
| QTest::newRow("html minimal") << minimal(QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2)) << "<b>bold</b><i>italic</i><b>bold</b>"; |
| QTest::newRow("aaa") << QRegExp("a{2,5}") << "aAaAaaaaaAa"; |
| QTest::newRow("aaa minimal") << minimal(QRegExp("a{2,5}")) << "aAaAaaaaaAa"; |
| QTest::newRow("minimal") << minimal(QRegExp(".*\\} [*8]")) << "}?} ?} *"; |
| QTest::newRow(".? minimal") << minimal(QRegExp(".?")) << ".?"; |
| QTest::newRow(".+ minimal") << minimal(QRegExp(".+")) << ".+"; |
| QTest::newRow("[.?] minimal") << minimal(QRegExp("[.?]")) << ".?"; |
| QTest::newRow("[.+] minimal") << minimal(QRegExp("[.+]")) << ".+"; |
| } |
| |
| void tst_QJSEngine::qRegExpInport() |
| { |
| QFETCH(QRegExp, rx); |
| QFETCH(QString, string); |
| |
| QJSEngine eng; |
| QJSValue rexp; |
| rexp = eng.toScriptValue(rx); |
| |
| QCOMPARE(rexp.isRegExp(), true); |
| QCOMPARE(rexp.isCallable(), false); |
| |
| QJSValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })"); |
| QJSValue result = func.call(QJSValueList() << string << rexp); |
| |
| rx.indexIn(string); |
| for (int i = 0; i <= rx.captureCount(); i++) { |
| QCOMPARE(result.property(i).toString(), rx.cap(i)); |
| } |
| } |
| |
| void tst_QJSEngine::qRegularExpressionImport_data() |
| { |
| QTest::addColumn<QRegularExpression>("rx"); |
| QTest::addColumn<QString>("string"); |
| QTest::addColumn<QString>("matched"); |
| |
| QTest::newRow("normal") << QRegularExpression("(test|foo)") << "test _ foo _ test _ Foo"; |
| QTest::newRow("normal2") << QRegularExpression("(Test|Foo)") << "test _ foo _ test _ Foo"; |
| QTest::newRow("case insensitive") << QRegularExpression("(test|foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo"; |
| QTest::newRow("case insensitive2") << QRegularExpression("(Test|Foo)", QRegularExpression::CaseInsensitiveOption) << "test _ foo _ test _ Foo"; |
| QTest::newRow("b(a*)(b*)") << QRegularExpression("b(a*)(b*)", QRegularExpression::CaseInsensitiveOption) << "aaabbBbaAabaAaababaaabbaaab"; |
| QTest::newRow("greedy") << QRegularExpression("a*(a*)", QRegularExpression::CaseInsensitiveOption) << "aaaabaaba"; |
| QTest::newRow("wildcard") << QRegularExpression(".*\\.txt") << "file.txt"; |
| QTest::newRow("wildcard 2") << QRegularExpression("a.b\\.txt") << "ab.txt abb.rtc acb.txt"; |
| QTest::newRow("slash") << QRegularExpression("g/.*/s", QRegularExpression::CaseInsensitiveOption) << "string/string/string"; |
| QTest::newRow("slash2") << QRegularExpression("g / .* / s", QRegularExpression::CaseInsensitiveOption) << "string / string / string"; |
| QTest::newRow("fixed") << QRegularExpression("a\\*aa\\.a\\(ba\\)\\*a\\\\ba", QRegularExpression::CaseInsensitiveOption) << "aa*aa.a(ba)*a\\ba"; |
| QTest::newRow("fixed insensitive") << QRegularExpression("A\\*A", QRegularExpression::CaseInsensitiveOption) << "a*A A*a A*A a*a"; |
| QTest::newRow("fixed sensitive") << QRegularExpression("A\\*A") << "a*A A*a A*A a*a"; |
| QTest::newRow("html") << QRegularExpression("<b>(.*)</b>") << "<b>bold</b><i>italic</i><b>bold</b>"; |
| QTest::newRow("html minimal") << QRegularExpression("^<b>(.*)</b>$") << "<b>bold</b><i>italic</i><b>bold</b>"; |
| QTest::newRow("aaa") << QRegularExpression("a{2,5}") << "aAaAaaaaaAa"; |
| QTest::newRow("aaa minimal") << QRegularExpression("^a{2,5}$") << "aAaAaaaaaAa"; |
| QTest::newRow("minimal") << QRegularExpression("^.*\\} [*8]$") << "}?} ?} *"; |
| QTest::newRow(".? minimal") << QRegularExpression("^.?$") << ".?"; |
| QTest::newRow(".+ minimal") << QRegularExpression("^.+$") << ".+"; |
| QTest::newRow("[.?] minimal") << QRegularExpression("^[.?]$") << ".?"; |
| QTest::newRow("[.+] minimal") << QRegularExpression("^[.+]$") << ".+"; |
| } |
| |
| void tst_QJSEngine::qRegularExpressionImport() |
| { |
| QFETCH(QRegularExpression, rx); |
| QFETCH(QString, string); |
| |
| QJSEngine eng; |
| QJSValue rexp; |
| rexp = eng.toScriptValue(rx); |
| |
| QCOMPARE(rexp.isRegExp(), true); |
| QCOMPARE(rexp.isCallable(), false); |
| |
| QJSValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })"); |
| QJSValue result = func.call(QJSValueList() << string << rexp); |
| |
| const QRegularExpressionMatch match = rx.match(string); |
| for (int i = 0; i <= match.lastCapturedIndex(); i++) |
| QCOMPARE(result.property(i).toString(), match.captured(i)); |
| } |
| |
| // QScriptValue::toDateTime() returns a local time, whereas JS dates |
| // are always stored as UTC. Qt Script must respect the current time |
| // zone, and correctly adjust for daylight saving time that may be in |
| // effect at a given date (QTBUG-9770). |
| void tst_QJSEngine::dateRoundtripJSQtJS() |
| { |
| #ifdef Q_OS_WIN |
| QSKIP("This test fails on Windows due to a bug in QDateTime."); |
| #endif |
| qint64 secs = QDate(2009, 1, 1).startOfDay(Qt::UTC).toSecsSinceEpoch(); |
| QJSEngine eng; |
| for (int i = 0; i < 8000; ++i) { |
| QJSValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0)); |
| QDateTime qtDate = jsDate.toDateTime(); |
| QJSValue jsDate2 = eng.toScriptValue(qtDate); |
| if (jsDate2.toNumber() != jsDate.toNumber()) |
| QFAIL(qPrintable(jsDate.toString())); |
| secs += 2*60*60; |
| } |
| } |
| |
| void tst_QJSEngine::dateRoundtripQtJSQt() |
| { |
| #ifdef Q_OS_WIN |
| QSKIP("This test fails on Windows due to a bug in QDateTime."); |
| #endif |
| QDateTime qtDate = QDate(2009, 1, 1).startOfDay(); |
| QJSEngine eng; |
| for (int i = 0; i < 8000; ++i) { |
| QJSValue jsDate = eng.toScriptValue(qtDate); |
| QDateTime qtDate2 = jsDate.toDateTime(); |
| if (qtDate2 != qtDate) |
| QFAIL(qPrintable(qtDate.toString())); |
| qtDate = qtDate.addSecs(2*60*60); |
| } |
| } |
| |
| void tst_QJSEngine::dateConversionJSQt() |
| { |
| #ifdef Q_OS_WIN |
| QSKIP("This test fails on Windows due to a bug in QDateTime."); |
| #endif |
| qint64 secs = QDate(2009, 1, 1).startOfDay(Qt::UTC).toSecsSinceEpoch(); |
| QJSEngine eng; |
| for (int i = 0; i < 8000; ++i) { |
| QJSValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0)); |
| QDateTime qtDate = jsDate.toDateTime(); |
| QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate); |
| QString jsUTCDateStr = jsDate.property("toISOString").callWithInstance(jsDate).toString(); |
| jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000") |
| if (qtUTCDateStr != jsUTCDateStr) |
| QFAIL(qPrintable(jsDate.toString())); |
| secs += 2*60*60; |
| } |
| } |
| |
| void tst_QJSEngine::dateConversionQtJS() |
| { |
| QDateTime qtDate = QDate(2009, 1, 1).startOfDay(); |
| QJSEngine eng; |
| for (int i = 0; i < 8000; ++i) { |
| QJSValue jsDate = eng.toScriptValue(qtDate); |
| QString jsUTCDateStr = jsDate.property("toISOString").callWithInstance(jsDate).toString(); |
| QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate); |
| jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000") |
| if (jsUTCDateStr != qtUTCDateStr) |
| QFAIL(qPrintable(qtDate.toString())); |
| qtDate = qtDate.addSecs(2*60*60); |
| } |
| } |
| |
| void tst_QJSEngine::functionPrototypeExtensions() |
| { |
| // QJS adds connect and disconnect properties to Function.prototype. |
| QJSEngine eng; |
| QJSValue funProto = eng.globalObject().property("Function").property("prototype"); |
| QVERIFY(funProto.isCallable()); |
| QVERIFY(funProto.property("connect").isCallable()); |
| QVERIFY(funProto.property("disconnect").isCallable()); |
| |
| // No properties should appear in for-in statements. |
| QJSValue props = eng.evaluate("props = []; for (var p in Function.prototype) props.push(p); props"); |
| QVERIFY(props.isArray()); |
| QCOMPARE(props.property("length").toInt(), 0); |
| } |
| |
| class ThreadedTestEngine : public QThread { |
| Q_OBJECT; |
| |
| public: |
| int result = 0; |
| |
| ThreadedTestEngine() {} |
| |
| void run() { |
| QJSEngine firstEngine; |
| QJSEngine secondEngine; |
| QJSValue value = firstEngine.evaluate("1"); |
| result = secondEngine.evaluate("1 + " + QString::number(value.toInt())).toInt(); |
| } |
| }; |
| |
| void tst_QJSEngine::threadedEngine() |
| { |
| ThreadedTestEngine thread1; |
| ThreadedTestEngine thread2; |
| thread1.start(); |
| thread2.start(); |
| thread1.wait(); |
| thread2.wait(); |
| QCOMPARE(thread1.result, 2); |
| QCOMPARE(thread2.result, 2); |
| } |
| |
| void tst_QJSEngine::functionDeclarationsInConditionals() |
| { |
| // Visibility of function declarations inside blocks is limited to the block |
| QJSEngine eng; |
| QJSValue result = eng.evaluate("if (true) {\n" |
| " function blah() { return false; }\n" |
| "} else {\n" |
| " function blah() { return true; }\n" |
| "}\n" |
| "blah();"); |
| QVERIFY(result.isError()); |
| } |
| |
| void tst_QJSEngine::arrayPop_QTBUG_35979() |
| { |
| QJSEngine eng; |
| QJSValue result = eng.evaluate("" |
| "var x = [1, 2]\n" |
| "x.pop()\n" |
| "x[1] = 3\n" |
| "x.toString()\n"); |
| QCOMPARE(result.toString(), QString("1,3")); |
| } |
| |
| void tst_QJSEngine::array_unshift_QTBUG_52065() |
| { |
| QJSEngine eng; |
| QJSValue result = eng.evaluate("[1, 2, 3, 4, 5, 6, 7, 8, 9]"); |
| QJSValue unshift = result.property(QStringLiteral("unshift")); |
| unshift.callWithInstance(result, QJSValueList() << QJSValue(0)); |
| |
| int len = result.property(QStringLiteral("length")).toInt(); |
| QCOMPARE(len, 10); |
| |
| for (int i = 0; i < len; ++i) |
| QCOMPARE(result.property(i).toInt(), i); |
| } |
| |
| void tst_QJSEngine::array_join_QTBUG_53672() |
| { |
| QJSEngine eng; |
| QJSValue result = eng.evaluate("Array.prototype.join.call(0)"); |
| QVERIFY(result.isString()); |
| QCOMPARE(result.toString(), QString("")); |
| } |
| |
| void tst_QJSEngine::regexpLastMatch() |
| { |
| QJSEngine eng; |
| |
| QCOMPARE(eng.evaluate("RegExp.input").toString(), QString()); |
| |
| QJSValue hasProperty; |
| |
| for (int i = 1; i < 9; ++i) { |
| hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$" + QString::number(i) + "\")"); |
| QVERIFY(hasProperty.isBool()); |
| QVERIFY(hasProperty.toBool()); |
| } |
| |
| hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$0\")"); |
| QVERIFY(hasProperty.isBool()); |
| QVERIFY(!hasProperty.toBool()); |
| |
| hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$10\")"); |
| QVERIFY(!hasProperty.toBool()); |
| |
| hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"lastMatch\")"); |
| QVERIFY(hasProperty.toBool()); |
| hasProperty = eng.evaluate("RegExp.hasOwnProperty(\"$&\")"); |
| QVERIFY(hasProperty.toBool()); |
| |
| QJSValue result = eng.evaluate("" |
| "var re = /h(el)l(o)/\n" |
| "var text = \"blah hello world\"\n" |
| "text.match(re)\n"); |
| QVERIFY(!result.isError()); |
| QJSValue match = eng.evaluate("RegExp.$1"); |
| QCOMPARE(match.toString(), QString("el")); |
| match = eng.evaluate("RegExp.$2"); |
| QCOMPARE(match.toString(), QString("o")); |
| for (int i = 3; i <= 9; ++i) { |
| match = eng.evaluate("RegExp.$" + QString::number(i)); |
| QVERIFY(match.isString()); |
| QCOMPARE(match.toString(), QString()); |
| } |
| QCOMPARE(eng.evaluate("RegExp.input").toString(), QString("blah hello world")); |
| QCOMPARE(eng.evaluate("RegExp.lastParen").toString(), QString("o")); |
| QCOMPARE(eng.evaluate("RegExp.leftContext").toString(), QString("blah ")); |
| QCOMPARE(eng.evaluate("RegExp.rightContext").toString(), QString(" world")); |
| |
| QCOMPARE(eng.evaluate("RegExp.lastMatch").toString(), QString("hello")); |
| |
| result = eng.evaluate("" |
| "var re = /h(ello)/\n" |
| "var text = \"hello\"\n" |
| "text.match(re)\n"); |
| QVERIFY(!result.isError()); |
| match = eng.evaluate("RegExp.$1"); |
| QCOMPARE(match.toString(), QString("ello")); |
| for (int i = 2; i <= 9; ++i) { |
| match = eng.evaluate("RegExp.$" + QString::number(i)); |
| QVERIFY(match.isString()); |
| QCOMPARE(match.toString(), QString()); |
| } |
| |
| } |
| |
| void tst_QJSEngine::regexpLastIndex() |
| { |
| QJSEngine eng; |
| QJSValue result; |
| result = eng.evaluate("function test(text, rx) {" |
| " var res;" |
| " while (res = rx.exec(text)) { " |
| " return true;" |
| " }" |
| " return false;" |
| " }" |
| "function tester(text) {" |
| " return test(text, /,\\s*/g);" |
| "}"); |
| QVERIFY(!result.isError()); |
| |
| result = eng.evaluate("tester(\", \\n\");"); |
| QVERIFY(result.toBool()); |
| result = eng.evaluate("tester(\", \\n\");"); |
| QVERIFY(result.toBool()); |
| } |
| |
| void tst_QJSEngine::indexedAccesses() |
| { |
| QJSEngine engine; |
| QJSValue v = engine.evaluate("function foo() { return 1[1] } foo()"); |
| QVERIFY(v.isUndefined()); |
| v = engine.evaluate("function foo() { return /x/[1] } foo()"); |
| QVERIFY(v.isUndefined()); |
| v = engine.evaluate("function foo() { return \"xy\"[1] } foo()"); |
| QVERIFY(v.isString()); |
| v = engine.evaluate("function foo() { return \"xy\"[2] } foo()"); |
| QVERIFY(v.isUndefined()); |
| } |
| |
| void tst_QJSEngine::prototypeChainGc() |
| { |
| QJSEngine engine; |
| |
| QJSValue getProto = engine.evaluate("Object.getPrototypeOf"); |
| |
| QJSValue factory = engine.evaluate("(function() { return Object.create(Object.create({})); })"); |
| QVERIFY(factory.isCallable()); |
| QJSValue obj = factory.call(); |
| engine.collectGarbage(); |
| |
| QJSValue proto = getProto.call(QJSValueList() << obj); |
| proto = getProto.call(QJSValueList() << proto); |
| QVERIFY(proto.isObject()); |
| } |
| |
| void tst_QJSEngine::prototypeChainGc_QTBUG38299() |
| { |
| QJSEngine engine; |
| engine.evaluate("var mapping = {" |
| "'prop1': \"val1\",\n" |
| "'prop2': \"val2\"\n" |
| "}\n" |
| "\n" |
| "delete mapping.prop2\n" |
| "delete mapping.prop1\n" |
| "\n"); |
| // Don't hang! |
| engine.collectGarbage(); |
| } |
| |
| void tst_QJSEngine::dynamicProperties() |
| { |
| { |
| QJSEngine engine; |
| QObject *obj = new QObject; |
| QJSValue wrapper = engine.newQObject(obj); |
| wrapper.setProperty("someRandomProperty", 42); |
| QCOMPARE(wrapper.property("someRandomProperty").toInt(), 42); |
| QVERIFY(!qmlContext(obj)); |
| } |
| { |
| QQmlEngine qmlEngine; |
| QQmlComponent component(&qmlEngine); |
| component.setData("import QtQml 2.0; QtObject { property QtObject subObject: QtObject {} }", QUrl()); |
| QObject *root = component.create(nullptr); |
| QVERIFY(root); |
| QVERIFY(qmlContext(root)); |
| |
| QJSValue wrapper = qmlEngine.newQObject(root); |
| wrapper.setProperty("someRandomProperty", 42); |
| QVERIFY(!wrapper.hasProperty("someRandomProperty")); |
| |
| QObject *subObject = qvariant_cast<QObject*>(root->property("subObject")); |
| QVERIFY(subObject); |
| QVERIFY(qmlContext(subObject)); |
| |
| wrapper = qmlEngine.newQObject(subObject); |
| wrapper.setProperty("someRandomProperty", 42); |
| QVERIFY(!wrapper.hasProperty("someRandomProperty")); |
| } |
| } |
| |
| class EvaluateWrapper : public QObject |
| { |
| Q_OBJECT |
| public: |
| EvaluateWrapper(QJSEngine *engine) |
| : engine(engine) |
| {} |
| |
| public slots: |
| QJSValue cppEvaluate(const QString &program) |
| { |
| return engine->evaluate(program); |
| } |
| |
| private: |
| QJSEngine *engine; |
| }; |
| |
| void tst_QJSEngine::scopeOfEvaluate() |
| { |
| QJSEngine engine; |
| QJSValue wrapper = engine.newQObject(new EvaluateWrapper(&engine)); |
| |
| engine.evaluate("testVariable = 42"); |
| |
| QJSValue function = engine.evaluate("(function(evalWrapper){\n" |
| "var testVariable = 100; \n" |
| "try { \n" |
| " return evalWrapper.cppEvaluate(\"(function() { return testVariable; })\")\n" |
| " ()\n" |
| "} catch (e) {}\n" |
| "})"); |
| QVERIFY(function.isCallable()); |
| QJSValue result = function.call(QJSValueList() << wrapper); |
| QCOMPARE(result.toInt(), 42); |
| } |
| |
| void tst_QJSEngine::callConstants() |
| { |
| QJSEngine engine; |
| engine.evaluate("function f() {\n" |
| " var one; one();\n" |
| " var two = null; two();\n" |
| "}\n"); |
| QJSValue exceptionResult = engine.evaluate("true()"); |
| QCOMPARE(exceptionResult.toString(), QString("TypeError: true is not a function")); |
| } |
| |
| void tst_QJSEngine::installTranslationFunctions() |
| { |
| QJSEngine eng; |
| QJSValue global = eng.globalObject(); |
| QVERIFY(global.property("qsTranslate").isUndefined()); |
| QVERIFY(global.property("QT_TRANSLATE_NOOP").isUndefined()); |
| QVERIFY(global.property("qsTr").isUndefined()); |
| QVERIFY(global.property("QT_TR_NOOP").isUndefined()); |
| QVERIFY(global.property("qsTrId").isUndefined()); |
| QVERIFY(global.property("QT_TRID_NOOP").isUndefined()); |
| |
| eng.installExtensions(QJSEngine::TranslationExtension); |
| QVERIFY(global.property("qsTranslate").isCallable()); |
| QVERIFY(global.property("QT_TRANSLATE_NOOP").isCallable()); |
| QVERIFY(global.property("qsTr").isCallable()); |
| QVERIFY(global.property("QT_TR_NOOP").isCallable()); |
| QVERIFY(global.property("qsTrId").isCallable()); |
| QVERIFY(global.property("QT_TRID_NOOP").isCallable()); |
| |
| { |
| QJSValue ret = eng.evaluate("qsTr('foo')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("qsTranslate('foo', 'bar')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("bar")); |
| } |
| { |
| QJSValue ret = eng.evaluate("QT_TR_NOOP('foo')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("QT_TRANSLATE_NOOP('foo', 'bar')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("bar")); |
| } |
| |
| { |
| QJSValue ret = eng.evaluate("qsTrId('foo')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("QT_TRID_NOOP('foo')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| { |
| QJSValue ret = eng.evaluate("qsTr('%1').arg('foo')"); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("foo")); |
| } |
| QVERIFY(eng.evaluate("QT_TRID_NOOP()").isUndefined()); |
| } |
| |
| class TranslationScope |
| { |
| public: |
| TranslationScope(const QString &fileName) |
| { |
| translator.load(fileName); |
| QCoreApplication::instance()->installTranslator(&translator); |
| } |
| ~TranslationScope() |
| { |
| QCoreApplication::instance()->removeTranslator(&translator); |
| } |
| |
| private: |
| QTranslator translator; |
| }; |
| |
| void tst_QJSEngine::translateScript_data() |
| { |
| QTest::addColumn<QString>("expression"); |
| QTest::addColumn<QString>("fileName"); |
| QTest::addColumn<QString>("expectedTranslation"); |
| |
| QString fileName = QString::fromLatin1("translatable.js"); |
| // Top-level |
| QTest::newRow("qsTr('One')@translatable.js") |
| << QString::fromLatin1("qsTr('One')") << fileName << QString::fromLatin1("En"); |
| QTest::newRow("qsTr('Hello')@translatable.js") |
| << QString::fromLatin1("qsTr('Hello')") << fileName << QString::fromLatin1("Hallo"); |
| // From function |
| QTest::newRow("(function() { return qsTr('One'); })()@translatable.js") |
| << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName << QString::fromLatin1("En"); |
| QTest::newRow("(function() { return qsTr('Hello'); })()@translatable.js") |
| << QString::fromLatin1("(function() { return qsTr('Hello'); })()") << fileName << QString::fromLatin1("Hallo"); |
| // Plural |
| QTest::newRow("qsTr('%n message(s) saved', '', 1)@translatable.js") |
| << QString::fromLatin1("qsTr('%n message(s) saved', '', 1)") << fileName << QString::fromLatin1("1 melding lagret"); |
| QTest::newRow("qsTr('%n message(s) saved', '', 3).arg@translatable.js") |
| << QString::fromLatin1("qsTr('%n message(s) saved', '', 3)") << fileName << QString::fromLatin1("3 meldinger lagret"); |
| |
| // Top-level |
| QTest::newRow("qsTranslate('FooContext', 'Two')@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName << QString::fromLatin1("To"); |
| QTest::newRow("qsTranslate('FooContext', 'Goodbye')@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye')") << fileName << QString::fromLatin1("Farvel"); |
| // From eval |
| QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')@translatable.js") |
| << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')") << fileName << QString::fromLatin1("To"); |
| QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')@translatable.js") |
| << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')") << fileName << QString::fromLatin1("Farvel"); |
| |
| QTest::newRow("qsTranslate('FooContext', 'Goodbye', '')@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '')") << fileName << QString::fromLatin1("Farvel"); |
| |
| QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 42)@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 42)") << fileName << QString::fromLatin1("Goodbye"); |
| |
| QTest::newRow("qsTr('One', 'not the same one')@translatable.js") |
| << QString::fromLatin1("qsTr('One', 'not the same one')") << fileName << QString::fromLatin1("Enda en"); |
| |
| QTest::newRow("qsTr('One', 'not the same one', 42)@translatable.js") |
| << QString::fromLatin1("qsTr('One', 'not the same one', 42)") << fileName << QString::fromLatin1("One"); |
| |
| // Plural |
| QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 1)") << fileName << QString::fromLatin1("1 fooaktig bar funnet"); |
| QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 2)") << fileName << QString::fromLatin1("2 fooaktige barer funnet"); |
| |
| // Don't exist in translation |
| QTest::newRow("qsTr('Three')@translatable.js") |
| << QString::fromLatin1("qsTr('Three')") << fileName << QString::fromLatin1("Three"); |
| QTest::newRow("qsTranslate('FooContext', 'So long')@translatable.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'So long')") << fileName << QString::fromLatin1("So long"); |
| QTest::newRow("qsTranslate('BarContext', 'Goodbye')@translatable.js") |
| << QString::fromLatin1("qsTranslate('BarContext', 'Goodbye')") << fileName << QString::fromLatin1("Goodbye"); |
| |
| // Translate strings from the second script (translatable2.js) |
| |
| QString fileName2 = QString::fromLatin1("translatable2.js"); |
| QTest::newRow("qsTr('Three')@translatable2.js") |
| << QString::fromLatin1("qsTr('Three')") << fileName2 << QString::fromLatin1("Tre"); |
| QTest::newRow("qsTr('Happy birthday!')@translatable2.js") |
| << QString::fromLatin1("qsTr('Happy birthday!')") << fileName2 << QString::fromLatin1("Gratulerer med dagen!"); |
| |
| // Not translated because translation is only in translatable.js |
| QTest::newRow("qsTr('One')@translatable2.js") |
| << QString::fromLatin1("qsTr('One')") << fileName2 << QString::fromLatin1("One"); |
| QTest::newRow("(function() { return qsTr('One'); })()@translatable2.js") |
| << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName2 << QString::fromLatin1("One"); |
| |
| // For qsTranslate() the filename shouldn't matter |
| QTest::newRow("qsTranslate('FooContext', 'Two')@translatable2.js") |
| << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName2 << QString::fromLatin1("To"); |
| QTest::newRow("qsTranslate('BarContext', 'Congratulations!')@translatable.js") |
| << QString::fromLatin1("qsTranslate('BarContext', 'Congratulations!')") << fileName << QString::fromLatin1("Gratulerer!"); |
| } |
| |
| void tst_QJSEngine::translateScript() |
| { |
| QFETCH(QString, expression); |
| QFETCH(QString, fileName); |
| QFETCH(QString, expectedTranslation); |
| |
| QJSEngine engine; |
| |
| TranslationScope tranScope(":/translations/translatable_la"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation); |
| } |
| |
| void tst_QJSEngine::translateScript_crossScript() |
| { |
| QJSEngine engine; |
| TranslationScope tranScope(":/translations/translatable_la"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QString fileName = QString::fromLatin1("translatable.js"); |
| QString fileName2 = QString::fromLatin1("translatable2.js"); |
| // qsTr() should use the innermost filename as context |
| engine.evaluate("function foo(s) { return bar(s); }", fileName); |
| engine.evaluate("function bar(s) { return qsTr(s); }", fileName2); |
| QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Tre")); |
| QCOMPARE(engine.evaluate("bar('Three')", fileName).toString(), QString::fromLatin1("Tre")); |
| QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("One")); |
| |
| engine.evaluate("function foo(s) { return bar(s); }", fileName2); |
| engine.evaluate("function bar(s) { return qsTr(s); }", fileName); |
| QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Three")); |
| QCOMPARE(engine.evaluate("bar('One')", fileName).toString(), QString::fromLatin1("En")); |
| QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("En")); |
| } |
| |
| void tst_QJSEngine::translateScript_trNoOp() |
| { |
| QJSEngine engine; |
| TranslationScope tranScope(":/translations/translatable_la"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QVERIFY(engine.evaluate("QT_TR_NOOP()").isUndefined()); |
| QCOMPARE(engine.evaluate("QT_TR_NOOP('One')").toString(), QString::fromLatin1("One")); |
| |
| QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP()").isUndefined()); |
| QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP('FooContext')").isUndefined()); |
| QCOMPARE(engine.evaluate("QT_TRANSLATE_NOOP('FooContext', 'Two')").toString(), QString::fromLatin1("Two")); |
| } |
| |
| void tst_QJSEngine::translateScript_callQsTrFromCpp() |
| { |
| QJSEngine engine; |
| TranslationScope tranScope(":/translations/translatable_la"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| // There is no context, but it shouldn't crash |
| QCOMPARE(engine.globalObject().property("qsTr").call(QJSValueList() << "One").toString(), QString::fromLatin1("One")); |
| } |
| |
| void tst_QJSEngine::translateWithInvalidArgs_data() |
| { |
| QTest::addColumn<QString>("expression"); |
| QTest::addColumn<QString>("expectedError"); |
| |
| QTest::newRow("qsTr()") << "qsTr()" << "Error: qsTr() requires at least one argument"; |
| QTest::newRow("qsTr(123)") << "qsTr(123)" << "Error: qsTr(): first argument (sourceText) must be a string"; |
| QTest::newRow("qsTr('foo', 123)") << "qsTr('foo', 123)" << "Error: qsTr(): second argument (disambiguation) must be a string"; |
| QTest::newRow("qsTr('foo', 'bar', 'baz')") << "qsTr('foo', 'bar', 'baz')" << "Error: qsTr(): third argument (n) must be a number"; |
| QTest::newRow("qsTr('foo', 'bar', true)") << "qsTr('foo', 'bar', true)" << "Error: qsTr(): third argument (n) must be a number"; |
| |
| QTest::newRow("qsTranslate()") << "qsTranslate()" << "Error: qsTranslate() requires at least two arguments"; |
| QTest::newRow("qsTranslate('foo')") << "qsTranslate('foo')" << "Error: qsTranslate() requires at least two arguments"; |
| QTest::newRow("qsTranslate(123, 'foo')") << "qsTranslate(123, 'foo')" << "Error: qsTranslate(): first argument (context) must be a string"; |
| QTest::newRow("qsTranslate('foo', 123)") << "qsTranslate('foo', 123)" << "Error: qsTranslate(): second argument (sourceText) must be a string"; |
| QTest::newRow("qsTranslate('foo', 'bar', 123)") << "qsTranslate('foo', 'bar', 123)" << "Error: qsTranslate(): third argument (disambiguation) must be a string"; |
| |
| QTest::newRow("qsTrId()") << "qsTrId()" << "Error: qsTrId() requires at least one argument"; |
| QTest::newRow("qsTrId(123)") << "qsTrId(123)" << "TypeError: qsTrId(): first argument (id) must be a string"; |
| QTest::newRow("qsTrId('foo', 'bar')") << "qsTrId('foo', 'bar')" << "TypeError: qsTrId(): second argument (n) must be a number"; |
| } |
| |
| void tst_QJSEngine::translateWithInvalidArgs() |
| { |
| QFETCH(QString, expression); |
| QFETCH(QString, expectedError); |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| QJSValue result = engine.evaluate(expression); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.toString(), expectedError); |
| } |
| |
| void tst_QJSEngine::translationContext_data() |
| { |
| QTest::addColumn<QString>("path"); |
| QTest::addColumn<QString>("text"); |
| QTest::addColumn<QString>("expectedTranslation"); |
| |
| QTest::newRow("translatable.js") << "translatable.js" << "One" << "En"; |
| QTest::newRow("/translatable.js") << "/translatable.js" << "One" << "En"; |
| QTest::newRow("/foo/translatable.js") << "/foo/translatable.js" << "One" << "En"; |
| QTest::newRow("/foo/bar/translatable.js") << "/foo/bar/translatable.js" << "One" << "En"; |
| QTest::newRow("./translatable.js") << "./translatable.js" << "One" << "En"; |
| QTest::newRow("../translatable.js") << "../translatable.js" << "One" << "En"; |
| QTest::newRow("foo/translatable.js") << "foo/translatable.js" << "One" << "En"; |
| QTest::newRow("file:///home/qt/translatable.js") << "file:///home/qt/translatable.js" << "One" << "En"; |
| QTest::newRow(":/resources/translatable.js") << ":/resources/translatable.js" << "One" << "En"; |
| QTest::newRow("/translatable.js.foo") << "/translatable.js.foo" << "One" << "En"; |
| QTest::newRow("/translatable.txt") << "/translatable.txt" << "One" << "En"; |
| QTest::newRow("translatable") << "translatable" << "One" << "En"; |
| QTest::newRow("foo/translatable") << "foo/translatable" << "One" << "En"; |
| |
| QTest::newRow("translatable.js/") << "translatable.js/" << "One" << "One"; |
| QTest::newRow("nosuchscript.js") << "" << "One" << "One"; |
| QTest::newRow("(empty)") << "" << "One" << "One"; |
| } |
| |
| void tst_QJSEngine::translationContext() |
| { |
| TranslationScope tranScope(":/translations/translatable_la"); |
| |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QFETCH(QString, path); |
| QFETCH(QString, text); |
| QFETCH(QString, expectedTranslation); |
| QJSValue ret = engine.evaluate(QString::fromLatin1("qsTr('%0')").arg(text), path); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), expectedTranslation); |
| } |
| |
| void tst_QJSEngine::translateScriptIdBased() |
| { |
| QJSEngine engine; |
| |
| TranslationScope tranScope(":/translations/idtranslatable_la"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QString fileName = QString::fromLatin1("idtranslatable.js"); |
| |
| QHash<QString, QString> expectedTranslations; |
| expectedTranslations["qtn_foo_bar"] = "First string"; |
| expectedTranslations["qtn_needle"] = "Second string"; |
| expectedTranslations["qtn_haystack"] = "Third string"; |
| expectedTranslations["qtn_bar_baz"] = "Fourth string"; |
| |
| QHash<QString, QString>::const_iterator it; |
| for (it = expectedTranslations.constBegin(); it != expectedTranslations.constEnd(); ++it) { |
| for (int x = 0; x < 2; ++x) { |
| QString fn; |
| if (x) |
| fn = fileName; |
| // Top-level |
| QCOMPARE(engine.evaluate(QString::fromLatin1("qsTrId('%0')") |
| .arg(it.key()), fn).toString(), |
| it.value()); |
| QCOMPARE(engine.evaluate(QString::fromLatin1("QT_TRID_NOOP('%0')") |
| .arg(it.key()), fn).toString(), |
| it.key()); |
| // From function |
| QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return qsTrId('%0'); })()") |
| .arg(it.key()), fn).toString(), |
| it.value()); |
| QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return QT_TRID_NOOP('%0'); })()") |
| .arg(it.key()), fn).toString(), |
| it.key()); |
| } |
| } |
| |
| // Plural form |
| QCOMPARE(engine.evaluate("qsTrId('qtn_bar_baz', 10)").toString(), |
| QString::fromLatin1("10 fooish bar(s) found")); |
| QCOMPARE(engine.evaluate("qsTrId('qtn_foo_bar', 10)").toString(), |
| QString::fromLatin1("qtn_foo_bar")); // Doesn't have plural |
| } |
| |
| // How to add a new test row: |
| // - Find a nice list of Unicode characters to choose from |
| // - Write source string/context/comment in .js using Unicode escape sequences (\uABCD) |
| // - Update corresponding .ts file (e.g. lupdate foo.js -ts foo.ts -codecfortr UTF-8) |
| // - Enter translation in Linguist |
| // - Update corresponding .qm file (e.g. lrelease foo.ts) |
| // - Evaluate script that performs translation; make sure the correct result is returned |
| // (e.g. by setting the resulting string as the text of a QLabel and visually verifying |
| // that it looks the same as what you entered in Linguist :-) ) |
| // - Generate the expectedTranslation column data using toUtf8().toHex() |
| void tst_QJSEngine::translateScriptUnicode_data() |
| { |
| QTest::addColumn<QString>("expression"); |
| QTest::addColumn<QString>("fileName"); |
| QTest::addColumn<QString>("expectedTranslation"); |
| |
| QString fileName = QString::fromLatin1("translatable-unicode.js"); |
| QTest::newRow("qsTr('H\\u2082O')@translatable-unicode.js") |
| << QString::fromLatin1("qsTr('H\\u2082O')") << fileName << QString::fromUtf8("\xcd\xbb\xcd\xbc\xcd\xbd"); |
| QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')@translatable-unicode.js") |
| << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << fileName << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2"); |
| QTest::newRow("qsTr('\\u0391\\u0392\\u0393')@translatable-unicode.js") |
| << QString::fromLatin1("qsTr('\\u0391\\u0392\\u0393')") << fileName << QString::fromUtf8("\xd3\x9c\xd2\xb4\xd1\xbc"); |
| QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')@translatable-unicode.js") |
| << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')") << fileName << QString::fromUtf8("\xd8\xae\xd8\xb3\xd8\xb3"); |
| QTest::newRow("qsTr('H\\u2082O', 'not the same H\\u2082O')@translatable-unicode.js") |
| << QString::fromLatin1("qsTr('H\\u2082O', 'not the same H\\u2082O')") << fileName << QString::fromUtf8("\xd4\xb6\xd5\x8a\xd5\x92"); |
| QTest::newRow("qsTr('H\\u2082O')") |
| << QString::fromLatin1("qsTr('H\\u2082O')") << QString() << QString::fromUtf8("\x48\xe2\x82\x82\x4f"); |
| QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") |
| << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << QString() << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2"); |
| } |
| |
| void tst_QJSEngine::translateScriptUnicode() |
| { |
| QFETCH(QString, expression); |
| QFETCH(QString, fileName); |
| QFETCH(QString, expectedTranslation); |
| |
| QJSEngine engine; |
| |
| TranslationScope tranScope(":/translations/translatable-unicode"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation); |
| } |
| |
| void tst_QJSEngine::translateScriptUnicodeIdBased_data() |
| { |
| QTest::addColumn<QString>("expression"); |
| QTest::addColumn<QString>("expectedTranslation"); |
| |
| QTest::newRow("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1'')") |
| << QString::fromLatin1("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1')") << QString::fromUtf8("\xc6\xa7\xc6\xb0\xc6\x88\xc8\xbc\xc8\x9d\xc8\xbf\xc8\x99"); |
| QTest::newRow("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") |
| << QString::fromLatin1("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") << QString::fromUtf8("\xc7\xa0\xc8\xa1\xc8\x8b\xc8\x85\xc8\x95"); |
| QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") |
| << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") << QString::fromUtf8("\x31\x30\x20\xc6\x92\xc6\xa1\xc7\x92\x28\xc8\x99\x29"); |
| QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") |
| << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") << QString::fromUtf8("\xc6\x91\xc6\xb0\xc7\xb9"); |
| QTest::newRow("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") |
| << QString::fromLatin1("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") << QString::fromUtf8("\xc7\x8d\xc6\x80\xc6\xa8\xc6\x90\xc6\x9e\xc6\xab"); |
| } |
| |
| void tst_QJSEngine::translateScriptUnicodeIdBased() |
| { |
| QFETCH(QString, expression); |
| QFETCH(QString, expectedTranslation); |
| |
| QJSEngine engine; |
| |
| TranslationScope tranScope(":/translations/idtranslatable-unicode"); |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| |
| QCOMPARE(engine.evaluate(expression).toString(), expectedTranslation); |
| } |
| |
| void tst_QJSEngine::translateFromBuiltinCallback() |
| { |
| QJSEngine eng; |
| eng.installExtensions(QJSEngine::TranslationExtension); |
| |
| // Callback has no translation context. |
| eng.evaluate("function foo() { qsTr('foo'); }"); |
| |
| // Stack at translation time will be: |
| // qsTr, foo, forEach, global |
| // qsTr() needs to walk to the outer-most (global) frame before it finds |
| // a translation context, and this should not crash. |
| eng.evaluate("[10,20].forEach(foo)", "script.js"); |
| } |
| |
| void tst_QJSEngine::translationFilePath_data() |
| { |
| QTest::addColumn<QString>("filename"); |
| |
| QTest::newRow("relative") << QStringLiteral("script.js"); |
| QTest::newRow("absolute unix") << QStringLiteral("/script.js"); |
| QTest::newRow("absolute /windows/") << QStringLiteral("c:/script.js"); |
| #ifdef Q_OS_WIN |
| QTest::newRow("absolute \\windows\\") << QStringLiteral("c:\\script.js"); |
| #endif |
| QTest::newRow("relative url") << QStringLiteral("file://script.js"); |
| QTest::newRow("absolute url unix") << QStringLiteral("file:///script.js"); |
| QTest::newRow("absolute url windows") << QStringLiteral("file://c:/script.js"); |
| } |
| |
| class DummyTranslator : public QTranslator |
| { |
| Q_OBJECT |
| |
| public: |
| DummyTranslator(const char *sourceText) |
| : srcTxt(sourceText) |
| {} |
| |
| QString translate(const char *context, const char *sourceText, const char *disambiguation, int n) const override |
| { |
| Q_UNUSED(disambiguation); |
| Q_UNUSED(n); |
| |
| if (srcTxt == sourceText) |
| ctxt = QByteArray(context); |
| |
| return QString(sourceText); |
| } |
| |
| bool isEmpty() const override |
| { |
| return false; |
| } |
| |
| const char *sourceText() const |
| { return srcTxt.constData(); } |
| |
| QByteArray context() const |
| { return ctxt; } |
| |
| private: |
| QByteArray srcTxt; |
| mutable QByteArray ctxt; |
| }; |
| |
| void tst_QJSEngine::translationFilePath() |
| { |
| QFETCH(QString, filename); |
| |
| DummyTranslator translator("some text"); |
| QCoreApplication::installTranslator(&translator); |
| QByteArray scriptContent = QByteArray("qsTr('%1')").replace("%1", translator.sourceText()); |
| |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| QJSValue result = engine.evaluate(scriptContent, filename); |
| QCOMPARE(translator.context(), QByteArray("script")); |
| |
| QCoreApplication::removeTranslator(&translator); |
| } |
| |
| void tst_QJSEngine::installConsoleFunctions() |
| { |
| QJSEngine engine; |
| QJSValue global = engine.globalObject(); |
| QVERIFY(global.property("console").isUndefined()); |
| QVERIFY(global.property("print").isUndefined()); |
| |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| QVERIFY(global.property("console").isObject()); |
| QVERIFY(global.property("print").isCallable()); |
| } |
| |
| void tst_QJSEngine::logging() |
| { |
| QLoggingCategory loggingCategory("js"); |
| QVERIFY(loggingCategory.isDebugEnabled()); |
| QVERIFY(loggingCategory.isWarningEnabled()); |
| QVERIFY(loggingCategory.isCriticalEnabled()); |
| |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| |
| QTest::ignoreMessage(QtDebugMsg, "console.debug"); |
| engine.evaluate("console.debug('console.debug')"); |
| QTest::ignoreMessage(QtDebugMsg, "console.log"); |
| engine.evaluate("console.log('console.log')"); |
| QTest::ignoreMessage(QtInfoMsg, "console.info"); |
| engine.evaluate("console.info('console.info')"); |
| QTest::ignoreMessage(QtWarningMsg, "console.warn"); |
| engine.evaluate("console.warn('console.warn')"); |
| QTest::ignoreMessage(QtCriticalMsg, "console.error"); |
| engine.evaluate("console.error('console.error')"); |
| |
| QTest::ignoreMessage(QtDebugMsg, ": 1"); |
| engine.evaluate("console.count()"); |
| |
| QTest::ignoreMessage(QtDebugMsg, ": 2"); |
| engine.evaluate("console.count()"); |
| } |
| |
| void tst_QJSEngine::tracing() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| |
| QTest::ignoreMessage(QtDebugMsg, "%entry (:1)"); |
| engine.evaluate("console.trace()"); |
| |
| QTest::ignoreMessage(QtDebugMsg, "a (:1)\nb (:1)\nc (:1)\n%entry (:1)"); |
| engine.evaluate("function a() { console.trace(); } function b() { a(); } function c() { b(); }"); |
| engine.evaluate("c()"); |
| } |
| |
| void tst_QJSEngine::asserts() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| |
| QTest::ignoreMessage(QtCriticalMsg, "This will fail\n%entry (:1)"); |
| engine.evaluate("console.assert(0, 'This will fail')"); |
| |
| QTest::ignoreMessage(QtCriticalMsg, "This will fail too\n%entry (:1)"); |
| engine.evaluate("console.assert(1 > 2, 'This will fail too')"); |
| } |
| |
| void tst_QJSEngine::exceptions() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| |
| QTest::ignoreMessage(QtCriticalMsg, "Exception 1\n%entry (:1)"); |
| engine.evaluate("console.exception('Exception 1')"); |
| } |
| |
| void tst_QJSEngine::installGarbageCollectionFunctions() |
| { |
| QJSEngine engine; |
| QJSValue global = engine.globalObject(); |
| QVERIFY(global.property("gc").isUndefined()); |
| |
| engine.installExtensions(QJSEngine::GarbageCollectionExtension); |
| QVERIFY(global.property("gc").isCallable()); |
| } |
| |
| void tst_QJSEngine::installAllExtensions() |
| { |
| QJSEngine engine; |
| QJSValue global = engine.globalObject(); |
| // Pick out a few properties from each extension and check that they're there. |
| QVERIFY(global.property("qsTranslate").isUndefined()); |
| QVERIFY(global.property("console").isUndefined()); |
| QVERIFY(global.property("print").isUndefined()); |
| QVERIFY(global.property("gc").isUndefined()); |
| |
| engine.installExtensions(QJSEngine::AllExtensions); |
| QVERIFY(global.property("qsTranslate").isCallable()); |
| QVERIFY(global.property("console").isObject()); |
| QVERIFY(global.property("print").isCallable()); |
| QVERIFY(global.property("gc").isCallable()); |
| } |
| |
| class ObjectWithPrivateMethods : public QObject |
| { |
| Q_OBJECT |
| private slots: |
| void myPrivateMethod() {} |
| }; |
| |
| void tst_QJSEngine::privateMethods() |
| { |
| ObjectWithPrivateMethods object; |
| QJSEngine engine; |
| QJSValue jsWrapper = engine.newQObject(&object); |
| QQmlEngine::setObjectOwnership(&object, QQmlEngine::CppOwnership); |
| |
| QSet<QString> privateMethods; |
| { |
| const QMetaObject *mo = object.metaObject(); |
| for (int i = 0; i < mo->methodCount(); ++i) { |
| const QMetaMethod method = mo->method(i); |
| if (method.access() == QMetaMethod::Private) |
| privateMethods << QString::fromUtf8(method.name()); |
| } |
| } |
| |
| QVERIFY(privateMethods.contains("myPrivateMethod")); |
| QVERIFY(privateMethods.contains("_q_reregisterTimers")); |
| privateMethods << QStringLiteral("deleteLater") << QStringLiteral("destroyed"); |
| |
| QJSValueIterator it(jsWrapper); |
| while (it.hasNext()) { |
| it.next(); |
| QVERIFY(!privateMethods.contains(it.name())); |
| } |
| } |
| |
| void tst_QJSEngine::engineForObject() |
| { |
| QObject object; |
| { |
| QJSEngine engine; |
| QVERIFY(!qjsEngine(&object)); |
| QJSValue wrapper = engine.newQObject(&object); |
| QQmlEngine::setObjectOwnership(&object, QQmlEngine::CppOwnership); |
| QVERIFY(qjsEngine(&object)); |
| #ifdef QT_DEPRECATED |
| QCOMPARE(qjsEngine(&object), wrapper.engine()); |
| #endif |
| } |
| QVERIFY(!qjsEngine(&object)); |
| } |
| |
| void tst_QJSEngine::intConversion_QTBUG43309() |
| { |
| // This failed in the interpreter: |
| QJSEngine engine; |
| QString jsCode = "var n = 0.1; var m = (n*255) | 0; m"; |
| QJSValue result = engine.evaluate( jsCode ); |
| QVERIFY(result.isNumber()); |
| QCOMPARE(result.toNumber(), 25.0); |
| } |
| |
| #ifdef QT_DEPRECATED |
| // QTBUG-44039 and QTBUG-43885: |
| void tst_QJSEngine::toFixed() |
| { |
| QJSEngine engine; |
| QJSValue result = engine.evaluate(QStringLiteral("(12.5).toFixed()")); |
| QVERIFY(result.isString()); |
| QCOMPARE(result.toString(), QStringLiteral("13")); |
| result = engine.evaluate(QStringLiteral("(12.05).toFixed(1)")); |
| QVERIFY(result.isString()); |
| QCOMPARE(result.toString(), QStringLiteral("12.1")); |
| } |
| #endif |
| |
| void tst_QJSEngine::argumentEvaluationOrder() |
| { |
| QJSEngine engine; |
| QJSValue ok = engine.evaluate( |
| "function record(arg1, arg2) {\n" |
| " parameters = [arg1, arg2]\n" |
| "}\n" |
| "function test() {\n" |
| " var i = 2;\n" |
| " record(i, i += 2);\n" |
| "}\n" |
| "test()\n" |
| "parameters[0] == 2 && parameters[1] == 4"); |
| qDebug() << ok.toString(); |
| QVERIFY(ok.isBool()); |
| QVERIFY(ok.toBool()); |
| |
| } |
| |
| class TestObject : public QObject |
| { |
| Q_OBJECT |
| public: |
| TestObject() {} |
| |
| bool called = false; |
| |
| Q_INVOKABLE void callMe(QQmlV4Function *) { |
| called = true; |
| } |
| }; |
| |
| void tst_QJSEngine::v4FunctionWithoutQML() |
| { |
| TestObject obj; |
| QJSEngine engine; |
| QJSValue wrapper = engine.newQObject(&obj); |
| QQmlEngine::setObjectOwnership(&obj, QQmlEngine::CppOwnership); |
| QVERIFY(!obj.called); |
| wrapper.property("callMe").call(); |
| QVERIFY(obj.called); |
| } |
| |
| void tst_QJSEngine::withNoContext() |
| { |
| // Don't crash (QTBUG-53794) |
| QJSEngine engine; |
| engine.evaluate("with (noContext) true"); |
| } |
| |
| void tst_QJSEngine::holeInPropertyData() |
| { |
| QJSEngine engine; |
| QJSValue ok = engine.evaluate( |
| "var o = {};\n" |
| "o.bar = 0xcccccccc;\n" |
| "o.foo = 0x55555555;\n" |
| "Object.defineProperty(o, 'bar', { get: function() { return 0xffffffff }});\n" |
| "o.bar === 0xffffffff && o.foo === 0x55555555;"); |
| QVERIFY(ok.isBool()); |
| QVERIFY(ok.toBool()); |
| } |
| |
| void tst_QJSEngine::basicBlockMergeAfterLoopPeeling() |
| { |
| QJSEngine engine; |
| QJSValue ok = engine.evaluate( |
| "function crashMe() {\n" |
| " var seen = false;\n" |
| " while (globalVar) {\n" |
| " if (seen)\n" |
| " return;\n" |
| " seen = true;\n" |
| " }\n" |
| "}\n"); |
| QVERIFY(!ok.isCallable()); |
| |
| } |
| |
| void tst_QJSEngine::modulusCrash() |
| { |
| QJSEngine engine; |
| QJSValue result = engine.evaluate( |
| "var a = -2147483648; var b = -1; var c = a % b; c;" |
| ); |
| QVERIFY(result.isNumber() && result.toNumber() == 0.); |
| } |
| |
| void tst_QJSEngine::malformedExpression() |
| { |
| QJSEngine engine; |
| engine.evaluate("5%55555&&5555555\n7-0"); |
| } |
| |
| void tst_QJSEngine::scriptScopes() |
| { |
| QJSEngine engine; |
| |
| QJSValue def = engine.evaluate("'use strict'; function foo() { return 42 }"); |
| QVERIFY(!def.isError()); |
| QJSValue globalObject = engine.globalObject(); |
| QJSValue foo = globalObject.property("foo"); |
| QVERIFY(foo.isObject()); |
| QVERIFY(foo.isCallable()); |
| |
| QJSValue use = engine.evaluate("'use strict'; foo()"); |
| QVERIFY(use.isNumber()); |
| QCOMPARE(use.toInt(), 42); |
| } |
| |
| void tst_QJSEngine::binaryNumbers() |
| { |
| QJSEngine engine; |
| |
| QJSValue result = engine.evaluate("0b1001"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == 9); |
| |
| result = engine.evaluate("0B1001"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == 9); |
| |
| result = engine.evaluate("0b2"); |
| QVERIFY(result.isError()); |
| } |
| |
| void tst_QJSEngine::octalNumbers() |
| { |
| QJSEngine engine; |
| |
| QJSValue result = engine.evaluate("0o11"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == 9); |
| |
| result = engine.evaluate("0O11"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == 9); |
| |
| result = engine.evaluate("0o9"); |
| QVERIFY(result.isError()); |
| } |
| |
| void tst_QJSEngine::incrementAfterNewline() |
| { |
| QJSEngine engine; |
| |
| QJSValue result = engine.evaluate("var x = 0; if (\n++x) x; else -x;"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == 1); |
| |
| result = engine.evaluate("var x = 0; if (\n--x) x; else -x;"); |
| QVERIFY(result.isNumber()); |
| QVERIFY(result.toNumber() == -1); |
| } |
| |
| void tst_QJSEngine::deleteInsideForIn() |
| { |
| QJSEngine engine; |
| |
| QJSValue iterationCount = engine.evaluate( |
| "var o = { a: 1, b: 2, c: 3, d: 4};\n" |
| "var count = 0;\n" |
| "for (var prop in o) { count++; delete o[prop]; }\n" |
| "count"); |
| QVERIFY(iterationCount.isNumber()); |
| QCOMPARE(iterationCount.toInt(), 4); |
| } |
| |
| void tst_QJSEngine::functionToString_data() |
| { |
| QTest::addColumn<QString>("source"); |
| QTest::addColumn<QString>("expectedString"); |
| |
| QTest::newRow("named function") << QString::fromLatin1("function f() {}; f.toString()") |
| << QString::fromLatin1("function f() { [native code] }"); |
| QTest::newRow("anonymous function") << QString::fromLatin1("(function() {}).toString()") |
| << QString::fromLatin1("function() { [native code] }"); |
| } |
| |
| // Tests that function.toString() prints the function's name. |
| void tst_QJSEngine::functionToString() |
| { |
| QFETCH(QString, source); |
| QFETCH(QString, expectedString); |
| |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::AllExtensions); |
| QJSValue evaluationResult = engine.evaluate(source); |
| QVERIFY(!evaluationResult.isError()); |
| QCOMPARE(evaluationResult.toString(), expectedString); |
| } |
| |
| void tst_QJSEngine::stringReplace() |
| { |
| QJSEngine engine; |
| |
| QJSValue val = engine.evaluate("'x'.replace('x', '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$1")); |
| |
| val = engine.evaluate("'x'.replace('x', '$10')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$10")); |
| |
| val = engine.evaluate("'x'.replace('x', '$01')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$01")); |
| |
| val = engine.evaluate("'x'.replace('x', '$0')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$0")); |
| |
| val = engine.evaluate("'x'.replace('x', '$00')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$00")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("x")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$01')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("x")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$2')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$2")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$02')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$02")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$0')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$0")); |
| |
| val = engine.evaluate("'x'.replace(/(x)/, '$00')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("$00")); |
| |
| val = engine.evaluate("'x'.replace(/()()()()()()()()()(x)/, '$11')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("1")); |
| |
| val = engine.evaluate("'x'.replace(/()()()()()()()()()(x)/, '$10')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("x")); |
| |
| val = engine.evaluate("'123'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123")); |
| |
| val = engine.evaluate("'123.00'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123")); |
| |
| val = engine.evaluate("'123.0'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123")); |
| |
| val = engine.evaluate("'123.'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123")); |
| |
| val = engine.evaluate("'123.50'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123.5")); |
| |
| val = engine.evaluate("'123.05'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("123.05")); |
| |
| val = engine.evaluate("'0.050'.replace(/\\.0*$|(\\.\\d*[1-9])(0+)$/, '$1')"); |
| QVERIFY(val.isString()); |
| QCOMPARE(val.toString(), QString("0.05")); |
| } |
| |
| void tst_QJSEngine::protoChanges_QTBUG68369() |
| { |
| QJSEngine engine; |
| QJSValue ok = engine.evaluate( |
| "var o = { x: true };" |
| "var p1 = {};" |
| "var p2 = {};" |
| "o.__proto__ = p1;" |
| "o.__proto__ = p2;" |
| "o.__proto__ = p1;" |
| "p1.y = true;" |
| "o.y" |
| ); |
| QVERIFY(ok.toBool() == true); |
| } |
| |
| void tst_QJSEngine::multilineStrings() |
| { |
| QJSEngine engine; |
| QJSValue result = engine.evaluate( |
| "var x = `a\nb`; x;" |
| ); |
| QVERIFY(result.isString()); |
| QVERIFY(result.toString() == QStringLiteral("a\nb")); |
| |
| } |
| |
| void tst_QJSEngine::throwError() |
| { |
| QJSEngine engine; |
| QJSValue wrappedThis = engine.newQObject(this); |
| QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); |
| engine.globalObject().setProperty("testCase", wrappedThis); |
| |
| QJSValue result = engine.evaluate( |
| "function test(){\n" |
| "try {\n" |
| " return testCase.throwingCppMethod1();\n" |
| "} catch (error) {\n" |
| " return error;\n" |
| "}\n" |
| "return \"not reached!\";\n" |
| "}\n" |
| "test();" |
| ); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.errorType(), QJSValue::GenericError); |
| QCOMPARE(result.property("lineNumber").toString(), "3"); |
| QCOMPARE(result.property("message").toString(), "blub"); |
| QVERIFY(!result.property("stack").isUndefined()); |
| } |
| |
| void tst_QJSEngine::throwErrorObject() |
| { |
| QJSEngine engine; |
| QJSValue wrappedThis = engine.newQObject(this); |
| QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); |
| engine.globalObject().setProperty("testCase", wrappedThis); |
| |
| QJSValue result = engine.evaluate( |
| "function test(){\n" |
| " try {\n" |
| " testCase.throwingCppMethod2();\n" |
| " } catch (error) {\n" |
| " if (error instanceof TypeError) {\n" |
| " return error;\n" |
| " }\n" |
| " }\n" |
| " return null;\n" |
| "}\n" |
| "test();" |
| ); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.errorType(), QJSValue::TypeError); |
| QCOMPARE(result.property("lineNumber").toString(), "3"); |
| QCOMPARE(result.property("message").toString(), "Wrong type"); |
| QVERIFY(!result.property("stack").isUndefined()); |
| } |
| |
| void tst_QJSEngine::returnError() |
| { |
| QJSEngine engine; |
| QJSValue wrappedThis = engine.newQObject(this); |
| QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); |
| engine.globalObject().setProperty("testCase", wrappedThis); |
| |
| QJSValue result = engine.evaluate("testCase.throwingCppMethod3()"); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.errorType(), QJSValue::EvalError); |
| QCOMPARE(result.property("lineNumber").toString(), "1"); |
| QCOMPARE(result.property("message").toString(), "Something is wrong"); |
| QVERIFY(!result.property("stack").isUndefined()); |
| } |
| |
| QJSValue tst_QJSEngine::throwingCppMethod1() |
| { |
| qjsEngine(this)->throwError("blub"); |
| return QJSValue(47); |
| } |
| |
| void tst_QJSEngine::throwingCppMethod2() |
| { |
| qjsEngine(this)->throwError(QJSValue::TypeError, "Wrong type"); |
| } |
| |
| QJSValue tst_QJSEngine::throwingCppMethod3() |
| { |
| return qjsEngine(this)->newErrorObject(QJSValue::EvalError, "Something is wrong"); |
| } |
| |
| void tst_QJSEngine::mathMinMax() |
| { |
| QJSEngine engine; |
| |
| QJSValue result = engine.evaluate("var a = .5; Math.min(1, 2, 3.5 + a, '5')"); |
| QCOMPARE(result.toNumber(), 1.0); |
| QVERIFY(QJSValuePrivate::getValue(&result) != nullptr); |
| QVERIFY(QJSValuePrivate::getValue(&result)->isInteger()); |
| |
| result = engine.evaluate("var a = .5; Math.max('0', 1, 2, 3.5 + a)"); |
| QCOMPARE(result.toNumber(), 4.0); |
| QVERIFY(QJSValuePrivate::getValue(&result) != nullptr); |
| QVERIFY(QJSValuePrivate::getValue(&result)->isInteger()); |
| } |
| |
| void tst_QJSEngine::importModule() |
| { |
| // This is just a basic test for the API. Primary test coverage is via the ES test suite. |
| QJSEngine engine; |
| QJSValue ns = engine.importModule(QStringLiteral(":/testmodule.mjs")); |
| QCOMPARE(ns.property("value").toInt(), 42); |
| ns.property("sideEffect").call(); |
| |
| // Make sure that importing a second time will return the same instance. |
| QJSValue secondNamespace = engine.importModule(QStringLiteral(":/testmodule.mjs")); |
| QCOMPARE(secondNamespace.property("value").toInt(), 43); |
| } |
| |
| void tst_QJSEngine::importModuleRelative() |
| { |
| const QString oldWorkingDirectory = QDir::currentPath(); |
| auto workingDirectoryGuard = qScopeGuard([oldWorkingDirectory]{ |
| QDir::setCurrent(oldWorkingDirectory); |
| }); |
| |
| QTemporaryDir tempDir; |
| QVERIFY(tempDir.isValid()); |
| QDir::setCurrent(tempDir.path()); |
| |
| { |
| QFile f(QStringLiteral("relativemodule.mjs")); |
| QVERIFY(f.open(QIODevice::WriteOnly|QIODevice::Truncate)); |
| f.write(QByteArrayLiteral("var value = 100; export { value }; export function change() { value = value + 1 }")); |
| } |
| |
| QJSEngine engine; |
| |
| { |
| QJSValue module = engine.importModule(QStringLiteral("relativemodule.mjs")); |
| QVERIFY2(!module.isError(), qPrintable(module.toString())); |
| QCOMPARE(module.property("value").toInt(), 100); |
| |
| module.property("change").call(); |
| } |
| |
| { |
| QJSValue sameModule = engine.importModule(tempDir.filePath(QStringLiteral("relativemodule.mjs"))); |
| QVERIFY2(!sameModule.isError(), qPrintable(sameModule.toString())); |
| QCOMPARE(sameModule.property("value").toInt(), 101); |
| } |
| } |
| |
| void tst_QJSEngine::importModuleWithLexicallyScopedVars() |
| { |
| QJSEngine engine; |
| QJSValue ns = engine.importModule(QStringLiteral(":/modulewithlexicals.mjs")); |
| QVERIFY2(!ns.isError(), qPrintable(ns.toString())); |
| QCOMPARE(ns.property("main").call().toInt(), 10); |
| } |
| |
| void tst_QJSEngine::importExportErrors() |
| { |
| { |
| QJSEngine engine; |
| QJSValue result = engine.importModule(QStringLiteral(":/importerror1.mjs")); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.property("lineNumber").toInt(), 2); |
| } |
| { |
| QJSEngine engine; |
| QJSValue result = engine.importModule(QStringLiteral(":/exporterror1.mjs")); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.property("lineNumber").toInt(), 2); |
| } |
| } |
| |
| void tst_QJSEngine::equality() |
| { |
| QJSEngine engine; |
| QJSValue ok = engine.evaluate("(0 < 0) ? 'ko' : 'ok'"); |
| QVERIFY(ok.isString()); |
| QCOMPARE(ok.toString(), QString("ok")); |
| } |
| |
| void tst_QJSEngine::aggressiveGc() |
| { |
| const QByteArray origAggressiveGc = qgetenv("QV4_MM_AGGRESSIVE_GC"); |
| qputenv("QV4_MM_AGGRESSIVE_GC", "true"); |
| { |
| QJSEngine engine; // ctor crashes if core allocation methods don't properly scope things. |
| QJSValue obj = engine.newObject(); |
| QVERIFY(obj.isObject()); |
| } |
| qputenv("QV4_MM_AGGRESSIVE_GC", origAggressiveGc); |
| } |
| |
| void tst_QJSEngine::noAccumulatorInTemplateLiteral() |
| { |
| const QByteArray origAggressiveGc = qgetenv("QV4_MM_AGGRESSIVE_GC"); |
| qputenv("QV4_MM_AGGRESSIVE_GC", "true"); |
| { |
| QJSEngine engine; |
| |
| // getTemplateLiteral should not save the accumulator as it's garbage and trashes |
| // the next GC run. Instead, we want to see the stack overflow error. |
| QJSValue value = engine.evaluate("function a(){\nS=o=>s\nFunction``\na()}a()"); |
| |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), "RangeError: Maximum call stack size exceeded."); |
| } |
| qputenv("QV4_MM_AGGRESSIVE_GC", origAggressiveGc); |
| } |
| |
| void tst_QJSEngine::interrupt_data() |
| { |
| QTest::addColumn<int>("jitThreshold"); |
| QTest::addColumn<QString>("code"); |
| |
| const int big = (1 << 24); |
| for (int i = 0; i <= big; i += big) { |
| const char *mode = i ? "interpret" : "jit"; |
| QTest::addRow("for with content / %s", mode) << i << "var a = 0; for (;;) { a += 2; }"; |
| QTest::addRow("for empty / %s", mode) << i << "for (;;) {}"; |
| QTest::addRow("for continue / %s", mode) << i << "for (;;) { continue; }"; |
| QTest::addRow("while with content / %s", mode) << i << "var a = 0; while (true) { a += 2; }"; |
| QTest::addRow("while empty / %s", mode) << i << "while (true) {}"; |
| QTest::addRow("while continue / %s", mode) << i << "while (true) { continue; }"; |
| QTest::addRow("do with content / %s", mode) << i << "var a = 0; do { a += 2; } while (true);"; |
| QTest::addRow("do empty / %s", mode) << i << "do {} while (true);"; |
| QTest::addRow("do continue / %s", mode) << i << "do { continue; } while (true);"; |
| QTest::addRow("nested loops / %s", mode) << i << "while (true) { for (;;) {} }"; |
| QTest::addRow("labeled continue / %s", mode) << i << "a: while (true) { for (;;) { continue a; } }"; |
| QTest::addRow("labeled break / %s", mode) << i << "while (true) { a: for (;;) { break a; } }"; |
| QTest::addRow("tail call / %s", mode) << i << "'use strict';\nfunction x() { return x(); }; x();"; |
| QTest::addRow("huge array join / %s", mode) << i << "Array(1E9)|1"; |
| } |
| } |
| |
| class TemporaryJitThreshold |
| { |
| Q_DISABLE_COPY_MOVE(TemporaryJitThreshold) |
| public: |
| TemporaryJitThreshold(int threshold) { |
| m_wasSet = qEnvironmentVariableIsSet(m_envVar); |
| m_value = qgetenv(m_envVar); |
| qputenv(m_envVar, QByteArray::number(threshold)); |
| } |
| |
| ~TemporaryJitThreshold() |
| { |
| if (m_wasSet) |
| qputenv(m_envVar, m_value); |
| else |
| qunsetenv(m_envVar); |
| } |
| |
| private: |
| const char *m_envVar = "QV4_JIT_CALL_THRESHOLD"; |
| bool m_wasSet = false; |
| QByteArray m_value; |
| }; |
| |
| void tst_QJSEngine::interrupt() |
| { |
| #if QT_CONFIG(cxx11_future) |
| QFETCH(int, jitThreshold); |
| QFETCH(QString, code); |
| |
| TemporaryJitThreshold threshold(jitThreshold); |
| Q_UNUSED(threshold); |
| |
| QJSEngine *engineInThread = nullptr; |
| QScopedPointer<QThread> worker(QThread::create([&engineInThread, &code, jitThreshold](){ |
| QJSEngine jsEngine; |
| engineInThread = &jsEngine; |
| QJSValue result = jsEngine.evaluate(code); |
| QVERIFY(jsEngine.isInterrupted()); |
| QVERIFY(result.isError()); |
| QCOMPARE(result.toString(), QString::fromLatin1("Error: Interrupted")); |
| engineInThread = nullptr; |
| })); |
| worker->start(); |
| |
| QTRY_VERIFY(engineInThread); |
| |
| engineInThread->setInterrupted(true); |
| |
| QVERIFY(worker->wait()); |
| QVERIFY(!engineInThread); |
| #else |
| QSKIP("This test requires C++11 futures"); |
| #endif |
| } |
| |
| void tst_QJSEngine::triggerBackwardJumpWithDestructuring() |
| { |
| QJSEngine engine; |
| auto value = engine.evaluate( |
| "function makeArray(n) { return [...Array(n).keys()]; }\n" |
| "for (let i=0;i<100;++i) {\n" |
| " let arr = makeArray(20)\n" |
| " arr.sort( (a, b) => b - a )\n" |
| "}" |
| ); |
| QVERIFY(!value.isError()); |
| } |
| |
| void tst_QJSEngine::arrayConcatOnSparseArray() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::GarbageCollectionExtension); |
| const auto value = engine.evaluate( |
| "(function() {\n" |
| " const v4 = [1,2,3];\n" |
| " const v7 = [4,5];\n" |
| " v7.length = 1337;\n" |
| " const v9 = v4.concat(v7);\n" |
| " gc();\n" |
| " return v9;\n" |
| "})();"); |
| QCOMPARE(value.property("length").toInt(), 1340); |
| for (int i = 0; i < 5; ++i) |
| QCOMPARE(value.property(i).toInt(), i + 1); |
| for (int i = 5; i < 1340; ++i) |
| QVERIFY(value.property(i).isUndefined()); |
| } |
| |
| void tst_QJSEngine::sortSparseArray() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| const auto value = engine.evaluate( |
| "(function() {\n" |
| " var sparse = [0];\n" |
| " sparse = Object.defineProperty(sparse, \"10\", " |
| " {get: ()=>{return 2}, set: ()=>{return 2}} );\n" |
| " return Array.prototype.sort.call(sparse, ()=>{});\n" |
| "})();"); |
| |
| QCOMPARE(value.property("length").toInt(), 11); |
| QVERIFY(value.property(0).isNumber()); |
| QCOMPARE(value.property(0).toInt(), 0); |
| QVERIFY(value.property(1).isNumber()); |
| QCOMPARE(value.property(1).toInt(), 2); |
| QVERIFY(value.property(10).isUndefined()); |
| } |
| |
| void tst_QJSEngine::compileBrokenRegexp() |
| { |
| QJSEngine engine; |
| const auto value = engine.evaluate( |
| "(function() {" |
| "var ret = new RegExp(Array(4097).join(" |
| " String.fromCharCode(58)) + Array(4097).join(String.fromCharCode(480)) " |
| " + Array(65537).join(String.fromCharCode(5307)));" |
| "return RegExp.prototype.compile.call(ret, 'a','b');" |
| "})();" |
| ); |
| |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), "SyntaxError: Invalid flags supplied to RegExp constructor"); |
| } |
| |
| void tst_QJSEngine::tostringRecursionCheck() |
| { |
| QJSEngine engine; |
| auto value = engine.evaluate(R"js( |
| var a = {}; |
| var b = new Array(1337); |
| function main() { |
| var ret = a.toLocaleString; |
| b[1] = ret; |
| Array = {}; |
| Object.toString = b[1]; |
| var ret = String.prototype.lastIndexOf.call({}, b[1]); |
| var ret = String.prototype.charAt.call(Function, Object); |
| } |
| main(); |
| )js"); |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded.")); |
| } |
| |
| void tst_QJSEngine::arrayIncludesWithLargeArray() |
| { |
| QJSEngine engine; |
| auto value = engine.evaluate(R"js( |
| let arr = new Array(10000000) |
| arr.includes(42) |
| )js"); |
| QVERIFY(value.isBool()); |
| QCOMPARE(value.toBool(), false); |
| } |
| |
| void tst_QJSEngine::printCircularArray() |
| { |
| QJSEngine engine; |
| engine.installExtensions(QJSEngine::ConsoleExtension); |
| QTest::ignoreMessage(QtMsgType::QtDebugMsg, "[[Circular]]"); |
| auto value = engine.evaluate(R"js( |
| let v1 = [] |
| v1.push(v1) |
| console.log(v1) |
| )js"); |
| } |
| |
| void tst_QJSEngine::sortNonStringArray() |
| { |
| QJSEngine engine; |
| const auto value = engine.evaluate( |
| "const v4 = [Symbol.iterator, 1];" |
| "const v5 = v4.sort();" |
| ); |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), "TypeError: Cannot convert a symbol to a string."); |
| } |
| |
| void tst_QJSEngine::iterateInvalidProxy() |
| { |
| QJSEngine engine; |
| const auto value = engine.evaluate( |
| "const v1 = new Proxy(Reflect, Reflect);" |
| "for (const v2 in v1) {}" |
| "const v3 = { getOwnPropertyDescriptor: eval, getPrototypeOf: eval };" |
| "const v4 = new Proxy(v3, v3);" |
| "for (const v5 in v4) {}" |
| ); |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), "TypeError: Type error"); |
| } |
| |
| void tst_QJSEngine::applyOnHugeArray() |
| { |
| QJSEngine engine; |
| const auto value = engine.evaluate( |
| "var a = new Array(10);" |
| "a[536870912] = Function;" |
| "Function.apply('aaaaaaaa', a);" |
| ); |
| QVERIFY(value.isError()); |
| QCOMPARE(value.toString(), "RangeError: Array too large for apply()."); |
| } |
| |
| void tst_QJSEngine::typedArraySet() |
| { |
| QJSEngine engine; |
| const auto value = engine.evaluate( |
| "(function() {" |
| " var length = 0xffffffe;" |
| " var offset = 0xfffffff0;" |
| " var e1;" |
| " var e2;" |
| " try {" |
| " var source1 = new Int8Array(length);" |
| " var target1 = new Int8Array(length);" |
| " target1.set(source1, offset);" |
| " } catch (intError) {" |
| " e1 = intError;" |
| " }" |
| " try {" |
| " var source2 = new Array(length);" |
| " var target2 = new Int8Array(length);" |
| " target2.set(source2, offset);" |
| " } catch (arrayError) {" |
| " e2 = arrayError;" |
| " }" |
| " return [e1, e2];" |
| "})();" |
| ); |
| |
| QVERIFY(value.isArray()); |
| for (int i = 0; i < 2; ++i) { |
| const auto error = value.property(i); |
| QVERIFY(error.isError()); |
| QCOMPARE(error.toString(), "RangeError: TypedArray.set: out of range"); |
| } |
| } |
| |
| void tst_QJSEngine::dataViewCtor() |
| { |
| QJSEngine engine; |
| const auto error = engine.evaluate(R"( |
| (function() { try { |
| var buf = new ArrayBuffer(0x200); |
| var vuln = new DataView(buf, 8, 0xfffffff8); |
| } catch (e) { |
| return e; |
| }})() |
| )"); |
| QVERIFY(error.isError()); |
| QCOMPARE(error.toString(), "RangeError: DataView: constructor arguments out of range"); |
| } |
| |
| void tst_QJSEngine::uiLanguage() |
| { |
| { |
| QJSEngine engine; |
| |
| QVERIFY(!engine.globalObject().hasProperty("Qt")); |
| |
| engine.installExtensions(QJSEngine::TranslationExtension); |
| QVERIFY(engine.globalObject().hasProperty("Qt")); |
| QVERIFY(engine.globalObject().property("Qt").hasProperty("uiLanguage")); |
| |
| engine.setUiLanguage("Blah"); |
| QCOMPARE(engine.globalObject().property("Qt").property("uiLanguage").toString(), "Blah"); |
| |
| engine.evaluate("Qt.uiLanguage = \"another\""); |
| QCOMPARE(engine.globalObject().property("Qt").property("uiLanguage").toString(), "another"); |
| } |
| |
| { |
| QQmlEngine qmlEngine; |
| QVERIFY(qmlEngine.globalObject().hasProperty("Qt")); |
| QVERIFY(qmlEngine.globalObject().property("Qt").hasProperty("uiLanguage")); |
| qmlEngine.setUiLanguage("Blah"); |
| QCOMPARE(qmlEngine.globalObject().property("Qt").property("uiLanguage").toString(), "Blah"); |
| } |
| } |
| |
| QTEST_MAIN(tst_QJSEngine) |
| |
| #include "tst_qjsengine.moc" |
| |