| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the test suite of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| |
| #include <QtTest/QtTest> |
| |
| #include <QtScript/qscriptengine.h> |
| #include <QtScript/qscriptclass.h> |
| #include <QtScript/qscriptclasspropertyiterator.h> |
| #include <QtScript/qscriptstring.h> |
| #include <QtScript/qscriptvalueiterator.h> |
| |
| Q_DECLARE_METATYPE(QScriptContext*) |
| Q_DECLARE_METATYPE(QScriptValueList) |
| Q_DECLARE_METATYPE(QScriptValue) |
| |
| class tst_QScriptClass : public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| tst_QScriptClass(); |
| virtual ~tst_QScriptClass(); |
| |
| private slots: |
| void newInstance(); |
| void setScriptClassOfExistingObject(); |
| void setScriptClassOfNonQtScriptObject(); |
| void getAndSetPropertyFromCpp(); |
| void getAndSetPropertyFromJS(); |
| void deleteUndeletableProperty(); |
| void writeReadOnlyProperty(); |
| void writePropertyWithoutWriteAccess(); |
| void getProperty_invalidValue(); |
| void enumerate(); |
| void extension_None(); |
| void extension_Callable(); |
| void extension_Callable_construct(); |
| void extension_HasInstance(); |
| void originalProperties1(); |
| void originalProperties2(); |
| void originalProperties3(); |
| void originalProperties4(); |
| void defaultImplementations(); |
| void scriptClassObjectInPrototype(); |
| void scriptClassWithNullEngine(); |
| void scriptClassInOtherEngine(); |
| }; |
| |
| tst_QScriptClass::tst_QScriptClass() |
| { |
| } |
| |
| tst_QScriptClass::~tst_QScriptClass() |
| { |
| } |
| |
| |
| |
| class TestClass : public QScriptClass |
| { |
| public: |
| struct CustomProperty { |
| QueryFlags qflags; |
| uint id; |
| QScriptValue::PropertyFlags pflags; |
| QScriptValue value; |
| |
| CustomProperty(QueryFlags qf, uint i, QScriptValue::PropertyFlags pf, |
| const QScriptValue &val) |
| : qflags(qf), id(i), pflags(pf), value(val) { } |
| }; |
| |
| enum CallableMode { |
| NotCallable, |
| CallableReturnsSum, |
| CallableReturnsArgument, |
| CallableReturnsInvalidVariant, |
| CallableReturnsGlobalObject, |
| CallableReturnsThisObject, |
| CallableReturnsCallee, |
| CallableReturnsArgumentsObject, |
| CallableInitializesThisObject |
| }; |
| |
| TestClass(QScriptEngine *engine); |
| ~TestClass(); |
| |
| void addCustomProperty(const QScriptString &name, QueryFlags qflags, |
| uint id, QScriptValue::PropertyFlags pflags, |
| const QScriptValue &value); |
| void removeCustomProperty(const QScriptString &name); |
| |
| QueryFlags queryProperty(const QScriptValue &object, |
| const QScriptString &name, |
| QueryFlags flags, uint *id); |
| |
| QScriptValue property(const QScriptValue &object, |
| const QScriptString &name, uint id); |
| |
| void setProperty(QScriptValue &object, const QScriptString &name, |
| uint id, const QScriptValue &value); |
| |
| QScriptValue::PropertyFlags propertyFlags( |
| const QScriptValue &object, const QScriptString &name, uint id); |
| |
| QScriptClassPropertyIterator *newIterator(const QScriptValue &object); |
| |
| QScriptValue prototype() const; |
| |
| QString name() const; |
| |
| bool supportsExtension(Extension extension) const; |
| QVariant extension(Extension extension, |
| const QVariant &argument = QVariant()); |
| |
| QScriptValue lastQueryPropertyObject() const; |
| QScriptString lastQueryPropertyName() const; |
| QueryFlags lastQueryPropertyFlags() const; |
| |
| QScriptValue lastPropertyObject() const; |
| QScriptString lastPropertyName() const; |
| uint lastPropertyId() const; |
| |
| QScriptValue lastSetPropertyObject() const; |
| QScriptString lastSetPropertyName() const; |
| uint lastSetPropertyId() const; |
| QScriptValue lastSetPropertyValue() const; |
| |
| QScriptValue lastPropertyFlagsObject() const; |
| QScriptString lastPropertyFlagsName() const; |
| uint lastPropertyFlagsId() const; |
| |
| QScriptClass::Extension lastExtensionType() const; |
| QVariant lastExtensionArgument() const; |
| |
| void clearReceivedArgs(); |
| |
| void setIterationEnabled(bool enable); |
| bool isIterationEnabled() const; |
| |
| void setCallableMode(CallableMode mode); |
| CallableMode callableMode() const; |
| |
| void setHasInstance(bool hasInstance); |
| bool hasInstance() const; |
| |
| private: |
| CustomProperty *findCustomProperty(const QScriptString &name); |
| |
| QHash<QScriptString, CustomProperty*> customProperties; |
| |
| QScriptValue m_lastQueryPropertyObject; |
| QScriptString m_lastQueryPropertyName; |
| QScriptClass::QueryFlags m_lastQueryPropertyFlags; |
| |
| QScriptValue m_lastPropertyObject; |
| QScriptString m_lastPropertyName; |
| uint m_lastPropertyId; |
| |
| QScriptValue m_lastSetPropertyObject; |
| QScriptString m_lastSetPropertyName; |
| uint m_lastSetPropertyId; |
| QScriptValue m_lastSetPropertyValue; |
| |
| QScriptValue m_lastPropertyFlagsObject; |
| QScriptString m_lastPropertyFlagsName; |
| uint m_lastPropertyFlagsId; |
| |
| QScriptClass::Extension m_lastExtensionType; |
| QVariant m_lastExtensionArgument; |
| |
| QScriptValue m_prototype; |
| bool m_iterationEnabled; |
| CallableMode m_callableMode; |
| bool m_hasInstance; |
| }; |
| |
| class TestClassPropertyIterator : public QScriptClassPropertyIterator |
| { |
| public: |
| TestClassPropertyIterator(const QHash<QScriptString, TestClass::CustomProperty*> &props, |
| const QScriptValue &object); |
| ~TestClassPropertyIterator(); |
| |
| bool hasNext() const; |
| void next(); |
| |
| bool hasPrevious() const; |
| void previous(); |
| |
| void toFront(); |
| void toBack(); |
| |
| QScriptString name() const; |
| uint id() const; |
| QScriptValue::PropertyFlags flags() const; |
| |
| private: |
| int m_index; |
| int m_last; |
| QHash<QScriptString, TestClass::CustomProperty*> m_props; |
| }; |
| |
| |
| |
| TestClass::TestClass(QScriptEngine *engine) |
| : QScriptClass(engine), m_iterationEnabled(true), |
| m_callableMode(NotCallable), m_hasInstance(false) |
| { |
| m_prototype = engine->newObject(); |
| clearReceivedArgs(); |
| } |
| |
| TestClass::~TestClass() |
| { |
| qDeleteAll(customProperties); |
| } |
| |
| TestClass::CustomProperty* TestClass::findCustomProperty(const QScriptString &name) |
| { |
| QHash<QScriptString, CustomProperty*>::const_iterator it; |
| it = customProperties.constFind(name); |
| if (it == customProperties.constEnd()) |
| return 0; |
| return it.value(); |
| |
| } |
| |
| void TestClass::addCustomProperty(const QScriptString &name, QueryFlags qflags, |
| uint id, QScriptValue::PropertyFlags pflags, |
| const QScriptValue &value) |
| { |
| customProperties.insert(name, new CustomProperty(qflags, id, pflags, value)); |
| } |
| |
| void TestClass::removeCustomProperty(const QScriptString &name) |
| { |
| CustomProperty *prop = customProperties.take(name); |
| if (prop) |
| delete prop; |
| } |
| |
| QScriptClass::QueryFlags TestClass::queryProperty(const QScriptValue &object, |
| const QScriptString &name, |
| QueryFlags flags, uint *id) |
| { |
| m_lastQueryPropertyObject = object; |
| m_lastQueryPropertyName = name; |
| m_lastQueryPropertyFlags = flags; |
| CustomProperty *prop = findCustomProperty(name); |
| if (!prop) |
| return {}; |
| *id = prop->id; |
| return prop->qflags & flags; |
| } |
| |
| QScriptValue TestClass::property(const QScriptValue &object, |
| const QScriptString &name, uint id) |
| { |
| m_lastPropertyObject = object; |
| m_lastPropertyName = name; |
| m_lastPropertyId = id; |
| CustomProperty *prop = findCustomProperty(name); |
| if (!prop) |
| return QScriptValue(); |
| return prop->value; |
| } |
| |
| void TestClass::setProperty(QScriptValue &object, const QScriptString &name, |
| uint id, const QScriptValue &value) |
| { |
| m_lastSetPropertyObject = object; |
| m_lastSetPropertyName = name; |
| m_lastSetPropertyId = id; |
| m_lastSetPropertyValue = value; |
| CustomProperty *prop = findCustomProperty(name); |
| if (!prop) |
| return; |
| if (prop->pflags & QScriptValue::ReadOnly) |
| return; |
| if (!value.isValid()) // deleteProperty() requested |
| removeCustomProperty(name); |
| else |
| prop->value = value; |
| } |
| |
| QScriptValue::PropertyFlags TestClass::propertyFlags( |
| const QScriptValue &object, const QScriptString &name, uint id) |
| { |
| m_lastPropertyFlagsObject = object; |
| m_lastPropertyFlagsName = name; |
| m_lastPropertyFlagsId = id; |
| CustomProperty *prop = findCustomProperty(name); |
| if (!prop) |
| return {}; |
| return prop->pflags; |
| } |
| |
| QScriptClassPropertyIterator *TestClass::newIterator(const QScriptValue &object) |
| { |
| if (!m_iterationEnabled) |
| return {}; |
| return new TestClassPropertyIterator(customProperties, object); |
| } |
| |
| QScriptValue TestClass::prototype() const |
| { |
| return m_prototype; |
| } |
| |
| QString TestClass::name() const |
| { |
| return QLatin1String("TestClass"); |
| } |
| |
| bool TestClass::supportsExtension(Extension extension) const |
| { |
| if (extension == Callable) |
| return (m_callableMode != NotCallable); |
| if (extension == HasInstance) |
| return m_hasInstance; |
| return false; |
| } |
| |
| QVariant TestClass::extension(Extension extension, |
| const QVariant &argument) |
| { |
| m_lastExtensionType = extension; |
| m_lastExtensionArgument = argument; |
| if (extension == Callable && m_callableMode != NotCallable) { |
| QScriptContext *ctx = qvariant_cast<QScriptContext*>(argument); |
| if (m_callableMode == CallableReturnsSum) { |
| qsreal sum = 0; |
| for (int i = 0; i < ctx->argumentCount(); ++i) |
| sum += ctx->argument(i).toNumber(); |
| QScriptValueIterator it(ctx->callee()); |
| while (it.hasNext()) { |
| it.next(); |
| sum += it.value().toNumber(); |
| } |
| return sum; |
| } else if (m_callableMode == CallableReturnsArgument) { |
| return QVariant::fromValue(ctx->argument(0)); |
| } else if (m_callableMode == CallableReturnsInvalidVariant) { |
| return QVariant(); |
| } else if (m_callableMode == CallableReturnsGlobalObject) { |
| return QVariant::fromValue(engine()->globalObject()); |
| } else if (m_callableMode == CallableReturnsThisObject) { |
| return QVariant::fromValue(ctx->thisObject()); |
| } else if (m_callableMode == CallableReturnsCallee) { |
| return QVariant::fromValue(ctx->callee()); |
| } else if (m_callableMode == CallableReturnsArgumentsObject) { |
| return QVariant::fromValue(ctx->argumentsObject()); |
| } else if (m_callableMode == CallableInitializesThisObject) { |
| engine()->newQObject(ctx->thisObject(), engine()); |
| return QVariant(); |
| } |
| } else if (extension == HasInstance && m_hasInstance) { |
| QScriptValueList args = qvariant_cast<QScriptValueList>(argument); |
| QScriptValue obj = args.at(0); |
| QScriptValue value = args.at(1); |
| return value.property("foo").equals(obj.property("foo")); |
| } |
| return QVariant(); |
| } |
| |
| QScriptValue TestClass::lastQueryPropertyObject() const |
| { |
| return m_lastQueryPropertyObject; |
| } |
| |
| QScriptString TestClass::lastQueryPropertyName() const |
| { |
| return m_lastQueryPropertyName; |
| } |
| |
| QScriptClass::QueryFlags TestClass::lastQueryPropertyFlags() const |
| { |
| return m_lastQueryPropertyFlags; |
| } |
| |
| QScriptValue TestClass::lastPropertyObject() const |
| { |
| return m_lastPropertyObject; |
| } |
| |
| QScriptString TestClass::lastPropertyName() const |
| { |
| return m_lastPropertyName; |
| } |
| |
| uint TestClass::lastPropertyId() const |
| { |
| return m_lastPropertyId; |
| } |
| |
| QScriptValue TestClass::lastSetPropertyObject() const |
| { |
| return m_lastSetPropertyObject; |
| } |
| |
| QScriptString TestClass::lastSetPropertyName() const |
| { |
| return m_lastSetPropertyName; |
| } |
| |
| uint TestClass::lastSetPropertyId() const |
| { |
| return m_lastSetPropertyId; |
| } |
| |
| QScriptValue TestClass::lastSetPropertyValue() const |
| { |
| return m_lastSetPropertyValue; |
| } |
| |
| QScriptValue TestClass::lastPropertyFlagsObject() const |
| { |
| return m_lastPropertyFlagsObject; |
| } |
| |
| QScriptString TestClass::lastPropertyFlagsName() const |
| { |
| return m_lastPropertyFlagsName; |
| } |
| |
| uint TestClass::lastPropertyFlagsId() const |
| { |
| return m_lastPropertyFlagsId; |
| } |
| |
| QScriptClass::Extension TestClass::lastExtensionType() const |
| { |
| return m_lastExtensionType; |
| } |
| |
| QVariant TestClass::lastExtensionArgument() const |
| { |
| return m_lastExtensionArgument; |
| } |
| |
| void TestClass::clearReceivedArgs() |
| { |
| m_lastQueryPropertyObject = QScriptValue(); |
| m_lastQueryPropertyName = QScriptString(); |
| m_lastQueryPropertyFlags = {}; |
| |
| m_lastPropertyObject = QScriptValue(); |
| m_lastPropertyName = QScriptString(); |
| m_lastPropertyId = uint(-1); |
| |
| m_lastSetPropertyObject = QScriptValue(); |
| m_lastSetPropertyName = QScriptString(); |
| m_lastSetPropertyId = uint(-1); |
| m_lastSetPropertyValue = QScriptValue(); |
| |
| m_lastPropertyFlagsObject = QScriptValue(); |
| m_lastPropertyFlagsName = QScriptString(); |
| m_lastPropertyFlagsId = uint(-1); |
| |
| m_lastExtensionType = static_cast<QScriptClass::Extension>(-1); |
| m_lastExtensionArgument = QVariant(); |
| } |
| |
| void TestClass::setIterationEnabled(bool enable) |
| { |
| m_iterationEnabled = enable; |
| } |
| |
| bool TestClass::isIterationEnabled() const |
| { |
| return m_iterationEnabled; |
| } |
| |
| void TestClass::setCallableMode(CallableMode mode) |
| { |
| m_callableMode = mode; |
| } |
| |
| TestClass::CallableMode TestClass::callableMode() const |
| { |
| return m_callableMode; |
| } |
| |
| void TestClass::setHasInstance(bool hasInstance) |
| { |
| m_hasInstance = hasInstance; |
| } |
| |
| bool TestClass::hasInstance() const |
| { |
| return m_hasInstance; |
| } |
| |
| |
| TestClassPropertyIterator::TestClassPropertyIterator(const QHash<QScriptString, TestClass::CustomProperty*> &props, |
| const QScriptValue &object) |
| : QScriptClassPropertyIterator(object) |
| { |
| m_props = props; |
| toFront(); |
| } |
| |
| TestClassPropertyIterator::~TestClassPropertyIterator() |
| { |
| } |
| |
| bool TestClassPropertyIterator::hasNext() const |
| { |
| return m_index < m_props.size(); |
| } |
| |
| void TestClassPropertyIterator::next() |
| { |
| m_last = m_index; |
| ++m_index; |
| } |
| |
| bool TestClassPropertyIterator::hasPrevious() const |
| { |
| return m_index > 0; |
| } |
| |
| void TestClassPropertyIterator::previous() |
| { |
| --m_index; |
| m_last = m_index; |
| } |
| |
| void TestClassPropertyIterator::toFront() |
| { |
| m_index = 0; |
| m_last = -1; |
| } |
| |
| void TestClassPropertyIterator::toBack() |
| { |
| m_index = m_props.size(); |
| m_last = -1; |
| } |
| |
| QScriptString TestClassPropertyIterator::name() const |
| { |
| return m_props.keys().value(m_last); |
| } |
| |
| uint TestClassPropertyIterator::id() const |
| { |
| QScriptString key = m_props.keys().value(m_last); |
| if (!key.isValid()) |
| return 0; |
| TestClass::CustomProperty *prop = m_props.value(key); |
| return prop->id; |
| } |
| |
| QScriptValue::PropertyFlags TestClassPropertyIterator::flags() const |
| { |
| QScriptString key = m_props.keys().value(m_last); |
| if (!key.isValid()) |
| return {}; |
| TestClass::CustomProperty *prop = m_props.value(key); |
| return prop->pflags; |
| } |
| |
| |
| |
| void tst_QScriptClass::newInstance() |
| { |
| QScriptEngine eng; |
| |
| TestClass cls(&eng); |
| |
| QScriptValue obj1 = eng.newObject(&cls); |
| QVERIFY(!obj1.data().isValid()); |
| QVERIFY(obj1.prototype().strictlyEquals(cls.prototype())); |
| QEXPECT_FAIL("", "QTBUG-17599: classname is not implemented", Continue); |
| QCOMPARE(obj1.toString(), QString::fromLatin1("[object TestClass]")); |
| QCOMPARE(obj1.scriptClass(), (QScriptClass*)&cls); |
| |
| QScriptValue num(&eng, 456); |
| QScriptValue obj2 = eng.newObject(&cls, num); |
| QVERIFY(obj2.data().strictlyEquals(num)); |
| QVERIFY(obj2.prototype().strictlyEquals(cls.prototype())); |
| QCOMPARE(obj2.scriptClass(), (QScriptClass*)&cls); |
| QVERIFY(!obj2.equals(obj1)); |
| QVERIFY(!obj2.strictlyEquals(obj1)); |
| } |
| |
| void tst_QScriptClass::setScriptClassOfExistingObject() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| QScriptValue obj3 = eng.newObject(); |
| QCOMPARE(obj3.scriptClass(), (QScriptClass*)0); |
| obj3.setScriptClass(&cls); |
| QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls); |
| |
| obj3.setScriptClass(0); |
| QCOMPARE(obj3.scriptClass(), (QScriptClass*)0); |
| obj3.setScriptClass(&cls); |
| QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls); |
| |
| TestClass cls2(&eng); |
| obj3.setScriptClass(&cls2); |
| QCOMPARE(obj3.scriptClass(), (QScriptClass*)&cls2); |
| } |
| |
| void tst_QScriptClass::setScriptClassOfNonQtScriptObject() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| // undefined behavior really, but shouldn't crash |
| QScriptValue arr = eng.newArray(); |
| QVERIFY(arr.isArray()); |
| QCOMPARE(arr.scriptClass(), (QScriptClass*)0); |
| QTest::ignoreMessage(QtWarningMsg, "QScriptValue::setScriptClass() failed: cannot change class of non-QScriptObject"); |
| arr.setScriptClass(&cls); |
| QEXPECT_FAIL("", "Changing class of arbitrary script object is not allowed (it's OK)", Continue); |
| QCOMPARE(arr.scriptClass(), (QScriptClass*)&cls); |
| QEXPECT_FAIL("", "Changing class of arbitrary script object is not allowed (it's OK)", Continue); |
| QVERIFY(!arr.isArray()); |
| QVERIFY(arr.isObject()); |
| } |
| |
| void tst_QScriptClass::getAndSetPropertyFromCpp() |
| { |
| QScriptEngine eng; |
| |
| TestClass cls(&eng); |
| |
| QScriptValue obj1 = eng.newObject(&cls); |
| QScriptValue obj2 = eng.newObject(&cls); |
| QScriptString foo = eng.toStringHandle("foo"); |
| QScriptString bar = eng.toStringHandle("bar"); |
| QScriptValue num(&eng, 123); |
| |
| // Initially our TestClass instances have no custom properties, |
| // and queryProperty() will always return false. |
| // Hence, the properties will be created as normal JS properties. |
| for (int x = 0; x < 2; ++x) { |
| QScriptValue &o = (x == 0) ? obj1 : obj2; |
| for (int y = 0; y < 2; ++y) { |
| QScriptString &s = (y == 0) ? foo : bar; |
| |
| // read property |
| cls.clearReceivedArgs(); |
| QScriptValue ret = o.property(s); |
| QVERIFY(!ret.isValid()); |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(o)); |
| QVERIFY(cls.lastQueryPropertyName() == s); |
| QVERIFY(!cls.lastPropertyObject().isValid()); |
| QVERIFY(!cls.lastSetPropertyObject().isValid()); |
| QVERIFY(cls.lastQueryPropertyFlags() == QScriptClass::HandlesReadAccess); |
| |
| // write property |
| cls.clearReceivedArgs(); |
| o.setProperty(s, num); |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(o)); |
| QVERIFY(cls.lastQueryPropertyName() == s); |
| QVERIFY(!cls.lastPropertyObject().isValid()); |
| QVERIFY(!cls.lastSetPropertyObject().isValid()); |
| QVERIFY(cls.lastQueryPropertyFlags() == QScriptClass::HandlesWriteAccess); |
| |
| // re-read property |
| // When a QScriptClass doesn't want to handle a property write, |
| // that property becomes a normal property and the QScriptClass |
| // shall not be queried about it again. |
| cls.clearReceivedArgs(); |
| QVERIFY(o.property(s).strictlyEquals(num)); |
| QVERIFY(!cls.lastQueryPropertyObject().isValid()); |
| } |
| } |
| |
| // add a custom property |
| QScriptString foo2 = eng.toStringHandle("foo2"); |
| const uint foo2Id = 123; |
| const QScriptValue::PropertyFlags foo2Pflags = QScriptValue::Undeletable; |
| QScriptValue foo2Value(&eng, 456); |
| cls.addCustomProperty(foo2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, |
| foo2Id, foo2Pflags, foo2Value); |
| |
| { |
| // read property |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = obj1.property(foo2); |
| QVERIFY(ret.strictlyEquals(foo2Value)); |
| } |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastQueryPropertyName() == foo2); |
| QVERIFY(cls.lastPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastPropertyName() == foo2); |
| QCOMPARE(cls.lastPropertyId(), foo2Id); |
| |
| // read flags |
| cls.clearReceivedArgs(); |
| QCOMPARE(obj1.propertyFlags(foo2), foo2Pflags); |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastQueryPropertyName() == foo2); |
| QEXPECT_FAIL("", "QTBUG-17601: classObject.getOwnPropertyDescriptor() reads the property value", Continue); |
| QVERIFY(!cls.lastPropertyObject().isValid()); |
| QVERIFY(cls.lastPropertyFlagsObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastPropertyFlagsName() == foo2); |
| QCOMPARE(cls.lastPropertyFlagsId(), foo2Id); |
| |
| // write property |
| cls.clearReceivedArgs(); |
| QScriptValue newFoo2Value(&eng, 789); |
| obj1.setProperty(foo2, newFoo2Value); |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastQueryPropertyName() == foo2); |
| |
| // read property again |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = obj1.property(foo2); |
| QVERIFY(ret.strictlyEquals(newFoo2Value)); |
| } |
| QVERIFY(cls.lastQueryPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastQueryPropertyName() == foo2); |
| QVERIFY(cls.lastPropertyObject().strictlyEquals(obj1)); |
| QVERIFY(cls.lastPropertyName() == foo2); |
| QCOMPARE(cls.lastPropertyId(), foo2Id); |
| } |
| |
| // attempt to delete custom property |
| obj1.setProperty(foo2, QScriptValue()); |
| // delete real property |
| obj1.setProperty(foo, QScriptValue()); |
| QVERIFY(!obj1.property(foo).isValid()); |
| obj1.setProperty(foo, num); |
| QVERIFY(obj1.property(foo).equals(num)); |
| |
| // remove script class; normal properties should remain |
| obj1.setScriptClass(0); |
| QCOMPARE(obj1.scriptClass(), (QScriptClass*)0); |
| QVERIFY(obj1.property(foo).equals(num)); |
| QVERIFY(obj1.property(bar).equals(num)); |
| obj1.setProperty(foo, QScriptValue()); |
| QVERIFY(!obj1.property(foo).isValid()); |
| obj1.setProperty(bar, QScriptValue()); |
| QVERIFY(!obj1.property(bar).isValid()); |
| } |
| |
| void tst_QScriptClass::getAndSetPropertyFromJS() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.addCustomProperty(eng.toStringHandle("x"), |
| QScriptClass::HandlesReadAccess |
| | QScriptClass::HandlesWriteAccess, |
| /*id=*/ 1, QScriptValue::PropertyFlags{}, /*value=*/ 123); |
| eng.globalObject().setProperty("o", eng.newObject(&cls)); |
| |
| // Accessing a custom property |
| QCOMPARE(eng.evaluate("o.x").toInt32(), 123); |
| QCOMPARE(eng.evaluate("o.x = 456; o.x").toInt32(), 456); |
| |
| // Accessing a new JS property |
| QVERIFY(eng.evaluate("o.y").isUndefined()); |
| QCOMPARE(eng.evaluate("o.y = 789; o.y").toInt32(), 789); |
| |
| // Deleting custom property |
| QVERIFY(eng.evaluate("delete o.x").toBool()); |
| QVERIFY(eng.evaluate("o.x").isUndefined()); |
| |
| // Deleting JS property |
| QVERIFY(eng.evaluate("delete o.y").toBool()); |
| QVERIFY(eng.evaluate("o.y").isUndefined()); |
| } |
| |
| void tst_QScriptClass::deleteUndeletableProperty() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.addCustomProperty(eng.toStringHandle("x"), QScriptClass::HandlesWriteAccess, |
| /*id=*/0, QScriptValue::Undeletable, QScriptValue()); |
| eng.globalObject().setProperty("o", eng.newObject(&cls)); |
| QVERIFY(!eng.evaluate("delete o.x").toBool()); |
| } |
| |
| void tst_QScriptClass::writeReadOnlyProperty() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.addCustomProperty(eng.toStringHandle("x"), |
| QScriptClass::HandlesReadAccess |
| | QScriptClass::HandlesWriteAccess, |
| /*id=*/0, QScriptValue::ReadOnly, 123); |
| eng.globalObject().setProperty("o", eng.newObject(&cls)); |
| // Note that if a property is read-only, the setProperty() |
| // reimplementation will still get called; it's up to that |
| // function to respect the ReadOnly flag. |
| QCOMPARE(eng.evaluate("o.x = 456; o.x").toInt32(), 123); |
| } |
| |
| void tst_QScriptClass::writePropertyWithoutWriteAccess() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.addCustomProperty(eng.toStringHandle("x"), |
| QScriptClass::HandlesReadAccess, |
| /*id=*/ 0, QScriptValue::PropertyFlags{}, 123); |
| eng.globalObject().setProperty("o", eng.newObject(&cls)); |
| QCOMPARE(eng.evaluate("o.x").toInt32(), 123); |
| |
| // This will create a JS property on the instance that |
| // shadows the custom property. |
| // This behavior is not documented. It might be more |
| // intuitive to treat a property that only handles read |
| // access as a read-only, non-shadowable property. |
| QCOMPARE(eng.evaluate("o.x = 456; o.x").toInt32(), 456); |
| |
| QVERIFY(eng.evaluate("delete o.x").toBool()); |
| // Now the custom property is seen again. |
| QCOMPARE(eng.evaluate("o.x").toInt32(), 123); |
| } |
| |
| void tst_QScriptClass::getProperty_invalidValue() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.addCustomProperty(eng.toStringHandle("foo"), QScriptClass::HandlesReadAccess, |
| /*id=*/0, QScriptValue::ReadOnly, QScriptValue()); |
| QScriptValue obj = eng.newObject(&cls); |
| |
| QVERIFY(obj.property("foo").isUndefined()); |
| |
| eng.globalObject().setProperty("obj", obj); |
| QVERIFY(eng.evaluate("obj.hasOwnProperty('foo'))").toBool()); |
| // The JS environment expects that a valid value is returned, |
| // otherwise we could crash. |
| QVERIFY(eng.evaluate("obj.foo").isUndefined()); |
| QVERIFY(eng.evaluate("obj.foo + ''").isString()); |
| QVERIFY(eng.evaluate("Object.getOwnPropertyDescriptor(obj, 'foo').value").isUndefined()); |
| QVERIFY(eng.evaluate("Object.getOwnPropertyDescriptor(obj, 'foo').value +''").isString()); |
| } |
| |
| void tst_QScriptClass::enumerate() |
| { |
| QScriptEngine eng; |
| |
| TestClass cls(&eng); |
| |
| QScriptValue obj = eng.newObject(&cls); |
| QScriptString foo = eng.toStringHandle("foo"); |
| obj.setProperty(foo, QScriptValue(&eng, 123)); |
| |
| cls.setIterationEnabled(false); |
| { |
| QScriptValueIterator it(obj); |
| QVERIFY(it.hasNext()); |
| it.next(); |
| QVERIFY(it.scriptName() == foo); |
| QVERIFY(!it.hasNext()); |
| } |
| |
| // add a custom property |
| QScriptString foo2 = eng.toStringHandle("foo2"); |
| const uint foo2Id = 123; |
| const QScriptValue::PropertyFlags foo2Pflags = QScriptValue::Undeletable; |
| QScriptValue foo2Value(&eng, 456); |
| cls.addCustomProperty(foo2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, |
| foo2Id, foo2Pflags, QScriptValue()); |
| |
| cls.setIterationEnabled(true); |
| QScriptValueIterator it(obj); |
| // This test relies on the order in which properties are enumerated, |
| // which we don't guarantee. However, for compatibility's sake we prefer |
| // that normal JS properties come before QScriptClass properties. |
| for (int x = 0; x < 2; ++x) { |
| QVERIFY(it.hasNext()); |
| it.next(); |
| QVERIFY(it.scriptName() == foo); |
| QVERIFY(it.hasNext()); |
| it.next(); |
| QVERIFY(it.scriptName() == foo2); |
| QCOMPARE(it.flags(), foo2Pflags); |
| QVERIFY(!it.hasNext()); |
| |
| QVERIFY(it.hasPrevious()); |
| it.previous(); |
| QVERIFY(it.scriptName() == foo2); |
| QCOMPARE(it.flags(), foo2Pflags); |
| QVERIFY(it.hasPrevious()); |
| it.previous(); |
| QVERIFY(it.scriptName() == foo); |
| QVERIFY(!it.hasPrevious()); |
| } |
| } |
| |
| void tst_QScriptClass::extension_None() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.setCallableMode(TestClass::NotCallable); |
| QVERIFY(!cls.supportsExtension(QScriptClass::Callable)); |
| QVERIFY(!cls.supportsExtension(QScriptClass::HasInstance)); |
| QScriptValue obj = eng.newObject(&cls); |
| QVERIFY(!obj.call().isValid()); |
| QCOMPARE((int)cls.lastExtensionType(), -1); |
| QVERIFY(!obj.instanceOf(obj)); |
| QCOMPARE((int)cls.lastExtensionType(), -1); |
| QVERIFY(!obj.construct().isValid()); |
| } |
| |
| void tst_QScriptClass::extension_Callable() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.setCallableMode(TestClass::CallableReturnsSum); |
| QVERIFY(cls.supportsExtension(QScriptClass::Callable)); |
| |
| QScriptValue obj = eng.newObject(&cls); |
| eng.globalObject().setProperty("obj", obj); |
| obj.setProperty("one", QScriptValue(&eng, 1)); |
| obj.setProperty("two", QScriptValue(&eng, 2)); |
| obj.setProperty("three", QScriptValue(&eng, 3)); |
| // From C++ |
| cls.clearReceivedArgs(); |
| { |
| QScriptValueList args; |
| args << QScriptValue(&eng, 4) << QScriptValue(&eng, 5); |
| QScriptValue ret = obj.call(obj, args); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qsreal(1+2+3+4+5)); |
| } |
| // From JS |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = eng.evaluate("obj(4, 5)"); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toNumber(), qsreal(1+2+3+4+5)); |
| } |
| |
| cls.setCallableMode(TestClass::CallableReturnsArgument); |
| // From C++ |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = obj.call(obj, QScriptValueList() << 123); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt32(), 123); |
| } |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = obj.call(obj, QScriptValueList() << true); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isBoolean()); |
| QCOMPARE(ret.toBoolean(), true); |
| } |
| { |
| QScriptValue ret = obj.call(obj, QScriptValueList() << QString::fromLatin1("ciao")); |
| QVERIFY(ret.isString()); |
| QCOMPARE(ret.toString(), QString::fromLatin1("ciao")); |
| } |
| { |
| QScriptValue objobj = eng.newObject(); |
| QScriptValue ret = obj.call(obj, QScriptValueList() << objobj); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(objobj)); |
| } |
| { |
| QScriptValue ret = obj.call(obj, QScriptValueList() << QScriptValue()); |
| QVERIFY(ret.isUndefined()); |
| } |
| // From JS |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = eng.evaluate("obj(123)"); |
| QVERIFY(ret.isNumber()); |
| QCOMPARE(ret.toInt32(), 123); |
| } |
| |
| cls.setCallableMode(TestClass::CallableReturnsInvalidVariant); |
| { |
| QScriptValue ret = obj.call(obj); |
| QVERIFY(ret.isUndefined()); |
| } |
| |
| cls.setCallableMode(TestClass::CallableReturnsThisObject); |
| // From C++ |
| { |
| QScriptValue ret = obj.call(obj); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(obj)); |
| } |
| // From JS |
| { |
| QScriptValue ret = eng.evaluate("obj()"); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(eng.globalObject())); |
| } |
| |
| cls.setCallableMode(TestClass::CallableReturnsCallee); |
| // From C++ |
| { |
| QScriptValue ret = obj.call(); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(obj)); |
| } |
| // From JS |
| { |
| QScriptValue ret = eng.evaluate("obj()"); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(obj)); |
| } |
| |
| cls.setCallableMode(TestClass::CallableReturnsArgumentsObject); |
| // From C++ |
| { |
| QScriptValue ret = obj.call(obj, QScriptValueList() << 123); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.property("length").isNumber()); |
| QCOMPARE(ret.property("length").toInt32(), 1); |
| QVERIFY(ret.property(0).isNumber()); |
| QCOMPARE(ret.property(0).toInt32(), 123); |
| } |
| // From JS |
| { |
| QScriptValue ret = eng.evaluate("obj(123)"); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.property("length").isNumber()); |
| QCOMPARE(ret.property("length").toInt32(), 1); |
| QVERIFY(ret.property(0).isNumber()); |
| QCOMPARE(ret.property(0).toInt32(), 123); |
| } |
| } |
| |
| void tst_QScriptClass::extension_Callable_construct() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| QScriptValue obj = eng.newObject(&cls); |
| eng.globalObject().setProperty("obj", obj); |
| |
| // From C++ |
| cls.clearReceivedArgs(); |
| cls.setCallableMode(TestClass::CallableReturnsGlobalObject); |
| { |
| QScriptValue ret = obj.construct(); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(eng.globalObject())); |
| } |
| // From JS |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = eng.evaluate("new obj()"); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isObject()); |
| QVERIFY(ret.strictlyEquals(eng.globalObject())); |
| } |
| // From C++ |
| cls.clearReceivedArgs(); |
| cls.setCallableMode(TestClass::CallableInitializesThisObject); |
| { |
| QScriptValue ret = obj.construct(); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isQObject()); |
| QCOMPARE(ret.toQObject(), (QObject*)&eng); |
| } |
| // From JS |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = eng.evaluate("new obj()"); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::Callable); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptContext*>()); |
| QVERIFY(ret.isQObject()); |
| QCOMPARE(ret.toQObject(), (QObject*)&eng); |
| } |
| } |
| |
| void tst_QScriptClass::extension_HasInstance() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| cls.setHasInstance(true); |
| QVERIFY(cls.supportsExtension(QScriptClass::HasInstance)); |
| |
| QScriptValue obj = eng.newObject(&cls); |
| obj.setProperty("foo", QScriptValue(&eng, 123)); |
| QScriptValue plain = eng.newObject(); |
| QVERIFY(!plain.instanceOf(obj)); |
| |
| eng.globalObject().setProperty("HasInstanceTester", obj); |
| eng.globalObject().setProperty("hasInstanceValue", plain); |
| cls.clearReceivedArgs(); |
| { |
| QScriptValue ret = eng.evaluate("hasInstanceValue instanceof HasInstanceTester"); |
| QCOMPARE(cls.lastExtensionType(), QScriptClass::HasInstance); |
| QCOMPARE(cls.lastExtensionArgument().userType(), qMetaTypeId<QScriptValueList>()); |
| QScriptValueList lst = qvariant_cast<QScriptValueList>(cls.lastExtensionArgument()); |
| QCOMPARE(lst.size(), 2); |
| QVERIFY(lst.at(0).strictlyEquals(obj)); |
| QVERIFY(lst.at(1).strictlyEquals(plain)); |
| QVERIFY(ret.isBoolean()); |
| QVERIFY(!ret.toBoolean()); |
| } |
| |
| plain.setProperty("foo", QScriptValue(&eng, 456)); |
| QVERIFY(!plain.instanceOf(obj)); |
| { |
| QScriptValue ret = eng.evaluate("hasInstanceValue instanceof HasInstanceTester"); |
| QVERIFY(ret.isBoolean()); |
| QVERIFY(!ret.toBoolean()); |
| } |
| |
| plain.setProperty("foo", obj.property("foo")); |
| QVERIFY(plain.instanceOf(obj)); |
| { |
| QScriptValue ret = eng.evaluate("hasInstanceValue instanceof HasInstanceTester"); |
| QVERIFY(ret.isBoolean()); |
| QVERIFY(ret.toBoolean()); |
| } |
| } |
| |
| // tests made to match Qt 4.7 (JSC) behaviour |
| void tst_QScriptClass::originalProperties1() |
| { |
| QScriptEngine eng; |
| |
| QScriptString orig1 = eng.toStringHandle("orig1"); |
| QScriptString orig2 = eng.toStringHandle("orig2"); |
| QScriptString orig3 = eng.toStringHandle("orig3"); |
| QScriptString new1 = eng.toStringHandle("new1"); |
| QScriptString new2 = eng.toStringHandle("new2"); |
| |
| { |
| TestClass cls1(&eng); |
| cls1.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 89); |
| cls1.addCustomProperty(new1, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "hello"); |
| |
| TestClass cls2(&eng); |
| cls2.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 59); |
| cls2.addCustomProperty(new2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "world"); |
| |
| QScriptValue obj1 = eng.newObject(); |
| obj1.setProperty(orig1 , 42); |
| obj1.setProperty(orig2 , "foo"); |
| obj1.prototype().setProperty(orig3, "bar"); |
| |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QVERIFY(!obj1.property(new2).isValid()); |
| |
| eng.globalObject().setProperty("obj" , obj1); |
| |
| obj1.setScriptClass(&cls1); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar")); |
| QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello")); |
| QVERIFY(!obj1.property(new2).isValid()); |
| |
| QScriptValue obj2 = eng.evaluate("obj"); |
| QCOMPARE(obj2.scriptClass(), &cls1); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QCOMPARE(obj2.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj2.property(orig3).toString(), QString::fromLatin1("bar")); |
| QCOMPARE(obj2.property(new1).toString(), QString::fromLatin1("hello")); |
| QVERIFY(!obj2.property(new2).isValid()); |
| |
| obj1.setScriptClass(&cls2); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world")); |
| |
| QCOMPARE(obj2.scriptClass(), &cls2); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QCOMPARE(obj2.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj2.property(orig3).toString(), QString::fromLatin1("bar")); |
| QVERIFY(!obj2.property(new1).isValid()); |
| QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world")); |
| |
| obj1.setScriptClass(0); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QVERIFY(!obj1.property(new2).isValid()); |
| } |
| } |
| |
| void tst_QScriptClass::originalProperties2() |
| { |
| QScriptEngine eng; |
| |
| QScriptString orig1 = eng.toStringHandle("orig1"); |
| QScriptString orig2 = eng.toStringHandle("orig2"); |
| QScriptString orig3 = eng.toStringHandle("orig3"); |
| QScriptString new1 = eng.toStringHandle("new1"); |
| QScriptString new2 = eng.toStringHandle("new2"); |
| |
| { |
| TestClass cls1(&eng); |
| cls1.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 89); |
| cls1.addCustomProperty(new1, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "hello"); |
| |
| TestClass cls2(&eng); |
| cls2.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 59); |
| cls2.addCustomProperty(new2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "world"); |
| |
| QScriptValue obj1 = eng.newObject(); |
| obj1.setProperty(orig1 , 42); |
| obj1.setProperty(orig2 , "foo"); |
| obj1.prototype().setProperty(orig3, "bar"); |
| |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("bar")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QVERIFY(!obj1.property(new2).isValid()); |
| |
| obj1.setScriptClass(&cls1); |
| obj1.setProperty(orig1 , QScriptValue(&eng, 852)); |
| obj1.setProperty(orig2 , "oli"); |
| obj1.setProperty(orig3 , "fu*c"); |
| obj1.setProperty(new1 , "moo"); |
| obj1.setProperty(new2 , "allo?"); |
| QCOMPARE(obj1.property(orig1).toInt32(), 852); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c")); |
| QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("moo")); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?")); |
| |
| obj1.setScriptClass(&cls2); |
| QCOMPARE(obj1.property(orig1).toInt32(), 852); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?")); |
| |
| obj1.setScriptClass(0); |
| QCOMPARE(obj1.property(orig1).toInt32(), 852); |
| QCOMPARE(obj1.property(orig2).toString(), QString::fromLatin1("foo")); |
| QCOMPARE(obj1.property(orig3).toString(), QString::fromLatin1("fu*c")); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("allo?")); |
| } |
| } |
| |
| void tst_QScriptClass::originalProperties3() |
| { |
| QScriptEngine eng; |
| |
| QScriptString orig1 = eng.toStringHandle("orig1"); |
| QScriptString orig2 = eng.toStringHandle("orig2"); |
| QScriptString orig3 = eng.toStringHandle("orig3"); |
| QScriptString new1 = eng.toStringHandle("new1"); |
| QScriptString new2 = eng.toStringHandle("new2"); |
| |
| { |
| TestClass cls1(&eng); |
| cls1.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 89); |
| cls1.addCustomProperty(new1, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "hello"); |
| |
| TestClass cls2(&eng); |
| cls2.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 59); |
| cls2.addCustomProperty(new2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "world"); |
| |
| QScriptValue obj1 = eng.newObject(&cls1); |
| QVERIFY(!obj1.property(orig1).isValid()); |
| QCOMPARE(obj1.property(orig2).toInt32(), 89); |
| QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello")); |
| QVERIFY(!obj1.property(new2).isValid()); |
| obj1.setProperty(orig1, 42); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| |
| eng.globalObject().setProperty("obj" , obj1); |
| obj1.setScriptClass(&cls2); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toInt32(), 59); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world")); |
| |
| QScriptValue obj2 = eng.evaluate("obj"); |
| QCOMPARE(obj2.scriptClass(), &cls2); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QCOMPARE(obj2.property(orig2).toInt32(), 59); |
| QVERIFY(!obj2.property(new1).isValid()); |
| QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world")); |
| |
| obj1.setScriptClass(0); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QVERIFY(!obj1.property(orig2).isValid()); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QVERIFY(!obj1.property(new2).isValid()); |
| |
| QCOMPARE(obj2.scriptClass(), (QScriptClass *)0); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QVERIFY(!obj2.property(orig2).isValid()); |
| QVERIFY(!obj2.property(new1).isValid()); |
| QVERIFY(!obj2.property(new2).isValid()); |
| } |
| } |
| |
| void tst_QScriptClass::originalProperties4() |
| { |
| QScriptEngine eng; |
| |
| QScriptString orig1 = eng.toStringHandle("orig1"); |
| QScriptString orig2 = eng.toStringHandle("orig2"); |
| QScriptString orig3 = eng.toStringHandle("orig3"); |
| QScriptString new1 = eng.toStringHandle("new1"); |
| QScriptString new2 = eng.toStringHandle("new2"); |
| |
| { |
| TestClass cls1(&eng); |
| cls1.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 89); |
| cls1.addCustomProperty(new1, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "hello"); |
| |
| TestClass cls2(&eng); |
| cls2.addCustomProperty(orig2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, 59); |
| cls2.addCustomProperty(new2, QScriptClass::HandlesReadAccess | QScriptClass::HandlesWriteAccess, 1, {}, "world"); |
| |
| QScriptValue obj1 = eng.newObject(&cls1); |
| QVERIFY(!obj1.property(orig1).isValid()); |
| QCOMPARE(obj1.property(orig2).toInt32(), 89); |
| QCOMPARE(obj1.property(new1).toString(), QString::fromLatin1("hello")); |
| QVERIFY(!obj1.property(new2).isValid()); |
| |
| eng.globalObject().setProperty("obj" , obj1); |
| |
| obj1.setScriptClass(0); |
| QVERIFY(obj1.isObject()); |
| QVERIFY(!obj1.property(orig1).isValid()); |
| QVERIFY(!obj1.property(orig2).isValid()); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QVERIFY(!obj1.property(new2).isValid()); |
| obj1.setProperty(orig1, 42); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| |
| QScriptValue obj2 = eng.evaluate("obj"); |
| QCOMPARE(obj2.scriptClass(), (QScriptClass *)0); |
| QVERIFY(obj2.isObject()); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QVERIFY(!obj2.property(orig2).isValid()); |
| QVERIFY(!obj2.property(new1).isValid()); |
| QVERIFY(!obj2.property(new2).isValid()); |
| |
| obj1.setScriptClass(&cls2); |
| QCOMPARE(obj1.property(orig1).toInt32(), 42); |
| QCOMPARE(obj1.property(orig2).toInt32(), 59); |
| QVERIFY(!obj1.property(new1).isValid()); |
| QCOMPARE(obj1.property(new2).toString(), QString::fromLatin1("world")); |
| |
| QCOMPARE(obj2.scriptClass(), (QScriptClass *)(&cls2)); |
| QCOMPARE(obj2.property(orig1).toInt32(), 42); |
| QCOMPARE(obj2.property(orig2).toInt32(), 59); |
| QVERIFY(!obj2.property(new1).isValid()); |
| QCOMPARE(obj2.property(new2).toString(), QString::fromLatin1("world")); |
| } |
| } |
| |
| void tst_QScriptClass::defaultImplementations() |
| { |
| QScriptEngine eng; |
| |
| QScriptClass defaultClass(&eng); |
| QCOMPARE(defaultClass.engine(), &eng); |
| QVERIFY(!defaultClass.prototype().isValid()); |
| QCOMPARE(defaultClass.name(), QString()); |
| |
| QScriptValue obj = eng.newObject(&defaultClass); |
| QCOMPARE(obj.scriptClass(), &defaultClass); |
| |
| QScriptString name = eng.toStringHandle("foo"); |
| uint id = -1; |
| QCOMPARE(defaultClass.queryProperty(obj, name, QScriptClass::HandlesReadAccess, &id), QScriptClass::QueryFlags{}); |
| QVERIFY(!defaultClass.property(obj, name, id).isValid()); |
| QCOMPARE(defaultClass.propertyFlags(obj, name, id), QScriptValue::PropertyFlags{}); |
| defaultClass.setProperty(obj, name, id, 123); |
| QVERIFY(!obj.property(name).isValid()); |
| |
| QCOMPARE(defaultClass.newIterator(obj), (QScriptClassPropertyIterator*)0); |
| |
| QVERIFY(!defaultClass.supportsExtension(QScriptClass::Callable)); |
| QVERIFY(!defaultClass.supportsExtension(QScriptClass::HasInstance)); |
| QVERIFY(!defaultClass.extension(QScriptClass::Callable).isValid()); |
| QVERIFY(!defaultClass.extension(QScriptClass::HasInstance).isValid()); |
| } |
| |
| void tst_QScriptClass::scriptClassObjectInPrototype() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| QScriptValue plainObject = eng.newObject(); |
| QScriptValue classObject = eng.newObject(&cls); |
| plainObject.setPrototype(classObject); |
| QVERIFY(plainObject.prototype().equals(classObject)); |
| eng.globalObject().setProperty("plainObject", plainObject); |
| eng.globalObject().setProperty("classObject", classObject); |
| |
| QScriptString name = eng.toStringHandle("x"); |
| cls.addCustomProperty(name, QScriptClass::HandlesReadAccess, /*id=*/ 1, |
| QScriptValue::PropertyFlags{}, /*value=*/ 123); |
| QVERIFY(plainObject.property(name).equals(classObject.property(name))); |
| QVERIFY(eng.evaluate("plainObject.x == classObject.x").toBool()); |
| |
| // Add a property that shadows the one in the script class. |
| plainObject.setProperty(name, 456); |
| QVERIFY(!plainObject.property(name).equals(classObject.property(name))); |
| QVERIFY(eng.evaluate("plainObject.x != classObject.x").toBool()); |
| |
| QVERIFY(eng.evaluate("delete plainObject.x").toBool()); |
| QVERIFY(eng.evaluate("plainObject.x == classObject.x").toBool()); |
| } |
| |
| void tst_QScriptClass::scriptClassWithNullEngine() |
| { |
| QScriptClass cls(0); |
| QCOMPARE(cls.engine(), (QScriptEngine*)0); |
| QScriptEngine eng; |
| QScriptValue obj = eng.newObject(&cls); |
| QVERIFY(obj.isObject()); |
| QCOMPARE(obj.scriptClass(), &cls); |
| // The class could have been "bound" to the engine at this point, |
| // but it's currently not. |
| // This behavior is not documented and is subject to change. |
| QCOMPARE(cls.engine(), (QScriptEngine*)0); |
| // The engine pointer stored in the QScriptClass is not actually used |
| // during property access, so this still works. |
| obj.setProperty("x", 123); |
| QVERIFY(obj.property("x").isNumber()); |
| } |
| |
| void tst_QScriptClass::scriptClassInOtherEngine() |
| { |
| QScriptEngine eng; |
| TestClass cls(&eng); |
| QScriptEngine eng2; |
| // We don't check that the class is associated with another engine, so |
| // we only get a warning when trying to set the prototype of the new |
| // instance. |
| // This behavior is not documented and is subject to change. |
| QTest::ignoreMessage(QtWarningMsg, "QScriptValue::setPrototype() failed: cannot set a prototype created in a different engine"); |
| QScriptValue obj = eng2.newObject(&cls); |
| QVERIFY(obj.isObject()); |
| QCOMPARE(obj.scriptClass(), &cls); |
| |
| obj.setProperty("x", 123); |
| QVERIFY(obj.property("x").isNumber()); |
| } |
| |
| QTEST_MAIN(tst_QScriptClass) |
| #include "tst_qscriptclass.moc" |