2900b5cfe384d968f09f1f3ee5a34d244eaa3cef
[profile/ivi/qtdeclarative.git] / tests / auto / qml / qjsonbinding / tst_qjsonbinding.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
16 **
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
20 **
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
28 **
29 ** Other Usage
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <QtTest/QtTest>
42 #include <QtQml/QtQml>
43 #include "../../shared/util.h"
44
45 Q_DECLARE_METATYPE(QJsonValue::Type)
46
47 class JsonPropertyObject : public QObject
48 {
49     Q_OBJECT
50     Q_PROPERTY(QJsonValue value READ value WRITE setValue)
51     Q_PROPERTY(QJsonObject object READ object WRITE setObject)
52     Q_PROPERTY(QJsonArray array READ array WRITE setArray)
53 public:
54     QJsonValue value() const { return m_value; }
55     void setValue(const QJsonValue &v) { m_value = v; }
56     QJsonObject object() const { return m_object; }
57     void setObject(const QJsonObject &o) { m_object = o; }
58     QJsonArray array() const { return m_array; }
59     void setArray(const QJsonArray &a) { m_array = a; }
60
61 private:
62     QJsonValue m_value;
63     QJsonObject m_object;
64     QJsonArray m_array;
65 };
66
67 class tst_qjsonbinding : public QQmlDataTest
68 {
69     Q_OBJECT
70 public:
71     tst_qjsonbinding() {}
72
73 private slots:
74     void cppJsConversion_data();
75     void cppJsConversion();
76
77     void readValueProperty_data();
78     void readValueProperty();
79     void readObjectOrArrayProperty_data();
80     void readObjectOrArrayProperty();
81
82     void writeValueProperty_data();
83     void writeValueProperty();
84     void writeObjectOrArrayProperty_data();
85     void writeObjectOrArrayProperty();
86
87     void writeProperty_incompatibleType_data();
88     void writeProperty_incompatibleType();
89
90     void writeProperty_javascriptExpression_data();
91     void writeProperty_javascriptExpression();
92
93 private:
94     QByteArray readAsUtf8(const QString &fileName);
95     static QJsonValue valueFromJson(const QByteArray &json);
96
97     void addPrimitiveDataTestFiles();
98     void addObjectDataTestFiles();
99     void addArrayDataTestFiles();
100 };
101
102 QByteArray tst_qjsonbinding::readAsUtf8(const QString &fileName)
103 {
104     QFile file(testFile(fileName));
105     file.open(QIODevice::ReadOnly);
106     QTextStream stream(&file);
107     return stream.readAll().trimmed().toUtf8();
108 }
109
110 QJsonValue tst_qjsonbinding::valueFromJson(const QByteArray &json)
111 {
112     if (json.isEmpty())
113         return QJsonValue(QJsonValue::Undefined);
114
115     QJsonDocument doc = QJsonDocument::fromJson(json);
116     if (!doc.isEmpty())
117         return doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
118
119     // QJsonDocument::fromJson() only handles objects and arrays...
120     // Wrap the JSON inside a dummy object and extract the value.
121     QByteArray wrappedJson = "{\"prop\":" + json + "}";
122     doc = QJsonDocument::fromJson(wrappedJson);
123     Q_ASSERT(doc.isObject());
124     return doc.object().value("prop");
125 }
126
127 void tst_qjsonbinding::addPrimitiveDataTestFiles()
128 {
129     QTest::newRow("true") << "true.json";
130     QTest::newRow("false") << "false.json";
131
132     QTest::newRow("null") << "null.json";
133
134     QTest::newRow("number.0") << "number.0.json";
135     QTest::newRow("number.1") << "number.1.json";
136
137     QTest::newRow("string.0") << "string.0.json";
138
139     QTest::newRow("undefined") << "empty.json";
140 }
141
142 void tst_qjsonbinding::addObjectDataTestFiles()
143 {
144     QTest::newRow("object.0") << "object.0.json";
145     QTest::newRow("object.1") << "object.1.json";
146     QTest::newRow("object.2") << "object.2.json";
147     QTest::newRow("object.3") << "object.3.json";
148     QTest::newRow("object.4") << "object.4.json";
149 }
150
151 void tst_qjsonbinding::addArrayDataTestFiles()
152 {
153     QTest::newRow("array.0") << "array.0.json";
154     QTest::newRow("array.1") << "array.1.json";
155     QTest::newRow("array.2") << "array.2.json";
156     QTest::newRow("array.3") << "array.3.json";
157     QTest::newRow("array.4") << "array.4.json";
158 }
159
160 void tst_qjsonbinding::cppJsConversion_data()
161 {
162     QTest::addColumn<QString>("fileName");
163
164     addPrimitiveDataTestFiles();
165     addObjectDataTestFiles();
166     addArrayDataTestFiles();
167 }
168
169 void tst_qjsonbinding::cppJsConversion()
170 {
171     QFETCH(QString, fileName);
172
173     QByteArray json = readAsUtf8(fileName);
174     QJsonValue jsonValue = valueFromJson(json);
175
176     QJSEngine eng;
177     QJSValue stringify = eng.globalObject().property("JSON").property("stringify");
178     QVERIFY(stringify.isCallable());
179
180     {
181         QJSValue jsValue = eng.toScriptValue(jsonValue);
182         QVERIFY(!jsValue.isVariant());
183         switch (jsonValue.type()) {
184         case QJsonValue::Null:
185             QVERIFY(jsValue.isNull());
186             break;
187         case QJsonValue::Bool:
188             QVERIFY(jsValue.isBool());
189             QCOMPARE(jsValue.toBool(), jsonValue.toBool());
190             break;
191         case QJsonValue::Double:
192             QVERIFY(jsValue.isNumber());
193             QCOMPARE(jsValue.toNumber(), jsonValue.toDouble());
194             break;
195         case QJsonValue::String:
196             QVERIFY(jsValue.isString());
197             QCOMPARE(jsValue.toString(), jsonValue.toString());
198             break;
199         case QJsonValue::Array:
200             QVERIFY(jsValue.isArray());
201             break;
202         case QJsonValue::Object:
203             QVERIFY(jsValue.isObject());
204             break;
205         case QJsonValue::Undefined:
206             QVERIFY(jsValue.isUndefined());
207             break;
208         }
209
210         if (jsValue.isUndefined()) {
211             QVERIFY(json.isEmpty());
212         } else {
213             QJSValue stringified = stringify.call(QJSValueList() << jsValue);
214             QVERIFY(!stringified.isError());
215             QCOMPARE(stringified.toString().toUtf8(), json);
216         }
217
218         QJsonValue roundtrip = qjsvalue_cast<QJsonValue>(jsValue);
219         // Workarounds for QTBUG-25164
220         if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
221             QVERIFY(roundtrip.isObject() && roundtrip.toObject().isEmpty());
222         else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
223             QVERIFY(roundtrip.isArray() && roundtrip.toArray().isEmpty());
224         else
225             QCOMPARE(roundtrip, jsonValue);
226     }
227
228     if (jsonValue.isObject()) {
229         QJsonObject jsonObject = jsonValue.toObject();
230         QJSValue jsObject = eng.toScriptValue(jsonObject);
231         QVERIFY(!jsObject.isVariant());
232         QVERIFY(jsObject.isObject());
233
234         QJSValue stringified = stringify.call(QJSValueList() << jsObject);
235         QVERIFY(!stringified.isError());
236         QCOMPARE(stringified.toString().toUtf8(), json);
237
238         QJsonObject roundtrip = qjsvalue_cast<QJsonObject>(jsObject);
239         QCOMPARE(roundtrip, jsonObject);
240     } else if (jsonValue.isArray()) {
241         QJsonArray jsonArray = jsonValue.toArray();
242         QJSValue jsArray = eng.toScriptValue(jsonArray);
243         QVERIFY(!jsArray.isVariant());
244         QVERIFY(jsArray.isArray());
245
246         QJSValue stringified = stringify.call(QJSValueList() << jsArray);
247         QVERIFY(!stringified.isError());
248         QCOMPARE(stringified.toString().toUtf8(), json);
249
250         QJsonArray roundtrip = qjsvalue_cast<QJsonArray>(jsArray);
251         QCOMPARE(roundtrip, jsonArray);
252     }
253 }
254
255 void tst_qjsonbinding::readValueProperty_data()
256 {
257     cppJsConversion_data();
258 }
259
260 void tst_qjsonbinding::readValueProperty()
261 {
262     QFETCH(QString, fileName);
263
264     QByteArray json = readAsUtf8(fileName);
265     QJsonValue jsonValue = valueFromJson(json);
266
267     QJSEngine eng;
268     JsonPropertyObject obj;
269     obj.setValue(jsonValue);
270     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
271     QJSValue stringified = eng.evaluate(
272                 "var v = obj.value; (typeof v == 'undefined') ? '' : JSON.stringify(v)");
273     QVERIFY(!stringified.isError());
274     QCOMPARE(stringified.toString().toUtf8(), json);
275 }
276
277 void tst_qjsonbinding::readObjectOrArrayProperty_data()
278 {
279     QTest::addColumn<QString>("fileName");
280
281     addObjectDataTestFiles();
282     addArrayDataTestFiles();
283 }
284
285 void tst_qjsonbinding::readObjectOrArrayProperty()
286 {
287     QFETCH(QString, fileName);
288
289     QByteArray json = readAsUtf8(fileName);
290     QJsonValue jsonValue = valueFromJson(json);
291     QVERIFY(jsonValue.isObject() || jsonValue.isArray());
292
293     QJSEngine eng;
294     JsonPropertyObject obj;
295     if (jsonValue.isObject())
296         obj.setObject(jsonValue.toObject());
297     else
298         obj.setArray(jsonValue.toArray());
299     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
300
301     QJSValue stringified = eng.evaluate(
302                 QString::fromLatin1("JSON.stringify(obj.%0)").arg(
303                     jsonValue.isObject() ? "object" : "array"));
304     QVERIFY(!stringified.isError());
305     QCOMPARE(stringified.toString().toUtf8(), json);
306 }
307
308 void tst_qjsonbinding::writeValueProperty_data()
309 {
310     readValueProperty_data();
311 }
312
313 void tst_qjsonbinding::writeValueProperty()
314 {
315     QFETCH(QString, fileName);
316
317     QByteArray json = readAsUtf8(fileName);
318     QJsonValue jsonValue = valueFromJson(json);
319
320     QJSEngine eng;
321     JsonPropertyObject obj;
322     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
323
324     QJSValue fun = eng.evaluate(
325                 "(function(json) {"
326                 "  void(obj.value = (json == '') ? undefined : JSON.parse(json));"
327                 "})");
328     QVERIFY(fun.isCallable());
329
330     QVERIFY(obj.value().isNull());
331     QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
332
333     // Workarounds for QTBUG-25164
334     if (jsonValue.isObject() && jsonValue.toObject().isEmpty())
335         QVERIFY(obj.value().isObject() && obj.value().toObject().isEmpty());
336     else if (jsonValue.isArray() && jsonValue.toArray().isEmpty())
337         QVERIFY(obj.value().isArray() && obj.value().toArray().isEmpty());
338     else
339         QCOMPARE(obj.value(), jsonValue);
340 }
341
342 void tst_qjsonbinding::writeObjectOrArrayProperty_data()
343 {
344     readObjectOrArrayProperty_data();
345 }
346
347 void tst_qjsonbinding::writeObjectOrArrayProperty()
348 {
349     QFETCH(QString, fileName);
350
351     QByteArray json = readAsUtf8(fileName);
352     QJsonValue jsonValue = valueFromJson(json);
353     QVERIFY(jsonValue.isObject() || jsonValue.isArray());
354
355     QJSEngine eng;
356     JsonPropertyObject obj;
357     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
358
359     QJSValue fun = eng.evaluate(
360                 QString::fromLatin1(
361                     "(function(json) {"
362                     "  void(obj.%0 = JSON.parse(json));"
363                     "})").arg(jsonValue.isObject() ? "object" : "array")
364                 );
365     QVERIFY(fun.isCallable());
366
367     QVERIFY(obj.object().isEmpty() && obj.array().isEmpty());
368     QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
369
370     if (jsonValue.isObject())
371         QCOMPARE(obj.object(), jsonValue.toObject());
372     else
373         QCOMPARE(obj.array(), jsonValue.toArray());
374 }
375
376 void tst_qjsonbinding::writeProperty_incompatibleType_data()
377 {
378     QTest::addColumn<QString>("property");
379     QTest::addColumn<QString>("expression");
380
381     QTest::newRow("value=function") << "value" << "(function(){})";
382
383     QTest::newRow("object=undefined") << "object" << "undefined";
384     QTest::newRow("object=null") << "object" << "null";
385     QTest::newRow("object=false") << "object" << "false";
386     QTest::newRow("object=true") << "object" << "true";
387     QTest::newRow("object=123") << "object" << "123";
388     QTest::newRow("object=42.35") << "object" << "42.35";
389     QTest::newRow("object='foo'") << "object" << "'foo'";
390     QTest::newRow("object=[]") << "object" << "[]";
391     QTest::newRow("object=function") << "object" << "(function(){})";
392
393     QTest::newRow("array=undefined") << "array" << "undefined";
394     QTest::newRow("array=null") << "array" << "null";
395     QTest::newRow("array=false") << "array" << "false";
396     QTest::newRow("array=true") << "array" << "true";
397     QTest::newRow("array=123") << "array" << "123";
398     QTest::newRow("array=42.35") << "array" << "42.35";
399     QTest::newRow("array='foo'") << "array" << "'foo'";
400     QTest::newRow("array={}") << "array" << "{}";
401     QTest::newRow("array=function") << "array" << "(function(){})";
402 }
403
404 void tst_qjsonbinding::writeProperty_incompatibleType()
405 {
406     QFETCH(QString, property);
407     QFETCH(QString, expression);
408
409     QJSEngine eng;
410     JsonPropertyObject obj;
411     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
412
413     QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1")
414                                 .arg(property).arg(expression));
415     QVERIFY(ret.isError());
416     QVERIFY(ret.toString().contains("Cannot assign"));
417 }
418
419 void tst_qjsonbinding::writeProperty_javascriptExpression_data()
420 {
421     QTest::addColumn<QString>("property");
422     QTest::addColumn<QString>("expression");
423     QTest::addColumn<QString>("expectedJson");
424
425     // Function properties should be omitted.
426     QTest::newRow("value = object with function property")
427             << "value" << "{ foo: function() {} }" << "{}";
428     QTest::newRow("object = object with function property")
429             << "object" << "{ foo: function() {} }" << "{}";
430     QTest::newRow("array = array with function property")
431             << "array" << "[function() {}]" << "[]";
432
433     // Inherited properties should not be included.
434     QTest::newRow("value = object with inherited property")
435             << "value" << "{ __proto__: { proto_foo: 123 } }"
436             << "{}";
437     QTest::newRow("value = object with inherited property 2")
438             << "value" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
439             << "{\"foo\":123}";
440     QTest::newRow("value = array with inherited property")
441             << "value" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
442             << "[]";
443     QTest::newRow("value = array with inherited property 2")
444             << "value" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
445             << "[10,20]";
446
447     QTest::newRow("object = object with inherited property")
448             << "object" << "{ __proto__: { proto_foo: 123 } }"
449             << "{}";
450     QTest::newRow("object = object with inherited property 2")
451             << "object" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
452             << "{\"foo\":123}";
453     QTest::newRow("array = array with inherited property")
454             << "array" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
455             << "[]";
456     QTest::newRow("array = array with inherited property 2")
457             << "array" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
458             << "[10,20]";
459
460     // Non-enumerable properties should be included.
461     QTest::newRow("value = object with non-enumerable property")
462             << "value" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
463             << "{\"foo\":123}";
464     QTest::newRow("object = object with non-enumerable property")
465             << "object" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
466             << "{\"foo\":123}";
467
468     // Cyclic data structures are permitted, but the cyclic links become
469     // empty objects.
470     QTest::newRow("value = cyclic object")
471             << "value" << "(function() { var o = { foo: 123 }; o.o = o; return o; })()"
472             << "{\"foo\":123,\"o\":{}}";
473     QTest::newRow("value = cyclic array")
474             << "value" << "(function() { var a = [10, 20]; a.push(a); return a; })()"
475             << "[10,20,[]]";
476     QTest::newRow("object = cyclic object")
477             << "object" << "(function() { var o = { bar: true }; o.o = o; return o; })()"
478             << "{\"bar\":true,\"o\":{}}";
479     QTest::newRow("array = cyclic array")
480             << "array" << "(function() { var a = [30, 40]; a.unshift(a); return a; })()"
481             << "[[],30,40]";
482
483     // Properties with undefined value are excluded.
484     QTest::newRow("value = { foo: undefined }")
485             << "value" << "{ foo: undefined }" << "{}";
486     QTest::newRow("value = { foo: undefined, bar: 123 }")
487             << "value" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
488     QTest::newRow("value = { foo: 456, bar: undefined }")
489             << "value" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
490
491     QTest::newRow("object = { foo: undefined }")
492             << "object" << "{ foo: undefined }" << "{}";
493     QTest::newRow("object = { foo: undefined, bar: 123 }")
494             << "object" << "{ foo: undefined, bar: 123 }" << "{\"bar\":123}";
495     QTest::newRow("object = { foo: 456, bar: undefined }")
496             << "object" << "{ foo: 456, bar: undefined }" << "{\"foo\":456}";
497
498     // QJsonArray::append() implicitly converts undefined values to null.
499     QTest::newRow("value = [undefined]")
500             << "value" << "[undefined]" << "[null]";
501     QTest::newRow("value = [undefined, 10]")
502             << "value" << "[undefined, 10]" << "[null,10]";
503     QTest::newRow("value = [10, undefined, 20]")
504             << "value" << "[10, undefined, 20]" << "[10,null,20]";
505
506     QTest::newRow("array = [undefined]")
507             << "array" << "[undefined]" << "[null]";
508     QTest::newRow("array = [undefined, 10]")
509             << "array" << "[undefined, 10]" << "[null,10]";
510     QTest::newRow("array = [10, undefined, 20]")
511             << "array" << "[10, undefined, 20]" << "[10,null,20]";
512 }
513
514 void tst_qjsonbinding::writeProperty_javascriptExpression()
515 {
516     QFETCH(QString, property);
517     QFETCH(QString, expression);
518     QFETCH(QString, expectedJson);
519
520     QJSEngine eng;
521     JsonPropertyObject obj;
522     eng.globalObject().setProperty("obj", eng.newQObject(&obj));
523
524     QJSValue ret = eng.evaluate(QString::fromLatin1("obj.%0 = %1; JSON.stringify(obj.%0)")
525                                 .arg(property).arg(expression));
526     QVERIFY(!ret.isError());
527     QCOMPARE(ret.toString(), expectedJson);
528 }
529
530 QTEST_MAIN(tst_qjsonbinding)
531
532 #include "tst_qjsonbinding.moc"