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