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 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | |
32 | #include <QJSEngine> |
33 | #include <QJSValue> |
34 | #include <QJSValueIterator> |
35 | |
36 | class tst_QJSValueIterator : public QObject |
37 | { |
38 | Q_OBJECT |
39 | |
40 | public: |
41 | tst_QJSValueIterator(); |
42 | virtual ~tst_QJSValueIterator(); |
43 | |
44 | private slots: |
45 | void iterateForward_data(); |
46 | void iterateForward(); |
47 | void iterateArray_data(); |
48 | void iterateArray(); |
49 | void iterateString(); |
50 | #if 0 |
51 | void iterateGetterSetter(); |
52 | #endif |
53 | void assignObjectToIterator(); |
54 | void iterateNonObject(); |
55 | void iterateOverObjectFromDeletedEngine(); |
56 | void iterateWithNext(); |
57 | }; |
58 | |
59 | tst_QJSValueIterator::tst_QJSValueIterator() |
60 | { |
61 | } |
62 | |
63 | tst_QJSValueIterator::~tst_QJSValueIterator() |
64 | { |
65 | } |
66 | |
67 | void tst_QJSValueIterator::iterateForward_data() |
68 | { |
69 | QTest::addColumn<QStringList>(name: "propertyNames" ); |
70 | QTest::addColumn<QStringList>(name: "propertyValues" ); |
71 | |
72 | QTest::newRow(dataTag: "no properties" ) |
73 | << QStringList() << QStringList(); |
74 | QTest::newRow(dataTag: "foo=bar" ) |
75 | << (QStringList() << "foo" ) |
76 | << (QStringList() << "bar" ); |
77 | QTest::newRow(dataTag: "foo=bar, baz=123" ) |
78 | << (QStringList() << "foo" << "baz" ) |
79 | << (QStringList() << "bar" << "123" ); |
80 | QTest::newRow(dataTag: "foo=bar, baz=123, rab=oof" ) |
81 | << (QStringList() << "foo" << "baz" << "rab" ) |
82 | << (QStringList() << "bar" << "123" << "oof" ); |
83 | } |
84 | |
85 | void tst_QJSValueIterator::iterateForward() |
86 | { |
87 | QFETCH(QStringList, propertyNames); |
88 | QFETCH(QStringList, propertyValues); |
89 | QMap<QString, QString> pmap; |
90 | QCOMPARE(propertyNames.size(), propertyValues.size()); |
91 | |
92 | QJSEngine engine; |
93 | QJSValue object = engine.newObject(); |
94 | for (int i = 0; i < propertyNames.size(); ++i) { |
95 | QString name = propertyNames.at(i); |
96 | QString value = propertyValues.at(i); |
97 | pmap.insert(akey: name, avalue: value); |
98 | object.setProperty(name, value: engine.toScriptValue(value)); |
99 | } |
100 | QJSValue otherObject = engine.newObject(); |
101 | otherObject.setProperty(name: "foo" , value: engine.toScriptValue(value: 123456)); |
102 | otherObject.setProperty(name: "protoProperty" , value: engine.toScriptValue(value: 654321)); |
103 | object.setPrototype(otherObject); // should not affect iterator |
104 | |
105 | QStringList lst; |
106 | QJSValueIterator it(object); |
107 | while (!pmap.isEmpty()) { |
108 | QCOMPARE(it.hasNext(), true); |
109 | QCOMPARE(it.hasNext(), true); |
110 | it.next(); |
111 | QString name = it.name(); |
112 | QCOMPARE(pmap.contains(name), true); |
113 | QCOMPARE(it.name(), name); |
114 | QCOMPARE(it.value().strictlyEquals(engine.toScriptValue(pmap.value(name))), true); |
115 | pmap.remove(akey: name); |
116 | lst.append(t: name); |
117 | } |
118 | |
119 | QCOMPARE(it.hasNext(), false); |
120 | QCOMPARE(it.hasNext(), false); |
121 | |
122 | it = object; |
123 | for (int i = 0; i < lst.count(); ++i) { |
124 | QCOMPARE(it.hasNext(), true); |
125 | it.next(); |
126 | QCOMPARE(it.name(), lst.at(i)); |
127 | } |
128 | } |
129 | |
130 | void tst_QJSValueIterator::iterateArray_data() |
131 | { |
132 | QTest::addColumn<QStringList>(name: "propertyNames" ); |
133 | QTest::addColumn<QStringList>(name: "propertyValues" ); |
134 | |
135 | QTest::newRow(dataTag: "no elements" ) << QStringList() << QStringList(); |
136 | |
137 | QTest::newRow(dataTag: "0=foo, 1=barr" ) |
138 | << (QStringList() << "0" << "1" ) |
139 | << (QStringList() << "foo" << "bar" ); |
140 | |
141 | |
142 | QTest::newRow(dataTag: "0=foo, 3=barr" ) |
143 | << (QStringList() << "0" << "1" << "2" << "3" ) |
144 | << (QStringList() << "foo" << "" << "" << "bar" ); |
145 | } |
146 | |
147 | void tst_QJSValueIterator::iterateArray() |
148 | { |
149 | QFETCH(QStringList, propertyNames); |
150 | QFETCH(QStringList, propertyValues); |
151 | |
152 | QJSEngine engine; |
153 | QJSValue array = engine.newArray(); |
154 | |
155 | // Fill the array |
156 | for (int i = 0; i < propertyNames.size(); ++i) { |
157 | array.setProperty(name: propertyNames.at(i), value: propertyValues.at(i)); |
158 | } |
159 | |
160 | // Iterate thru array properties. Note that the QJSValueIterator doesn't guarantee |
161 | // any order on the iteration! |
162 | int length = array.property(name: "length" ).toInt(); |
163 | QCOMPARE(length, propertyNames.size()); |
164 | |
165 | bool iteratedThruLength = false; |
166 | QHash<QString, QJSValue> arrayProperties; |
167 | QJSValueIterator it(array); |
168 | |
169 | // Iterate forward |
170 | while (it.hasNext()) { |
171 | it.next(); |
172 | |
173 | const QString name = it.name(); |
174 | if (name == QString::fromLatin1(str: "length" )) { |
175 | QVERIFY(it.value().isNumber()); |
176 | QCOMPARE(it.value().toInt(), length); |
177 | QVERIFY2(!iteratedThruLength, "'length' appeared more than once during iteration." ); |
178 | iteratedThruLength = true; |
179 | continue; |
180 | } |
181 | |
182 | // Storing the properties we iterate in a hash to compare with test data. |
183 | QVERIFY2(!arrayProperties.contains(name), "property appeared more than once during iteration." ); |
184 | arrayProperties.insert(akey: name, avalue: it.value()); |
185 | QVERIFY(it.value().strictlyEquals(array.property(name))); |
186 | } |
187 | |
188 | // Verify properties |
189 | QVERIFY(iteratedThruLength); |
190 | QCOMPARE(arrayProperties.size(), propertyNames.size()); |
191 | for (int i = 0; i < propertyNames.size(); ++i) { |
192 | QVERIFY(arrayProperties.contains(propertyNames.at(i))); |
193 | QCOMPARE(arrayProperties.value(propertyNames.at(i)).toString(), propertyValues.at(i)); |
194 | } |
195 | |
196 | #if 0 |
197 | |
198 | // Iterate backwards |
199 | arrayProperties.clear(); |
200 | iteratedThruLength = false; |
201 | it.toBack(); |
202 | |
203 | while (it.hasPrevious()) { |
204 | it.previous(); |
205 | |
206 | const QString name = it.name(); |
207 | if (name == QString::fromLatin1("length" )) { |
208 | QVERIFY(it.value().isNumber()); |
209 | QCOMPARE(it.value().toInt(), length); |
210 | QCOMPARE(it.flags(), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable); |
211 | QVERIFY2(!iteratedThruLength, "'length' appeared more than once during iteration." ); |
212 | iteratedThruLength = true; |
213 | continue; |
214 | } |
215 | |
216 | // Storing the properties we iterate in a hash to compare with test data. |
217 | QVERIFY2(!arrayProperties.contains(name), "property appeared more than once during iteration." ); |
218 | arrayProperties.insert(name, it.value()); |
219 | QCOMPARE(it.flags(), array.propertyFlags(name)); |
220 | QVERIFY(it.value().strictlyEquals(array.property(name))); |
221 | } |
222 | |
223 | // Verify properties |
224 | QVERIFY(iteratedThruLength); |
225 | QCOMPARE(arrayProperties.size(), propertyNames.size()); |
226 | for (int i = 0; i < propertyNames.size(); ++i) { |
227 | QVERIFY(arrayProperties.contains(propertyNames.at(i))); |
228 | QCOMPARE(arrayProperties.value(propertyNames.at(i)).toString(), propertyValues.at(i)); |
229 | } |
230 | |
231 | // ### Do we still need this test? |
232 | // Forward test again but as object |
233 | arrayProperties.clear(); |
234 | iteratedThruLength = false; |
235 | QJSValue arrayObject = engine.toObject(array); |
236 | QJSValueIterator it2(arrayObject); |
237 | |
238 | while (it2.hasNext()) { |
239 | it2.next(); |
240 | |
241 | const QString name = it2.name(); |
242 | if (name == QString::fromLatin1("length" )) { |
243 | QVERIFY(it2.value().isNumber()); |
244 | QCOMPARE(it2.value().toInt(), length); |
245 | QCOMPARE(it2.flags(), QScriptValue::SkipInEnumeration | QScriptValue::Undeletable); |
246 | QVERIFY2(!iteratedThruLength, "'length' appeared more than once during iteration." ); |
247 | iteratedThruLength = true; |
248 | continue; |
249 | } |
250 | |
251 | // Storing the properties we iterate in a hash to compare with test data. |
252 | QVERIFY2(!arrayProperties.contains(name), "property appeared more than once during iteration." ); |
253 | arrayProperties.insert(name, it2.value()); |
254 | QCOMPARE(it2.flags(), arrayObject.propertyFlags(name)); |
255 | QVERIFY(it2.value().strictlyEquals(arrayObject.property(name))); |
256 | } |
257 | |
258 | // Verify properties |
259 | QVERIFY(iteratedThruLength); |
260 | QCOMPARE(arrayProperties.size(), propertyNames.size()); |
261 | for (int i = 0; i < propertyNames.size(); ++i) { |
262 | QVERIFY(arrayProperties.contains(propertyNames.at(i))); |
263 | QCOMPARE(arrayProperties.value(propertyNames.at(i)).toString(), propertyValues.at(i)); |
264 | } |
265 | #endif |
266 | } |
267 | |
268 | void tst_QJSValueIterator::iterateString() |
269 | { |
270 | QJSEngine engine; |
271 | QJSValue obj = engine.evaluate(program: "new String('ciao')" ); |
272 | QVERIFY(obj.property("length" ).isNumber()); |
273 | int length = obj.property(name: "length" ).toInt(); |
274 | QCOMPARE(length, 4); |
275 | |
276 | QJSValueIterator it(obj); |
277 | QHash<QString, QJSValue> stringProperties; |
278 | bool iteratedThruLength = false; |
279 | |
280 | while (it.hasNext()) { |
281 | it.next(); |
282 | const QString name = it.name(); |
283 | |
284 | if (name == QString::fromLatin1(str: "length" )) { |
285 | QVERIFY(it.value().isNumber()); |
286 | QCOMPARE(it.value().toInt(), length); |
287 | QVERIFY2(!iteratedThruLength, "'length' appeared more than once during iteration." ); |
288 | iteratedThruLength = true; |
289 | continue; |
290 | } |
291 | |
292 | QVERIFY2(!stringProperties.contains(name), "property appeared more than once during iteration." ); |
293 | stringProperties.insert(akey: name, avalue: it.value()); |
294 | QVERIFY(it.value().strictlyEquals(obj.property(name))); |
295 | } |
296 | |
297 | QVERIFY(iteratedThruLength); |
298 | QCOMPARE(stringProperties.size(), length); |
299 | #if 0 |
300 | // And going backwards |
301 | iteratedThruLength = false; |
302 | stringProperties.clear(); |
303 | it.toBack(); |
304 | |
305 | while (it.hasPrevious()) { |
306 | it.previous(); |
307 | const QString name = it.name(); |
308 | |
309 | if (name == QString::fromLatin1("length" )) { |
310 | QVERIFY(it.value().isNumber()); |
311 | QCOMPARE(it.value().toInt(), length); |
312 | QVERIFY2(!iteratedThruLength, "'length' appeared more than once during iteration." ); |
313 | iteratedThruLength = true; |
314 | continue; |
315 | } |
316 | |
317 | QVERIFY2(!stringProperties.contains(name), "property appeared more than once during iteration." ); |
318 | stringProperties.insert(name, it.value()); |
319 | QVERIFY(it.value().strictlyEquals(obj.property(name))); |
320 | } |
321 | #endif |
322 | } |
323 | |
324 | #if 0 // FIXME what we should to keep from here? |
325 | static QJSValue myGetterSetter(QScriptContext *ctx, QJSEngine *) |
326 | { |
327 | if (ctx->argumentCount() == 1) |
328 | ctx->thisObject().setProperty("bar" , ctx->argument(0)); |
329 | return ctx->thisObject().property("bar" ); |
330 | } |
331 | |
332 | static QJSValue myGetter(QScriptContext *ctx, QJSEngine *) |
333 | { |
334 | return ctx->thisObject().property("bar" ); |
335 | } |
336 | |
337 | static QJSValue mySetter(QScriptContext *ctx, QJSEngine *) |
338 | { |
339 | ctx->thisObject().setProperty("bar" , ctx->argument(0)); |
340 | return ctx->argument(0); |
341 | } |
342 | |
343 | void tst_QJSValueIterator::iterateGetterSetter() |
344 | { |
345 | // unified getter/setter function |
346 | { |
347 | QJSEngine eng; |
348 | QJSValue obj = eng.newObject(); |
349 | obj.setProperty("foo" , eng.newFunction(myGetterSetter), |
350 | QScriptValue::PropertyGetter | QScriptValue::PropertySetter); |
351 | QJSValue val(&eng, 123); |
352 | obj.setProperty("foo" , val); |
353 | QVERIFY(obj.property("bar" ).strictlyEquals(val)); |
354 | QVERIFY(obj.property("foo" ).strictlyEquals(val)); |
355 | |
356 | QJSValueIterator it(obj); |
357 | QVERIFY(it.hasNext()); |
358 | it.next(); |
359 | QCOMPARE(it.name(), QString::fromLatin1("foo" )); |
360 | QCOMPARE(it.flags(), QScriptValue::PropertyFlags(QScriptValue::PropertyGetter | QScriptValue::PropertySetter)); |
361 | QVERIFY(it.value().strictlyEquals(val)); |
362 | QJSValue val2(&eng, 456); |
363 | it.setValue(val2); |
364 | QVERIFY(obj.property("bar" ).strictlyEquals(val2)); |
365 | QVERIFY(obj.property("foo" ).strictlyEquals(val2)); |
366 | |
367 | QVERIFY(it.hasNext()); |
368 | it.next(); |
369 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
370 | QVERIFY(!it.hasNext()); |
371 | |
372 | QVERIFY(it.hasPrevious()); |
373 | it.previous(); |
374 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
375 | QVERIFY(it.hasPrevious()); |
376 | it.previous(); |
377 | QCOMPARE(it.name(), QString::fromLatin1("foo" )); |
378 | QCOMPARE(it.flags(), QScriptValue::PropertyFlags(QScriptValue::PropertyGetter | QScriptValue::PropertySetter)); |
379 | QVERIFY(it.value().strictlyEquals(val2)); |
380 | it.setValue(val); |
381 | QVERIFY(obj.property("bar" ).strictlyEquals(val)); |
382 | QVERIFY(obj.property("foo" ).strictlyEquals(val)); |
383 | } |
384 | // separate getter/setter function |
385 | for (int x = 0; x < 2; ++x) { |
386 | QJSEngine eng; |
387 | QJSValue obj = eng.newObject(); |
388 | if (x == 0) { |
389 | obj.setProperty("foo" , eng.newFunction(myGetter), QScriptValue::PropertyGetter); |
390 | obj.setProperty("foo" , eng.newFunction(mySetter), QScriptValue::PropertySetter); |
391 | } else { |
392 | obj.setProperty("foo" , eng.newFunction(mySetter), QScriptValue::PropertySetter); |
393 | obj.setProperty("foo" , eng.newFunction(myGetter), QScriptValue::PropertyGetter); |
394 | } |
395 | QJSValue val(&eng, 123); |
396 | obj.setProperty("foo" , val); |
397 | QVERIFY(obj.property("bar" ).strictlyEquals(val)); |
398 | QVERIFY(obj.property("foo" ).strictlyEquals(val)); |
399 | |
400 | QJSValueIterator it(obj); |
401 | QVERIFY(it.hasNext()); |
402 | it.next(); |
403 | QCOMPARE(it.name(), QString::fromLatin1("foo" )); |
404 | QVERIFY(it.value().strictlyEquals(val)); |
405 | QJSValue val2(&eng, 456); |
406 | it.setValue(val2); |
407 | QVERIFY(obj.property("bar" ).strictlyEquals(val2)); |
408 | QVERIFY(obj.property("foo" ).strictlyEquals(val2)); |
409 | |
410 | QVERIFY(it.hasNext()); |
411 | it.next(); |
412 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
413 | QVERIFY(!it.hasNext()); |
414 | |
415 | QVERIFY(it.hasPrevious()); |
416 | it.previous(); |
417 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
418 | QVERIFY(it.hasPrevious()); |
419 | it.previous(); |
420 | QCOMPARE(it.name(), QString::fromLatin1("foo" )); |
421 | QVERIFY(it.value().strictlyEquals(val2)); |
422 | it.setValue(val); |
423 | QVERIFY(obj.property("bar" ).strictlyEquals(val)); |
424 | QVERIFY(obj.property("foo" ).strictlyEquals(val)); |
425 | } |
426 | } |
427 | #endif |
428 | |
429 | void tst_QJSValueIterator::assignObjectToIterator() |
430 | { |
431 | QJSEngine eng; |
432 | QJSValue obj1 = eng.newObject(); |
433 | obj1.setProperty(name: "foo" , value: 123); |
434 | QJSValue obj2 = eng.newObject(); |
435 | obj2.setProperty(name: "bar" , value: 456); |
436 | |
437 | QJSValueIterator it(obj1); |
438 | QVERIFY(it.hasNext()); |
439 | it.next(); |
440 | it = obj2; |
441 | QVERIFY(it.hasNext()); |
442 | it.next(); |
443 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
444 | |
445 | it = obj1; |
446 | QVERIFY(it.hasNext()); |
447 | it.next(); |
448 | QCOMPARE(it.name(), QString::fromLatin1("foo" )); |
449 | |
450 | it = obj2; |
451 | QVERIFY(it.hasNext()); |
452 | it.next(); |
453 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
454 | |
455 | it = obj2; |
456 | QVERIFY(it.hasNext()); |
457 | it.next(); |
458 | QCOMPARE(it.name(), QString::fromLatin1("bar" )); |
459 | } |
460 | |
461 | void tst_QJSValueIterator::iterateNonObject() |
462 | { |
463 | QJSValueIterator it(123); |
464 | QVERIFY(!it.hasNext()); |
465 | it.next(); |
466 | it.name(); |
467 | it.value(); |
468 | QJSValue num(5); |
469 | it = num; |
470 | QVERIFY(!it.hasNext()); |
471 | } |
472 | |
473 | void tst_QJSValueIterator::iterateOverObjectFromDeletedEngine() |
474 | { |
475 | QJSEngine *engine = new QJSEngine; |
476 | QJSValue objet = engine->newObject(); |
477 | |
478 | // populate object with properties |
479 | QHash<QString, int> properties; |
480 | properties.insert(akey: "foo" ,avalue: 1235); |
481 | properties.insert(akey: "oof" ,avalue: 5321); |
482 | properties.insert(akey: "ofo" ,avalue: 3521); |
483 | QHash<QString, int>::const_iterator i = properties.constBegin(); |
484 | for (; i != properties.constEnd(); ++i) { |
485 | objet.setProperty(name: i.key(), value: i.value()); |
486 | } |
487 | |
488 | // start iterating |
489 | QJSValueIterator it(objet); |
490 | it.next(); |
491 | QVERIFY(properties.contains(it.name())); |
492 | |
493 | delete engine; |
494 | |
495 | QVERIFY(objet.isUndefined()); |
496 | QVERIFY(it.name().isEmpty()); |
497 | QVERIFY(it.value().isUndefined()); |
498 | |
499 | QVERIFY(!it.hasNext()); |
500 | it.next(); |
501 | |
502 | QVERIFY(it.name().isEmpty()); |
503 | QVERIFY(it.value().isUndefined()); |
504 | |
505 | } |
506 | |
507 | void tst_QJSValueIterator::iterateWithNext() |
508 | { |
509 | QJSEngine engine; |
510 | QJSValue value = engine.newObject(); |
511 | value.setProperty(name: "one" , value: 1); |
512 | value.setProperty(name: "two" , value: 2); |
513 | value.setProperty(name: "three" , value: 3); |
514 | |
515 | QStringList list; |
516 | list << QStringLiteral("one" ) << QStringLiteral("three" ) << QStringLiteral("two" ); |
517 | |
518 | int counter = 0; |
519 | QJSValueIterator it(value); |
520 | QStringList actualList; |
521 | while (it.next()) { |
522 | ++counter; |
523 | actualList << it.name(); |
524 | } |
525 | |
526 | std::sort(first: actualList.begin(), last: actualList.end()); |
527 | |
528 | QCOMPARE(counter, 3); |
529 | QCOMPARE(list, actualList); |
530 | |
531 | } |
532 | |
533 | QTEST_MAIN(tst_QJSValueIterator) |
534 | #include "tst_qjsvalueiterator.moc" |
535 | |