Add QJson support to QV8Engine
authorKent Hansen <kent.hansen@nokia.com>
Fri, 23 Mar 2012 17:14:29 +0000 (18:14 +0100)
committerQt by Nokia <qt-info@nokia.com>
Mon, 16 Apr 2012 07:25:06 +0000 (09:25 +0200)
Make QV8Engine perform direct conversion between JavaScript values
and QJson{Value,Object,Array}.

This implementation always makes a deep clone of the
QJson{Object,Array} when converting to JS; it might make sense to add
a lazy conversion scheme for dealing with large objects.

Change-Id: Id0b65891a19515ce22f1e51fa8a28d9f3e595271
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
Reviewed-by: Jamey Hicks <jamey.hicks@nokia.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
26 files changed:
src/qml/qml/v8/qv8engine.cpp
src/qml/qml/v8/qv8engine_p.h
src/qml/qml/v8/qv8jsonwrapper.cpp [new file with mode: 0644]
src/qml/qml/v8/qv8jsonwrapper_p.h [new file with mode: 0644]
src/qml/qml/v8/qv8qobjectwrapper.cpp
src/qml/qml/v8/v8.pri
tests/auto/qml/qjsonbinding/data/array.0.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/array.1.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/array.2.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/array.3.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/array.4.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/empty.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/false.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/null.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/number.0.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/number.1.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/object.0.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/object.1.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/object.2.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/object.3.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/object.4.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/string.0.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/data/true.json [new file with mode: 0644]
tests/auto/qml/qjsonbinding/qjsonbinding.pro [new file with mode: 0644]
tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp [new file with mode: 0644]
tests/auto/qml/qml.pro

index 2302d0e..8444d65 100644 (file)
@@ -58,6 +58,9 @@
 #include "qv8domerrors_p.h"
 #include "qv8sqlerrors_p.h"
 
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qjsonvalue.h>
 
 Q_DECLARE_METATYPE(QJSValue)
 Q_DECLARE_METATYPE(QList<int>)
@@ -155,6 +158,7 @@ QV8Engine::QV8Engine(QJSEngine* qq, QJSEngine::ContextOwnership ownership)
     m_variantWrapper.init(this);
     m_valueTypeWrapper.init(this);
     m_sequenceWrapper.init(this);
+    m_jsonWrapper.init(this);
 
     {
     v8::Handle<v8::Value> v = global()->Get(v8::String::New("Object"))->ToObject()->Get(v8::String::New("getOwnPropertyNames"));
@@ -182,6 +186,7 @@ QV8Engine::~QV8Engine()
 
     qPersistentDispose(m_strongReferencer);
 
+    m_jsonWrapper.destroy();
     m_sequenceWrapper.destroy();
     m_valueTypeWrapper.destroy();
     m_variantWrapper.destroy();
@@ -220,6 +225,9 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
     if (typeHint == QVariant::Bool)
         return QVariant(value->BooleanValue());
 
+    if (typeHint == QMetaType::QJsonValue)
+        return QVariant::fromValue(jsonValueFromJS(value));
+
     if (value->IsObject()) {
         QV8ObjectResource *r = (QV8ObjectResource *)value->ToObject()->GetExternalResource();
         if (r) {
@@ -251,6 +259,9 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
             case QV8ObjectResource::SequenceType:
                 return m_sequenceWrapper.toVariant(r);
             }
+        } else if (typeHint == QMetaType::QJsonObject
+                   && !value->IsArray() && !value->IsFunction()) {
+            return QVariant::fromValue(jsonObjectFromJS(value));
         }
     }
 
@@ -269,6 +280,8 @@ QVariant QV8Engine::toVariant(v8::Handle<v8::Value> value, int typeHint)
             }
 
             return qVariantFromValue<QList<QObject*> >(list);
+        } else if (typeHint == QMetaType::QJsonArray) {
+            return QVariant::fromValue(jsonArrayFromJS(value));
         }
 
         bool succeeded = false;
@@ -368,6 +381,12 @@ v8::Handle<v8::Value> QV8Engine::fromVariant(const QVariant &variant)
                 return arrayFromVariantList(this, *reinterpret_cast<const QVariantList *>(ptr));
             case QMetaType::QVariantMap:
                 return objectFromVariantMap(this, *reinterpret_cast<const QVariantMap *>(ptr));
+            case QMetaType::QJsonValue:
+                return jsonValueToJS(*reinterpret_cast<const QJsonValue *>(ptr));
+            case QMetaType::QJsonObject:
+                return jsonObjectToJS(*reinterpret_cast<const QJsonObject *>(ptr));
+            case QMetaType::QJsonArray:
+                return jsonArrayToJS(*reinterpret_cast<const QJsonArray *>(ptr));
 
             default:
                 break;
@@ -1152,6 +1171,15 @@ v8::Handle<v8::Value> QV8Engine::metaTypeToJS(int type, const void *data)
     case QMetaType::QVariant:
         result = variantToJS(*reinterpret_cast<const QVariant*>(data));
         break;
+    case QMetaType::QJsonValue:
+        result = jsonValueToJS(*reinterpret_cast<const QJsonValue *>(data));
+        break;
+    case QMetaType::QJsonObject:
+        result = jsonObjectToJS(*reinterpret_cast<const QJsonObject *>(data));
+        break;
+    case QMetaType::QJsonArray:
+        result = jsonArrayToJS(*reinterpret_cast<const QJsonArray *>(data));
+        break;
     default:
         if (type == qMetaTypeId<QJSValue>()) {
             return QJSValuePrivate::get(*reinterpret_cast<const QJSValue*>(data))->asV8Value(this);
@@ -1267,6 +1295,15 @@ bool QV8Engine::metaTypeFromJS(v8::Handle<v8::Value> value, int type, void *data
     case QMetaType::QVariant:
         *reinterpret_cast<QVariant*>(data) = variantFromJS(value);
         return true;
+    case QMetaType::QJsonValue:
+        *reinterpret_cast<QJsonValue *>(data) = jsonValueFromJS(value);
+        return true;
+    case QMetaType::QJsonObject:
+        *reinterpret_cast<QJsonObject *>(data) = jsonObjectFromJS(value);
+        return true;
+    case QMetaType::QJsonArray:
+        *reinterpret_cast<QJsonArray *>(data) = jsonArrayFromJS(value);
+        return true;
     default:
     ;
     }
@@ -1383,6 +1420,36 @@ QVariant QV8Engine::variantFromJS(v8::Handle<v8::Value> value)
     return variantMapFromJS(value->ToObject());
 }
 
+v8::Handle<v8::Value> QV8Engine::jsonValueToJS(const QJsonValue &value)
+{
+    return m_jsonWrapper.fromJsonValue(value);
+}
+
+QJsonValue QV8Engine::jsonValueFromJS(v8::Handle<v8::Value> value)
+{
+    return m_jsonWrapper.toJsonValue(value);
+}
+
+v8::Local<v8::Object> QV8Engine::jsonObjectToJS(const QJsonObject &object)
+{
+    return m_jsonWrapper.fromJsonObject(object);
+}
+
+QJsonObject QV8Engine::jsonObjectFromJS(v8::Handle<v8::Value> value)
+{
+    return m_jsonWrapper.toJsonObject(value);
+}
+
+v8::Local<v8::Array> QV8Engine::jsonArrayToJS(const QJsonArray &array)
+{
+    return m_jsonWrapper.fromJsonArray(array);
+}
+
+QJsonArray QV8Engine::jsonArrayFromJS(v8::Handle<v8::Value> value)
+{
+    return m_jsonWrapper.toJsonArray(value);
+}
+
 bool QV8Engine::convertToNativeQObject(v8::Handle<v8::Value> value,
                                                   const QByteArray &targetType,
                                                   void **result)
index 2eb3668..1fc03d8 100644 (file)
@@ -80,6 +80,7 @@
 #include "qv8variantwrapper_p.h"
 #include "qv8valuetypewrapper_p.h"
 #include "qv8sequencewrapper_p.h"
+#include "qv8jsonwrapper_p.h"
 
 QT_BEGIN_NAMESPACE
 
@@ -415,6 +416,13 @@ public:
     v8::Handle<v8::Value> variantToJS(const QVariant &value);
     QVariant variantFromJS(v8::Handle<v8::Value> value);
 
+    v8::Handle<v8::Value> jsonValueToJS(const QJsonValue &value);
+    QJsonValue jsonValueFromJS(v8::Handle<v8::Value> value);
+    v8::Local<v8::Object> jsonObjectToJS(const QJsonObject &object);
+    QJsonObject jsonObjectFromJS(v8::Handle<v8::Value> value);
+    v8::Local<v8::Array> jsonArrayToJS(const QJsonArray &array);
+    QJsonArray jsonArrayFromJS(v8::Handle<v8::Value> value);
+
     v8::Handle<v8::Value> metaTypeToJS(int type, const void *data);
     bool metaTypeFromJS(v8::Handle<v8::Value> value, int type, void *data);
 
@@ -477,6 +485,7 @@ protected:
     QV8VariantWrapper m_variantWrapper;
     QV8ValueTypeWrapper m_valueTypeWrapper;
     QV8SequenceWrapper m_sequenceWrapper;
+    QV8JsonWrapper m_jsonWrapper;
 
     v8::Persistent<v8::Function> m_getOwnPropertyNames;
     v8::Persistent<v8::Function> m_freezeObject;
diff --git a/src/qml/qml/v8/qv8jsonwrapper.cpp b/src/qml/qml/v8/qv8jsonwrapper.cpp
new file mode 100644 (file)
index 0000000..ff8cc4f
--- /dev/null
@@ -0,0 +1,183 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qv8jsonwrapper_p.h"
+#include "qv8engine_p.h"
+#include "qjsconverter_impl_p.h"
+
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qjsonvalue.h>
+
+QT_BEGIN_NAMESPACE
+
+QV8JsonWrapper::QV8JsonWrapper()
+: m_engine(0)
+{
+}
+
+QV8JsonWrapper::~QV8JsonWrapper()
+{
+}
+
+void QV8JsonWrapper::init(QV8Engine *engine)
+{
+    m_engine = engine;
+}
+
+void QV8JsonWrapper::destroy()
+{
+}
+
+v8::Handle<v8::Value> QV8JsonWrapper::fromJsonValue(const QJsonValue &value)
+{
+    if (value.isString())
+        return QJSConverter::toString(value.toString());
+    else if (value.isDouble())
+        return v8::Number::New(value.toDouble());
+    else if (value.isBool())
+        return value.toBool() ? v8::True() : v8::False();
+    else if (value.isArray())
+        return fromJsonArray(value.toArray());
+    else if (value.isObject())
+        return fromJsonObject(value.toObject());
+    else if (value.isNull())
+        return v8::Null();
+    else
+        return v8::Undefined();
+}
+
+QJsonValue QV8JsonWrapper::toJsonValue(v8::Handle<v8::Value> value)
+{
+    if (value->IsString())
+        return QJsonValue(QJSConverter::toString(value.As<v8::String>()));
+    else if (value->IsNumber())
+        return QJsonValue(value->NumberValue());
+    else if (value->IsBoolean())
+        return QJsonValue(value->BooleanValue());
+    else if (value->IsArray())
+        return toJsonArray(value.As<v8::Array>());
+    else if (value->IsObject())
+        return toJsonObject(value.As<v8::Object>());
+    else if (value->IsNull())
+        return QJsonValue(QJsonValue::Null);
+    else
+        return QJsonValue(QJsonValue::Undefined);
+}
+
+v8::Local<v8::Object> QV8JsonWrapper::fromJsonObject(const QJsonObject &object)
+{
+    v8::Local<v8::Object> v8object = v8::Object::New();
+    for (QJsonObject::const_iterator it = object.begin(); it != object.end(); ++it)
+        v8object->Set(QJSConverter::toString(it.key()), fromJsonValue(it.value()));
+    return v8object;
+}
+
+QJsonObject QV8JsonWrapper::toJsonObject(v8::Handle<v8::Value> value)
+{
+    QJsonObject result;
+    if (!value->IsObject() || value->IsArray() || value->IsFunction())
+        return result;
+
+    v8::Handle<v8::Object> v8object(value.As<v8::Object>());
+    int hash = v8object->GetIdentityHash();
+    if (m_visitedConversionObjects.contains(hash)) {
+        // Avoid recursion.
+        // For compatibility with QVariant{List,Map} conversion, we return an
+        // empty object (and no error is thrown).
+        return result;
+    }
+
+    m_visitedConversionObjects.insert(hash);
+
+    v8::Local<v8::Array> propertyNames = m_engine->getOwnPropertyNames(v8object);
+    uint32_t length = propertyNames->Length();
+    for (uint32_t i = 0; i < length; ++i) {
+        v8::Local<v8::Value> name = propertyNames->Get(i);
+        v8::Local<v8::Value> propertyValue = v8object->Get(name);
+        if (!propertyValue->IsFunction())
+            result.insert(QJSConverter::toString(name->ToString()), toJsonValue(propertyValue));
+    }
+
+    m_visitedConversionObjects.remove(hash);
+
+    return result;
+}
+
+v8::Local<v8::Array> QV8JsonWrapper::fromJsonArray(const QJsonArray &array)
+{
+    int size = array.size();
+    v8::Local<v8::Array> v8array = v8::Array::New(size);
+    for (int i = 0; i < size; i++)
+        v8array->Set(i, fromJsonValue(array.at(i)));
+    return v8array;
+}
+
+QJsonArray QV8JsonWrapper::toJsonArray(v8::Handle<v8::Value> value)
+{
+    QJsonArray result;
+    if (!value->IsArray())
+        return result;
+
+    v8::Handle<v8::Array> v8array(value.As<v8::Array>());
+    int hash = v8array->GetIdentityHash();
+    if (m_visitedConversionObjects.contains(hash)) {
+        // Avoid recursion.
+        // For compatibility with QVariant{List,Map} conversion, we return an
+        // empty array (and no error is thrown).
+        return result;
+    }
+
+    m_visitedConversionObjects.insert(hash);
+
+    uint32_t length = v8array->Length();
+    for (uint32_t i = 0; i < length; ++i) {
+        v8::Local<v8::Value> element = v8array->Get(i);
+        if (!element->IsFunction())
+            result.append(toJsonValue(element));
+    }
+
+    m_visitedConversionObjects.remove(hash);
+
+    return result;
+}
+
+QT_END_NAMESPACE
diff --git a/src/qml/qml/v8/qv8jsonwrapper_p.h b/src/qml/qml/v8/qv8jsonwrapper_p.h
new file mode 100644 (file)
index 0000000..842a3aa
--- /dev/null
@@ -0,0 +1,93 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the QtQml module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QV8JSONWRAPPER_P_H
+#define QV8JSONWRAPPER_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API.  It exists purely as an
+// implementation detail.  This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qglobal.h>
+#include <QtCore/qset.h>
+#include <private/qv8_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QJsonValue;
+class QJsonObject;
+class QJsonArray;
+
+class QV8Engine;
+class QV8JsonWrapper
+{
+public:
+    QV8JsonWrapper();
+    ~QV8JsonWrapper();
+
+    void init(QV8Engine *);
+    void destroy();
+
+    v8::Handle<v8::Value> fromJsonValue(const QJsonValue &value);
+    QJsonValue toJsonValue(v8::Handle<v8::Value> value);
+
+    v8::Local<v8::Object> fromJsonObject(const QJsonObject &object);
+    QJsonObject toJsonObject(v8::Handle<v8::Value> value);
+
+    v8::Local<v8::Array> fromJsonArray(const QJsonArray &array);
+    QJsonArray toJsonArray(v8::Handle<v8::Value> value);
+
+private:
+    QV8Engine *m_engine;
+    QSet<int> m_visitedConversionObjects;
+};
+
+QT_END_NAMESPACE
+
+#endif // QV8JSONWRAPPER_P_H
+
index ce85725..9a5aaca 100644 (file)
@@ -54,6 +54,7 @@
 #include <private/qqmlexpression_p.h>
 
 #include <QtQml/qjsvalue.h>
+#include <QtCore/qjsonvalue.h>
 #include <QtCore/qvarlengtharray.h>
 #include <QtCore/qtimer.h>
 #include <QtCore/qatomic.h>
@@ -659,6 +660,8 @@ static inline void StoreProperty(QV8Engine *engine, QObject *object, QQmlPropert
         QMetaObject::metacall(object, QMetaObject::ResetProperty, property->coreIndex, a);
     } else if (value->IsUndefined() && property->propType == qMetaTypeId<QVariant>()) {
         PROPERTY_STORE(QVariant, QVariant());
+    } else if (value->IsUndefined() && property->propType == QMetaType::QJsonValue) {
+        PROPERTY_STORE(QJsonValue, QJsonValue(QJsonValue::Undefined));
     } else if (value->IsUndefined()) {
         QString error = QLatin1String("Cannot assign [undefined] to ") +
                         QLatin1String(QMetaType::typeName(property->propType));
index 7816c84..52b6bf4 100644 (file)
@@ -15,6 +15,7 @@ HEADERS += \
     $$PWD/qv8variantwrapper_p.h \
     $$PWD/qv8variantresource_p.h \
     $$PWD/qv8valuetypewrapper_p.h \
+    $$PWD/qv8jsonwrapper_p.h \
     $$PWD/qv8include_p.h \
     $$PWD/qv8worker_p.h \
     $$PWD/qv8bindings_p.h \
@@ -33,6 +34,7 @@ SOURCES += \
     $$PWD/qv8listwrapper.cpp \
     $$PWD/qv8variantwrapper.cpp \
     $$PWD/qv8valuetypewrapper.cpp \
+    $$PWD/qv8jsonwrapper.cpp \
     $$PWD/qv8include.cpp \
     $$PWD/qv8worker.cpp \
     $$PWD/qv8bindings.cpp \
diff --git a/tests/auto/qml/qjsonbinding/data/array.0.json b/tests/auto/qml/qjsonbinding/data/array.0.json
new file mode 100644 (file)
index 0000000..fe51488
--- /dev/null
@@ -0,0 +1 @@
+[]
diff --git a/tests/auto/qml/qjsonbinding/data/array.1.json b/tests/auto/qml/qjsonbinding/data/array.1.json
new file mode 100644 (file)
index 0000000..3214bfe
--- /dev/null
@@ -0,0 +1 @@
+[123]
diff --git a/tests/auto/qml/qjsonbinding/data/array.2.json b/tests/auto/qml/qjsonbinding/data/array.2.json
new file mode 100644 (file)
index 0000000..7fd87cd
--- /dev/null
@@ -0,0 +1 @@
+[true,false,null,"hello"]
diff --git a/tests/auto/qml/qjsonbinding/data/array.3.json b/tests/auto/qml/qjsonbinding/data/array.3.json
new file mode 100644 (file)
index 0000000..3b418fc
--- /dev/null
@@ -0,0 +1 @@
+[{"a":42}]
diff --git a/tests/auto/qml/qjsonbinding/data/array.4.json b/tests/auto/qml/qjsonbinding/data/array.4.json
new file mode 100644 (file)
index 0000000..55e9fdc
--- /dev/null
@@ -0,0 +1 @@
+[[[42]],[]]
diff --git a/tests/auto/qml/qjsonbinding/data/empty.json b/tests/auto/qml/qjsonbinding/data/empty.json
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/auto/qml/qjsonbinding/data/false.json b/tests/auto/qml/qjsonbinding/data/false.json
new file mode 100644 (file)
index 0000000..c508d53
--- /dev/null
@@ -0,0 +1 @@
+false
diff --git a/tests/auto/qml/qjsonbinding/data/null.json b/tests/auto/qml/qjsonbinding/data/null.json
new file mode 100644 (file)
index 0000000..19765bd
--- /dev/null
@@ -0,0 +1 @@
+null
diff --git a/tests/auto/qml/qjsonbinding/data/number.0.json b/tests/auto/qml/qjsonbinding/data/number.0.json
new file mode 100644 (file)
index 0000000..190a180
--- /dev/null
@@ -0,0 +1 @@
+123
diff --git a/tests/auto/qml/qjsonbinding/data/number.1.json b/tests/auto/qml/qjsonbinding/data/number.1.json
new file mode 100644 (file)
index 0000000..c07e7a1
--- /dev/null
@@ -0,0 +1 @@
+42.35
diff --git a/tests/auto/qml/qjsonbinding/data/object.0.json b/tests/auto/qml/qjsonbinding/data/object.0.json
new file mode 100644 (file)
index 0000000..0967ef4
--- /dev/null
@@ -0,0 +1 @@
+{}
diff --git a/tests/auto/qml/qjsonbinding/data/object.1.json b/tests/auto/qml/qjsonbinding/data/object.1.json
new file mode 100644 (file)
index 0000000..bde58e7
--- /dev/null
@@ -0,0 +1 @@
+{"foo":123}
diff --git a/tests/auto/qml/qjsonbinding/data/object.2.json b/tests/auto/qml/qjsonbinding/data/object.2.json
new file mode 100644 (file)
index 0000000..d6f25a1
--- /dev/null
@@ -0,0 +1 @@
+{"a":true,"b":false,"c":null,"d":"hello"}
diff --git a/tests/auto/qml/qjsonbinding/data/object.3.json b/tests/auto/qml/qjsonbinding/data/object.3.json
new file mode 100644 (file)
index 0000000..9b76117
--- /dev/null
@@ -0,0 +1 @@
+{"a":{"b":{"c":42}}}
diff --git a/tests/auto/qml/qjsonbinding/data/object.4.json b/tests/auto/qml/qjsonbinding/data/object.4.json
new file mode 100644 (file)
index 0000000..3773137
--- /dev/null
@@ -0,0 +1 @@
+{"a":[],"b":[42],"c":{"d":null}}
diff --git a/tests/auto/qml/qjsonbinding/data/string.0.json b/tests/auto/qml/qjsonbinding/data/string.0.json
new file mode 100644 (file)
index 0000000..3580093
--- /dev/null
@@ -0,0 +1 @@
+"hello"
diff --git a/tests/auto/qml/qjsonbinding/data/true.json b/tests/auto/qml/qjsonbinding/data/true.json
new file mode 100644 (file)
index 0000000..27ba77d
--- /dev/null
@@ -0,0 +1 @@
+true
diff --git a/tests/auto/qml/qjsonbinding/qjsonbinding.pro b/tests/auto/qml/qjsonbinding/qjsonbinding.pro
new file mode 100644 (file)
index 0000000..92e5b7a
--- /dev/null
@@ -0,0 +1,16 @@
+CONFIG += testcase
+TARGET = tst_qjsonbinding
+macx:CONFIG -= app_bundle
+
+SOURCES += tst_qjsonbinding.cpp
+INCLUDEPATH += ../../shared
+
+include (../../shared/util.pri)
+
+# QMAKE_CXXFLAGS = -fprofile-arcs -ftest-coverage
+# LIBS += -lgcov
+
+TESTDATA = data/*
+
+CONFIG += parallel_test
+QT += core qml testlib
diff --git a/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp b/tests/auto/qml/qjsonbinding/tst_qjsonbinding.cpp
new file mode 100644 (file)
index 0000000..09b2562
--- /dev/null
@@ -0,0 +1,535 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QtTest/QtTest>
+#include <QtQml/QtQml>
+#include "../../shared/util.h"
+
+Q_DECLARE_METATYPE(QJsonValue::Type)
+
+class JsonPropertyObject : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(QJsonValue value READ value WRITE setValue)
+    Q_PROPERTY(QJsonObject object READ object WRITE setObject)
+    Q_PROPERTY(QJsonArray array READ array WRITE setArray)
+public:
+    QJsonValue value() const { return m_value; }
+    void setValue(const QJsonValue &v) { m_value = v; }
+    QJsonObject object() const { return m_object; }
+    void setObject(const QJsonObject &o) { m_object = o; }
+    QJsonArray array() const { return m_array; }
+    void setArray(const QJsonArray &a) { m_array = a; }
+
+private:
+    QJsonValue m_value;
+    QJsonObject m_object;
+    QJsonArray m_array;
+};
+
+class tst_qjsonbinding : public QQmlDataTest
+{
+    Q_OBJECT
+public:
+    tst_qjsonbinding() {}
+
+private slots:
+    void cppJsConversion_data();
+    void cppJsConversion();
+
+    void readValueProperty_data();
+    void readValueProperty();
+    void readObjectOrArrayProperty_data();
+    void readObjectOrArrayProperty();
+
+    void writeValueProperty_data();
+    void writeValueProperty();
+    void writeObjectOrArrayProperty_data();
+    void writeObjectOrArrayProperty();
+
+    void writeProperty_incompatibleType_data();
+    void writeProperty_incompatibleType();
+
+    void writeProperty_javascriptExpression_data();
+    void writeProperty_javascriptExpression();
+
+private:
+    QByteArray readAsUtf8(const QString &fileName);
+    static QJsonValue valueFromJson(const QByteArray &json);
+
+    void addPrimitiveDataTestFiles();
+    void addObjectDataTestFiles();
+    void addArrayDataTestFiles();
+};
+
+QByteArray tst_qjsonbinding::readAsUtf8(const QString &fileName)
+{
+    QFile file(testFile(fileName));
+    file.open(QIODevice::ReadOnly);
+    QTextStream stream(&file);
+    return stream.readAll().trimmed().toUtf8();
+}
+
+QJsonValue tst_qjsonbinding::valueFromJson(const QByteArray &json)
+{
+    if (json.isEmpty())
+        return QJsonValue(QJsonValue::Undefined);
+
+    QJsonDocument doc = QJsonDocument::fromJson(json);
+    if (!doc.isEmpty())
+        return doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
+
+    // QJsonDocument::fromJson() only handles objects and arrays...
+    // Wrap the JSON inside a dummy object and extract the value.
+    QByteArray wrappedJson = "{\"prop\":" + json + "}";
+    doc = QJsonDocument::fromJson(wrappedJson);
+    Q_ASSERT(doc.isObject());
+    return doc.object().value("prop");
+}
+
+void tst_qjsonbinding::addPrimitiveDataTestFiles()
+{
+    QTest::newRow("true") << "true.json";
+    QTest::newRow("false") << "false.json";
+
+    QTest::newRow("null") << "null.json";
+
+    QTest::newRow("number.0") << "number.0.json";
+    QTest::newRow("number.1") << "number.1.json";
+
+    QTest::newRow("string.0") << "string.0.json";
+
+    QTest::newRow("undefined") << "empty.json";
+}
+
+void tst_qjsonbinding::addObjectDataTestFiles()
+{
+    QTest::newRow("object.0") << "object.0.json";
+    QTest::newRow("object.1") << "object.1.json";
+    QTest::newRow("object.2") << "object.2.json";
+    QTest::newRow("object.3") << "object.3.json";
+    QTest::newRow("object.4") << "object.4.json";
+}
+
+void tst_qjsonbinding::addArrayDataTestFiles()
+{
+    QTest::newRow("array.0") << "array.0.json";
+    QTest::newRow("array.1") << "array.1.json";
+    QTest::newRow("array.2") << "array.2.json";
+    QTest::newRow("array.3") << "array.3.json";
+    QTest::newRow("array.4") << "array.4.json";
+}
+
+void tst_qjsonbinding::cppJsConversion_data()
+{
+    QTest::addColumn<QString>("fileName");
+
+    addPrimitiveDataTestFiles();
+    addObjectDataTestFiles();
+    addArrayDataTestFiles();
+}
+
+void tst_qjsonbinding::cppJsConversion()
+{
+    QFETCH(QString, fileName);
+
+    QByteArray json = readAsUtf8(fileName);
+    QJsonValue jsonValue = valueFromJson(json);
+
+    QJSEngine eng;
+    QJSValue stringify = eng.globalObject().property("JSON").property("stringify");
+    QVERIFY(stringify.isCallable());
+
+    {
+        QJSValue jsValue = eng.toScriptValue(jsonValue);
+        QVERIFY(!jsValue.isVariant());
+        switch (jsonValue.type()) {
+        case QJsonValue::Null:
+            QVERIFY(jsValue.isNull());
+            break;
+        case QJsonValue::Bool:
+            QVERIFY(jsValue.isBool());
+            QCOMPARE(jsValue.toBool(), jsonValue.toBool());
+            break;
+        case QJsonValue::Double:
+            QVERIFY(jsValue.isNumber());
+            QCOMPARE(jsValue.toNumber(), jsonValue.toDouble());
+            break;
+        case QJsonValue::String:
+            QVERIFY(jsValue.isString());
+            QCOMPARE(jsValue.toString(), jsonValue.toString());
+            break;
+        case QJsonValue::Array:
+            QVERIFY(jsValue.isArray());
+            break;
+        case QJsonValue::Object:
+            QVERIFY(jsValue.isObject());
+            break;
+        case QJsonValue::Undefined:
+            QVERIFY(jsValue.isUndefined());
+            break;
+        }
+
+        if (jsValue.isUndefined()) {
+            QVERIFY(json.isEmpty());
+        } else {
+            QJSValue stringified = stringify.call(QJSValueList() << jsValue);
+            QVERIFY(!stringified.isError());
+            QCOMPARE(stringified.toString().toUtf8(), json);
+        }
+
+        QJsonValue roundtrip = qjsvalue_cast<QJsonValue>(jsValue);
+        // Workarounds for QTBUG-25164
+        if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
+            QVERIFY(roundtrip.isObject() && roundtrip.toObject().isEmpty());
+        else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
+            QVERIFY(roundtrip.isArray() && roundtrip.toArray().isEmpty());
+        else
+            QCOMPARE(roundtrip, jsonValue);
+    }
+
+    if (jsonValue.isObject()) {
+        QJsonObject jsonObject = jsonValue.toObject();
+        QJSValue jsObject = eng.toScriptValue(jsonObject);
+        QVERIFY(!jsObject.isVariant());
+        QVERIFY(jsObject.isObject());
+
+        QJSValue stringified = stringify.call(QJSValueList() << jsObject);
+        QVERIFY(!stringified.isError());
+        QCOMPARE(stringified.toString().toUtf8(), json);
+
+        QJsonObject roundtrip = qjsvalue_cast<QJsonObject>(jsObject);
+        QCOMPARE(roundtrip, jsonObject);
+    } else if (jsonValue.isArray()) {
+        QJsonArray jsonArray = jsonValue.toArray();
+        QJSValue jsArray = eng.toScriptValue(jsonArray);
+        QVERIFY(!jsArray.isVariant());
+        QVERIFY(jsArray.isArray());
+
+        QJSValue stringified = stringify.call(QJSValueList() << jsArray);
+        QVERIFY(!stringified.isError());
+        QCOMPARE(stringified.toString().toUtf8(), json);
+
+        QJsonArray roundtrip = qjsvalue_cast<QJsonArray>(jsArray);
+        QCOMPARE(roundtrip, jsonArray);
+    }
+}
+
+void tst_qjsonbinding::readValueProperty_data()
+{
+    cppJsConversion_data();
+}
+
+void tst_qjsonbinding::readValueProperty()
+{
+    QFETCH(QString, fileName);
+
+    QByteArray json = readAsUtf8(fileName);
+    QJsonValue jsonValue = valueFromJson(json);
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    obj.setValue(jsonValue);
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+    QJSValue stringified = eng.evaluate(
+                "var v = obj.value; (typeof v == 'undefined') ? '' : JSON.stringify(v)");
+    QVERIFY(!stringified.isError());
+    QCOMPARE(stringified.toString().toUtf8(), json);
+}
+
+void tst_qjsonbinding::readObjectOrArrayProperty_data()
+{
+    QTest::addColumn<QString>("fileName");
+
+    addObjectDataTestFiles();
+    addArrayDataTestFiles();
+}
+
+void tst_qjsonbinding::readObjectOrArrayProperty()
+{
+    QFETCH(QString, fileName);
+
+    QByteArray json = readAsUtf8(fileName);
+    QJsonValue jsonValue = valueFromJson(json);
+    QVERIFY(jsonValue.isObject() || jsonValue.isArray());
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    if (jsonValue.isObject())
+        obj.setObject(jsonValue.toObject());
+    else
+        obj.setArray(jsonValue.toArray());
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+
+    QJSValue stringified = eng.evaluate(
+                QString::fromLatin1("JSON.stringify(obj.%0)").arg(
+                    jsonValue.isObject() ? "object" : "array"));
+    QVERIFY(!stringified.isError());
+    QCOMPARE(stringified.toString().toUtf8(), json);
+}
+
+void tst_qjsonbinding::writeValueProperty_data()
+{
+    readValueProperty_data();
+}
+
+void tst_qjsonbinding::writeValueProperty()
+{
+    QFETCH(QString, fileName);
+
+    QByteArray json = readAsUtf8(fileName);
+    QJsonValue jsonValue = valueFromJson(json);
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+
+    QJSValue fun = eng.evaluate(
+                "(function(json) {"
+                "  void(obj.value = (json == '') ? undefined : JSON.parse(json));"
+                "})");
+    QVERIFY(fun.isCallable());
+
+    QVERIFY(obj.value().isNull());
+    QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
+
+    // Workarounds for QTBUG-25164
+    if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
+        QVERIFY(obj.value().isObject() && obj.value().toObject().isEmpty());
+    else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
+        QVERIFY(obj.value().isArray() && obj.value().toArray().isEmpty());
+    else
+        QCOMPARE(obj.value(), jsonValue);
+}
+
+void tst_qjsonbinding::writeObjectOrArrayProperty_data()
+{
+    readObjectOrArrayProperty_data();
+}
+
+void tst_qjsonbinding::writeObjectOrArrayProperty()
+{
+    QFETCH(QString, fileName);
+
+    QByteArray json = readAsUtf8(fileName);
+    QJsonValue jsonValue = valueFromJson(json);
+    QVERIFY(jsonValue.isObject() || jsonValue.isArray());
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+
+    QJSValue fun = eng.evaluate(
+                QString::fromLatin1(
+                    "(function(json) {"
+                    "  void(obj.%0 = JSON.parse(json));"
+                    "})").arg(jsonValue.isObject() ? "object" : "array")
+                );
+    QVERIFY(fun.isCallable());
+
+    QVERIFY(obj.object().isEmpty() && obj.array().isEmpty());
+    QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
+
+    if (jsonValue.isObject())
+        QCOMPARE(obj.object(), jsonValue.toObject());
+    else
+        QCOMPARE(obj.array(), jsonValue.toArray());
+}
+
+void tst_qjsonbinding::writeProperty_incompatibleType_data()
+{
+    QTest::addColumn<QString>("property");
+    QTest::addColumn<QString>("expression");
+
+    QTest::newRow("value=function") << "value" << "(function(){})";
+
+    QTest::newRow("object=undefined") << "object" << "undefined";
+    QTest::newRow("object=null") << "object" << "null";
+    QTest::newRow("object=false") << "object" << "false";
+    QTest::newRow("object=true") << "object" << "true";
+    QTest::newRow("object=123") << "object" << "123";
+    QTest::newRow("object=42.35") << "object" << "42.35";
+    QTest::newRow("object='foo'") << "object" << "'foo'";
+    QTest::newRow("object=[]") << "object" << "[]";
+    QTest::newRow("object=function") << "object" << "(function(){})";
+
+    QTest::newRow("array=undefined") << "array" << "undefined";
+    QTest::newRow("array=null") << "array" << "null";
+    QTest::newRow("array=false") << "array" << "false";
+    QTest::newRow("array=true") << "array" << "true";
+    QTest::newRow("array=123") << "array" << "123";
+    QTest::newRow("array=42.35") << "array" << "42.35";
+    QTest::newRow("array='foo'") << "array" << "'foo'";
+    QTest::newRow("array={}") << "array" << "{}";
+    QTest::newRow("array=function") << "array" << "(function(){})";
+}
+
+void tst_qjsonbinding::writeProperty_incompatibleType()
+{
+    QFETCH(QString, property);
+    QFETCH(QString, expression);
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+
+    QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1")
+                                .arg(property).arg(expression));
+    QEXPECT_FAIL("value=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
+    QEXPECT_FAIL("object=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
+    QEXPECT_FAIL("array=function", "See 'XXX TODO: uncomment the following lines' in qv8qobjectwrapper.cpp", Abort);
+    QVERIFY(ret.isError());
+    QVERIFY(ret.toString().contains("Cannot assign"));
+}
+
+void tst_qjsonbinding::writeProperty_javascriptExpression_data()
+{
+    QTest::addColumn<QString>("property");
+    QTest::addColumn<QString>("expression");
+    QTest::addColumn<QString>("expectedJson");
+
+    // Function properties should be omitted.
+    QTest::newRow("value = object with function property")
+            << "value" << "{ foo: function() {} }" << "{}";
+    QTest::newRow("object = object with function property")
+            << "object" << "{ foo: function() {} }" << "{}";
+    QTest::newRow("array = array with function property")
+            << "array" << "[function() {}]" << "[]";
+
+    // Inherited properties should not be included.
+    QTest::newRow("value = object with inherited property")
+            << "value" << "{ __proto__: { proto_foo: 123 } }"
+            << "{}";
+    QTest::newRow("value = object with inherited property 2")
+            << "value" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
+            << "{\"foo\":123}";
+    QTest::newRow("value = array with inherited property")
+            << "value" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
+            << "[]";
+    QTest::newRow("value = array with inherited property 2")
+            << "value" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
+            << "[10,20]";
+
+    QTest::newRow("object = object with inherited property")
+            << "object" << "{ __proto__: { proto_foo: 123 } }"
+            << "{}";
+    QTest::newRow("object = object with inherited property 2")
+            << "object" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
+            << "{\"foo\":123}";
+    QTest::newRow("array = array with inherited property")
+            << "array" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
+            << "[]";
+    QTest::newRow("array = array with inherited property 2")
+            << "array" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
+            << "[10,20]";
+
+    // Non-enumerable properties should be included.
+    QTest::newRow("value = object with non-enumerable property")
+            << "value" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
+            << "{\"foo\":123}";
+    QTest::newRow("object = object with non-enumerable property")
+            << "object" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
+            << "{\"foo\":123}";
+
+    // Cyclic data structures are permitted, but the cyclic links become
+    // empty objects.
+    QTest::newRow("value = cyclic object")
+            << "value" << "(function() { var o = { foo: 123 }; o.o = o; return o; })()"
+            << "{\"foo\":123,\"o\":{}}";
+    QTest::newRow("value = cyclic array")
+            << "value" << "(function() { var a = [10, 20]; a.push(a); return a; })()"
+            << "[10,20,[]]";
+    QTest::newRow("object = cyclic object")
+            << "object" << "(function() { var o = { bar: true }; o.o = o; return o; })()"
+            << "{\"bar\":true,\"o\":{}}";
+    QTest::newRow("array = cyclic array")
+            << "array" << "(function() { var a = [30, 40]; a.unshift(a); return a; })()"
+            << "[[],30,40]";
+
+    // Properties with undefined value are excluded.
+    QTest::newRow("value = { foo: undefined }")
+            << "value" << "{ foo: undefined }" << "{}";
+    QTest::newRow("value = { foo: undefined, bar: 123 }")
+            << "value" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
+    QTest::newRow("value = { foo: 456, bar: undefined }")
+            << "value" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
+
+    QTest::newRow("object = { foo: undefined }")
+            << "object" << "{ foo: undefined }" << "{}";
+    QTest::newRow("object = { foo: undefined, bar: 123 }")
+            << "object" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
+    QTest::newRow("object = { foo: 456, bar: undefined }")
+            << "object" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
+
+    // QJsonArray::append() implicitly converts undefined values to null.
+    QTest::newRow("value = [undefined]")
+            << "value" << "[undefined]" << "[null]";
+    QTest::newRow("value = [undefined, 10]")
+            << "value" << "[undefined, 10]" << "[null,10]";
+    QTest::newRow("value = [10, undefined, 20]")
+            << "value" << "[10, undefined, 20]" << "[10,null,20]";
+
+    QTest::newRow("array = [undefined]")
+            << "array" << "[undefined]" << "[null]";
+    QTest::newRow("array = [undefined, 10]")
+            << "array" << "[undefined, 10]" << "[null,10]";
+    QTest::newRow("array = [10, undefined, 20]")
+            << "array" << "[10, undefined, 20]" << "[10,null,20]";
+}
+
+void tst_qjsonbinding::writeProperty_javascriptExpression()
+{
+    QFETCH(QString, property);
+    QFETCH(QString, expression);
+    QFETCH(QString, expectedJson);
+
+    QJSEngine eng;
+    JsonPropertyObject obj;
+    eng.globalObject().setProperty("obj", eng.newQObject(&obj));
+
+    QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1; JSON.stringify(obj.%0)")
+                                .arg(property).arg(expression));
+    QVERIFY(!ret.isError());
+    QCOMPARE(ret.toString(), expectedJson);
+}
+
+QTEST_MAIN(tst_qjsonbinding)
+
+#include "tst_qjsonbinding.moc"
index 0657ea8..3166c3b 100644 (file)
@@ -8,6 +8,7 @@ PUBLICTESTS += \
     qjsengine \
     qjsvalue \
     qjsvalueiterator \
+    qjsonbinding \
     qmlmin \
     qmlplugindump \
     qqmlcomponent \