tests/auto/qscriptengine/tst_qscriptengine.cpp
changeset 33 3e2da88830cd
parent 30 5dc02b23752f
child 37 758a864f9613
--- a/tests/auto/qscriptengine/tst_qscriptengine.cpp	Tue Jul 06 15:10:48 2010 +0300
+++ b/tests/auto/qscriptengine/tst_qscriptengine.cpp	Wed Aug 18 10:37:55 2010 +0300
@@ -51,6 +51,8 @@
 #include <QtCore/qnumeric.h>
 #include <stdlib.h>
 
+#include <QtScript/private/qscriptdeclarativeclass_p.h>
+
 Q_DECLARE_METATYPE(QList<int>)
 Q_DECLARE_METATYPE(QObjectList)
 Q_DECLARE_METATYPE(QScriptProgram)
@@ -169,6 +171,8 @@
     void qRegExpInport_data();
     void qRegExpInport();
     void reentrency();
+    void newFixedStaticScopeObject();
+    void newGrowingStaticScopeObject();
 };
 
 tst_QScriptEngine::tst_QScriptEngine()
@@ -4261,6 +4265,35 @@
         QScriptEngine eng;
         QCOMPARE(eng.evaluate("Array()").toString(), QString());
     }
+
+    {
+        QScriptEngine eng1;
+        QScriptEngine eng2;
+        {
+            QScriptValue d1 = eng1.newDate(0);
+            QScriptValue d2 = eng2.newDate(0);
+            QCOMPARE(d1.toDateTime(), d2.toDateTime());
+            QCOMPARE(d2.toDateTime(), d1.toDateTime());
+        }
+        {
+            QScriptValue r1 = eng1.newRegExp("foo", "gim");
+            QScriptValue r2 = eng2.newRegExp("foo", "gim");
+            QCOMPARE(r1.toRegExp(), r2.toRegExp());
+            QCOMPARE(r2.toRegExp(), r1.toRegExp());
+        }
+        {
+            QScriptValue o1 = eng1.newQObject(this);
+            QScriptValue o2 = eng2.newQObject(this);
+            QCOMPARE(o1.toQObject(), o2.toQObject());
+            QCOMPARE(o2.toQObject(), o1.toQObject());
+        }
+        {
+            QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject);
+            QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject);
+            QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject());
+            QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject());
+        }
+    }
 }
 
 void tst_QScriptEngine:: incDecNonObjectProperty()
@@ -4955,5 +4988,243 @@
     QCOMPARE(eng.evaluate("foo() + hello").toInt32(), 5+6+9);
 }
 
+void tst_QScriptEngine::newFixedStaticScopeObject()
+{
+    QScriptEngine eng;
+    static const int propertyCount = 4;
+    QString names[] = { "foo", "bar", "baz", "Math" };
+    QScriptValue values[] = { 123, "ciao", true, false };
+    QScriptValue::PropertyFlags flags[] = { QScriptValue::Undeletable,
+                                            QScriptValue::ReadOnly | QScriptValue::Undeletable,
+                                            QScriptValue::SkipInEnumeration | QScriptValue::Undeletable,
+                                            QScriptValue::Undeletable };
+    QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount, names, values, flags);
+
+    // Query property.
+    for (int i = 0; i < propertyCount; ++i) {
+        for (int x = 0; x < 2; ++x) {
+            if (x) {
+                // Properties can't be deleted.
+                scope.setProperty(names[i], QScriptValue());
+            }
+            QVERIFY(scope.property(names[i]).equals(values[i]));
+            QCOMPARE(scope.propertyFlags(names[i]), flags[i]);
+        }
+    }
+
+    // Property that doesn't exist.
+    QVERIFY(!scope.property("noSuchProperty").isValid());
+    QCOMPARE(scope.propertyFlags("noSuchProperty"), QScriptValue::PropertyFlags());
+
+    // Write to writable property.
+    {
+        QScriptValue oldValue = scope.property("foo");
+        QVERIFY(oldValue.isNumber());
+        QScriptValue newValue = oldValue.toNumber() * 2;
+        scope.setProperty("foo", newValue);
+        QVERIFY(scope.property("foo").equals(newValue));
+        scope.setProperty("foo", oldValue);
+        QVERIFY(scope.property("foo").equals(oldValue));
+    }
+
+    // Write to read-only property.
+    scope.setProperty("bar", 456);
+    QVERIFY(scope.property("bar").equals("ciao"));
+
+    // Iterate.
+    {
+        QScriptValueIterator it(scope);
+        QSet<QString> iteratedNames;
+        while (it.hasNext()) {
+            it.next();
+            iteratedNames.insert(it.name());
+        }
+        for (int i = 0; i < propertyCount; ++i)
+            QVERIFY(iteratedNames.contains(names[i]));
+    }
+
+    // Push it on the scope chain of a new context.
+    QScriptContext *ctx = eng.pushContext();
+    ctx->pushScope(scope);
+    QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
+    QVERIFY(ctx->activationObject().equals(scope));
+
+    // Read property from JS.
+    for (int i = 0; i < propertyCount; ++i) {
+        for (int x = 0; x < 2; ++x) {
+            if (x) {
+                // Property can't be deleted from JS.
+                QScriptValue ret = eng.evaluate(QString::fromLatin1("delete %0").arg(names[i]));
+                QVERIFY(ret.equals(false));
+            }
+            QVERIFY(eng.evaluate(names[i]).equals(values[i]));
+        }
+    }
+
+    // Property that doesn't exist.
+    QVERIFY(eng.evaluate("noSuchProperty").equals("ReferenceError: Can't find variable: noSuchProperty"));
+
+    // Write property from JS.
+    {
+        QScriptValue oldValue = eng.evaluate("foo");
+        QVERIFY(oldValue.isNumber());
+        QScriptValue newValue = oldValue.toNumber() * 2;
+        QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
+        scope.setProperty("foo", oldValue);
+        QVERIFY(eng.evaluate("foo").equals(oldValue));
+    }
+
+    // Write to read-only property.
+    QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
+
+    // Create a closure and return properties from there.
+    {
+        QScriptValue props = eng.evaluate("(function() { var baz = 'shadow'; return [foo, bar, baz, Math, Array]; })()");
+        QVERIFY(props.isArray());
+        // "foo" and "bar" come from scope object.
+        QVERIFY(props.property(0).equals(scope.property("foo")));
+        QVERIFY(props.property(1).equals(scope.property("bar")));
+        // "baz" shadows property in scope object.
+        QVERIFY(props.property(2).equals("shadow"));
+        // "Math" comes from scope object, and shadows Global Object's "Math".
+        QVERIFY(props.property(3).equals(scope.property("Math")));
+        QVERIFY(!props.property(3).equals(eng.globalObject().property("Math")));
+        // "Array" comes from Global Object.
+        QVERIFY(props.property(4).equals(eng.globalObject().property("Array")));
+    }
+
+    // As with normal JS, assigning to an undefined variable will create
+    // the property on the Global Object, not the inner scope.
+    QVERIFY(!eng.globalObject().property("newProperty").isValid());
+    QVERIFY(eng.evaluate("(function() { newProperty = 789; })()").isUndefined());
+    QVERIFY(!scope.property("newProperty").isValid());
+    QVERIFY(eng.globalObject().property("newProperty").isNumber());
+
+    // Nested static scope.
+    {
+        static const int propertyCount2 = 2;
+        QString names2[] = { "foo", "hum" };
+        QScriptValue values2[] = { 321, "hello" };
+        QScriptValue::PropertyFlags flags2[] = { QScriptValue::Undeletable,
+                                                 QScriptValue::ReadOnly | QScriptValue::Undeletable };
+        QScriptValue scope2 = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount2, names2, values2, flags2);
+        ctx->pushScope(scope2);
+
+        // "foo" shadows scope.foo.
+        QVERIFY(eng.evaluate("foo").equals(scope2.property("foo")));
+        QVERIFY(!eng.evaluate("foo").equals(scope.property("foo")));
+        // "hum" comes from scope2.
+        QVERIFY(eng.evaluate("hum").equals(scope2.property("hum")));
+        // "Array" comes from Global Object.
+        QVERIFY(eng.evaluate("Array").equals(eng.globalObject().property("Array")));
+
+        ctx->popScope();
+    }
+
+    QScriptValue fun = eng.evaluate("(function() { return foo; })");
+    QVERIFY(fun.isFunction());
+    eng.popContext();
+    // Function's scope chain persists after popContext().
+    QVERIFY(fun.call().equals(scope.property("foo")));
+}
+
+void tst_QScriptEngine::newGrowingStaticScopeObject()
+{
+    QScriptEngine eng;
+    QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng);
+
+    // Initially empty.
+    QVERIFY(!QScriptValueIterator(scope).hasNext());
+    QVERIFY(!scope.property("foo").isValid());
+
+    // Add a static property.
+    scope.setProperty("foo", 123);
+    QVERIFY(scope.property("foo").equals(123));
+    QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
+
+    // Modify existing property.
+    scope.setProperty("foo", 456);
+    QVERIFY(scope.property("foo").equals(456));
+
+    // Add a read-only property.
+    scope.setProperty("bar", "ciao", QScriptValue::ReadOnly);
+    QVERIFY(scope.property("bar").equals("ciao"));
+    QCOMPARE(scope.propertyFlags("bar"), QScriptValue::ReadOnly | QScriptValue::Undeletable);
+
+    // Attempt to modify read-only property.
+    scope.setProperty("bar", "hello");
+    QVERIFY(scope.property("bar").equals("ciao"));
+
+    // Properties can't be deleted.
+    scope.setProperty("foo", QScriptValue());
+    QVERIFY(scope.property("foo").equals(456));
+    scope.setProperty("bar", QScriptValue());
+    QVERIFY(scope.property("bar").equals("ciao"));
+
+    // Iterate.
+    {
+        QScriptValueIterator it(scope);
+        QSet<QString> iteratedNames;
+        while (it.hasNext()) {
+            it.next();
+            iteratedNames.insert(it.name());
+        }
+        QCOMPARE(iteratedNames.size(), 2);
+        QVERIFY(iteratedNames.contains("foo"));
+        QVERIFY(iteratedNames.contains("bar"));
+    }
+
+    // Push it on the scope chain of a new context.
+    QScriptContext *ctx = eng.pushContext();
+    ctx->pushScope(scope);
+    QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
+    QVERIFY(ctx->activationObject().equals(scope));
+
+    // Read property from JS.
+    QVERIFY(eng.evaluate("foo").equals(scope.property("foo")));
+    QVERIFY(eng.evaluate("bar").equals(scope.property("bar")));
+
+    // Write property from JS.
+    {
+        QScriptValue oldValue = eng.evaluate("foo");
+        QVERIFY(oldValue.isNumber());
+        QScriptValue newValue = oldValue.toNumber() * 2;
+        QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
+        scope.setProperty("foo", oldValue);
+        QVERIFY(eng.evaluate("foo").equals(oldValue));
+    }
+
+    // Write to read-only property.
+    QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
+
+    // Shadow property.
+    QVERIFY(eng.evaluate("Math").equals(eng.globalObject().property("Math")));
+    scope.setProperty("Math", "fake Math");
+    QVERIFY(eng.evaluate("Math").equals(scope.property("Math")));
+
+    // Variable declarations will create properties on the scope.
+    eng.evaluate("var baz = 456");
+    QVERIFY(scope.property("baz").equals(456));
+
+    // Function declarations will create properties on the scope.
+    eng.evaluate("function fun() { return baz; }");
+    QVERIFY(scope.property("fun").isFunction());
+    QVERIFY(scope.property("fun").call().equals(scope.property("baz")));
+
+    // Demonstrate the limitation of a growable static scope: Once a function that
+    // uses the scope has been compiled, it won't pick up properties that are added
+    // to the scope later.
+    {
+        QScriptValue fun = eng.evaluate("(function() { return futureProperty; })");
+        QVERIFY(fun.isFunction());
+        QCOMPARE(fun.call().toString(), QString::fromLatin1("ReferenceError: Can't find variable: futureProperty"));
+        scope.setProperty("futureProperty", "added after the function was compiled");
+        // If scope were dynamic, this would return the new property.
+        QCOMPARE(fun.call().toString(), QString::fromLatin1("ReferenceError: Can't find variable: futureProperty"));
+    }
+
+    eng.popContext();
+}
+
 QTEST_MAIN(tst_QScriptEngine)
 #include "tst_qscriptengine.moc"