1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
41 #include <QtTest/QtTest>
42 #include <QtQml/QtQml>
43 #include "../../shared/util.h"
45 Q_DECLARE_METATYPE(QJsonValue::Type)
47 class JsonPropertyObject : public QObject
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)
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; }
67 class tst_qjsonbinding : public QQmlDataTest
74 void cppJsConversion_data();
75 void cppJsConversion();
77 void readValueProperty_data();
78 void readValueProperty();
79 void readObjectOrArrayProperty_data();
80 void readObjectOrArrayProperty();
82 void writeValueProperty_data();
83 void writeValueProperty();
84 void writeObjectOrArrayProperty_data();
85 void writeObjectOrArrayProperty();
87 void writeProperty_incompatibleType_data();
88 void writeProperty_incompatibleType();
90 void writeProperty_javascriptExpression_data();
91 void writeProperty_javascriptExpression();
94 QByteArray readAsUtf8(const QString &fileName);
95 static QJsonValue valueFromJson(const QByteArray &json);
97 void addPrimitiveDataTestFiles();
98 void addObjectDataTestFiles();
99 void addArrayDataTestFiles();
102 QByteArray tst_qjsonbinding::readAsUtf8(const QString &fileName)
104 QFile file(testFile(fileName));
105 file.open(QIODevice::ReadOnly);
106 QTextStream stream(&file);
107 return stream.readAll().trimmed().toUtf8();
110 QJsonValue tst_qjsonbinding::valueFromJson(const QByteArray &json)
113 return QJsonValue(QJsonValue::Undefined);
115 QJsonDocument doc = QJsonDocument::fromJson(json);
117 return doc.isObject() ? QJsonValue(doc.object()) : QJsonValue(doc.array());
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");
127 void tst_qjsonbinding::addPrimitiveDataTestFiles()
129 QTest::newRow("true") << "true.json";
130 QTest::newRow("false") << "false.json";
132 QTest::newRow("null") << "null.json";
134 QTest::newRow("number.0") << "number.0.json";
135 QTest::newRow("number.1") << "number.1.json";
137 QTest::newRow("string.0") << "string.0.json";
139 QTest::newRow("undefined") << "empty.json";
142 void tst_qjsonbinding::addObjectDataTestFiles()
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";
151 void tst_qjsonbinding::addArrayDataTestFiles()
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";
160 void tst_qjsonbinding::cppJsConversion_data()
162 QTest::addColumn<QString>("fileName");
164 addPrimitiveDataTestFiles();
165 addObjectDataTestFiles();
166 addArrayDataTestFiles();
169 void tst_qjsonbinding::cppJsConversion()
171 QFETCH(QString, fileName);
173 QByteArray json = readAsUtf8(fileName);
174 QJsonValue jsonValue = valueFromJson(json);
177 QJSValue stringify = eng.globalObject().property("JSON").property("stringify");
178 QVERIFY(stringify.isCallable());
181 QJSValue jsValue = eng.toScriptValue(jsonValue);
182 QVERIFY(!jsValue.isVariant());
183 switch (jsonValue.type()) {
184 case QJsonValue::Null:
185 QVERIFY(jsValue.isNull());
187 case QJsonValue::Bool:
188 QVERIFY(jsValue.isBool());
189 QCOMPARE(jsValue.toBool(), jsonValue.toBool());
191 case QJsonValue::Double:
192 QVERIFY(jsValue.isNumber());
193 QCOMPARE(jsValue.toNumber(), jsonValue.toDouble());
195 case QJsonValue::String:
196 QVERIFY(jsValue.isString());
197 QCOMPARE(jsValue.toString(), jsonValue.toString());
199 case QJsonValue::Array:
200 QVERIFY(jsValue.isArray());
202 case QJsonValue::Object:
203 QVERIFY(jsValue.isObject());
205 case QJsonValue::Undefined:
206 QVERIFY(jsValue.isUndefined());
210 if (jsValue.isUndefined()) {
211 QVERIFY(json.isEmpty());
213 QJSValue stringified = stringify.call(QJSValueList() << jsValue);
214 QVERIFY(!stringified.isError());
215 QCOMPARE(stringified.toString().toUtf8(), json);
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());
225 QCOMPARE(roundtrip, jsonValue);
228 if (jsonValue.isObject()) {
229 QJsonObject jsonObject = jsonValue.toObject();
230 QJSValue jsObject = eng.toScriptValue(jsonObject);
231 QVERIFY(!jsObject.isVariant());
232 QVERIFY(jsObject.isObject());
234 QJSValue stringified = stringify.call(QJSValueList() << jsObject);
235 QVERIFY(!stringified.isError());
236 QCOMPARE(stringified.toString().toUtf8(), json);
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());
246 QJSValue stringified = stringify.call(QJSValueList() << jsArray);
247 QVERIFY(!stringified.isError());
248 QCOMPARE(stringified.toString().toUtf8(), json);
250 QJsonArray roundtrip = qjsvalue_cast<QJsonArray>(jsArray);
251 QCOMPARE(roundtrip, jsonArray);
255 void tst_qjsonbinding::readValueProperty_data()
257 cppJsConversion_data();
260 void tst_qjsonbinding::readValueProperty()
262 QFETCH(QString, fileName);
264 QByteArray json = readAsUtf8(fileName);
265 QJsonValue jsonValue = valueFromJson(json);
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);
277 void tst_qjsonbinding::readObjectOrArrayProperty_data()
279 QTest::addColumn<QString>("fileName");
281 addObjectDataTestFiles();
282 addArrayDataTestFiles();
285 void tst_qjsonbinding::readObjectOrArrayProperty()
287 QFETCH(QString, fileName);
289 QByteArray json = readAsUtf8(fileName);
290 QJsonValue jsonValue = valueFromJson(json);
291 QVERIFY(jsonValue.isObject() || jsonValue.isArray());
294 JsonPropertyObject obj;
295 if (jsonValue.isObject())
296 obj.setObject(jsonValue.toObject());
298 obj.setArray(jsonValue.toArray());
299 eng.globalObject().setProperty("obj", eng.newQObject(&obj));
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);
308 void tst_qjsonbinding::writeValueProperty_data()
310 readValueProperty_data();
313 void tst_qjsonbinding::writeValueProperty()
315 QFETCH(QString, fileName);
317 QByteArray json = readAsUtf8(fileName);
318 QJsonValue jsonValue = valueFromJson(json);
321 JsonPropertyObject obj;
322 eng.globalObject().setProperty("obj", eng.newQObject(&obj));
324 QJSValue fun = eng.evaluate(
326 " void(obj.value = (json == '') ? undefined : JSON.parse(json));"
328 QVERIFY(fun.isCallable());
330 QVERIFY(obj.value().isNull());
331 QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
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());
339 QCOMPARE(obj.value(), jsonValue);
342 void tst_qjsonbinding::writeObjectOrArrayProperty_data()
344 readObjectOrArrayProperty_data();
347 void tst_qjsonbinding::writeObjectOrArrayProperty()
349 QFETCH(QString, fileName);
351 QByteArray json = readAsUtf8(fileName);
352 QJsonValue jsonValue = valueFromJson(json);
353 QVERIFY(jsonValue.isObject() || jsonValue.isArray());
356 JsonPropertyObject obj;
357 eng.globalObject().setProperty("obj", eng.newQObject(&obj));
359 QJSValue fun = eng.evaluate(
362 " void(obj.%0 = JSON.parse(json));"
363 "})").arg(jsonValue.isObject() ? "object" : "array")
365 QVERIFY(fun.isCallable());
367 QVERIFY(obj.object().isEmpty() && obj.array().isEmpty());
368 QVERIFY(fun.call(QJSValueList() << QString::fromUtf8(json)).isUndefined());
370 if (jsonValue.isObject())
371 QCOMPARE(obj.object(), jsonValue.toObject());
373 QCOMPARE(obj.array(), jsonValue.toArray());
376 void tst_qjsonbinding::writeProperty_incompatibleType_data()
378 QTest::addColumn<QString>("property");
379 QTest::addColumn<QString>("expression");
381 QTest::newRow("value=function") << "value" << "(function(){})";
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(){})";
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(){})";
404 void tst_qjsonbinding::writeProperty_incompatibleType()
406 QFETCH(QString, property);
407 QFETCH(QString, expression);
410 JsonPropertyObject obj;
411 eng.globalObject().setProperty("obj", eng.newQObject(&obj));
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"));
419 void tst_qjsonbinding::writeProperty_javascriptExpression_data()
421 QTest::addColumn<QString>("property");
422 QTest::addColumn<QString>("expression");
423 QTest::addColumn<QString>("expectedJson");
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() {}]" << "[]";
433 // Inherited properties should not be included.
434 QTest::newRow("value = object with inherited property")
435 << "value" << "{ __proto__: { proto_foo: 123 } }"
437 QTest::newRow("value = object with inherited property 2")
438 << "value" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
440 QTest::newRow("value = array with inherited property")
441 << "value" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
443 QTest::newRow("value = array with inherited property 2")
444 << "value" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
447 QTest::newRow("object = object with inherited property")
448 << "object" << "{ __proto__: { proto_foo: 123 } }"
450 QTest::newRow("object = object with inherited property 2")
451 << "object" << "{ foo: 123, __proto__: { proto_foo: 456 } }"
453 QTest::newRow("array = array with inherited property")
454 << "array" << "(function() { var a = []; a.__proto__ = { proto_foo: 123 }; return a; })()"
456 QTest::newRow("array = array with inherited property 2")
457 << "array" << "(function() { var a = [10, 20]; a.__proto__ = { proto_foo: 123 }; return a; })()"
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 })"
464 QTest::newRow("object = object with non-enumerable property")
465 << "object" << "Object.defineProperty({}, 'foo', { value: 123, enumerable: false })"
468 // Cyclic data structures are permitted, but the cyclic links become
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; })()"
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; })()"
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}";
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}";
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]";
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]";
514 void tst_qjsonbinding::writeProperty_javascriptExpression()
516 QFETCH(QString, property);
517 QFETCH(QString, expression);
518 QFETCH(QString, expectedJson);
521 JsonPropertyObject obj;
522 eng.globalObject().setProperty("obj", eng.newQObject(&obj));
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);
530 QTEST_MAIN(tst_qjsonbinding)
532 #include "tst_qjsonbinding.moc"