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
36class tst_QJSValueIterator : public QObject
37{
38 Q_OBJECT
39
40public:
41 tst_QJSValueIterator();
42 virtual ~tst_QJSValueIterator();
43
44private 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
59tst_QJSValueIterator::tst_QJSValueIterator()
60{
61}
62
63tst_QJSValueIterator::~tst_QJSValueIterator()
64{
65}
66
67void 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
85void 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
130void 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
147void 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
268void 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?
325static 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
332static QJSValue myGetter(QScriptContext *ctx, QJSEngine *)
333{
334 return ctx->thisObject().property("bar");
335}
336
337static QJSValue mySetter(QScriptContext *ctx, QJSEngine *)
338{
339 ctx->thisObject().setProperty("bar", ctx->argument(0));
340 return ctx->argument(0);
341}
342
343void 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
429void 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
461void 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
473void 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
507void 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
533QTEST_MAIN(tst_QJSValueIterator)
534#include "tst_qjsvalueiterator.moc"
535

source code of qtdeclarative/tests/auto/qml/qjsvalueiterator/tst_qjsvalueiterator.cpp