1/*
2    Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18*/
19
20#ifndef tst_qscriptvalueiterator_h
21#define tst_qscriptvalueiterator_h
22
23#include "qscriptengine.h"
24#include "qscriptvalue.h"
25#include "qscriptvalueiterator.h"
26#include <QtCore/qhash.h>
27#include <QtTest/QtTest>
28
29class tst_QScriptValueIterator : public QObject {
30    Q_OBJECT
31
32public:
33    tst_QScriptValueIterator();
34    virtual ~tst_QScriptValueIterator();
35
36private slots:
37    void iterateForward_data();
38    void iterateForward();
39    void iterateBackward_data();
40    void iterateBackward();
41    void iterateArray_data();
42    void iterateArray();
43    void iterateBackAndForth();
44    void setValue();
45    void remove();
46    void removeMixed();
47    void removeUndeletable();
48    void iterateString();
49    void assignObjectToIterator();
50};
51
52tst_QScriptValueIterator::tst_QScriptValueIterator()
53{
54}
55
56tst_QScriptValueIterator::~tst_QScriptValueIterator()
57{
58}
59
60void tst_QScriptValueIterator::iterateForward_data()
61{
62    QTest::addColumn<QStringList>("propertyNames");
63    QTest::addColumn<QStringList>("propertyValues");
64
65    QTest::newRow("no properties")
66        << QStringList() << QStringList();
67    QTest::newRow("foo=bar")
68        << (QStringList() << "foo")
69        << (QStringList() << "bar");
70    QTest::newRow("foo=bar, baz=123")
71        << (QStringList() << "foo" << "baz")
72        << (QStringList() << "bar" << "123");
73    QTest::newRow("foo=bar, baz=123, rab=oof")
74        << (QStringList() << "foo" << "baz" << "rab")
75        << (QStringList() << "bar" << "123" << "oof");
76}
77
78void tst_QScriptValueIterator::iterateForward()
79{
80    QFETCH(QStringList, propertyNames);
81    QFETCH(QStringList, propertyValues);
82    QMap<QString, QString> pmap;
83    Q_ASSERT(propertyNames.size() == propertyValues.size());
84
85    QScriptEngine engine;
86    QScriptValue object = engine.newObject();
87    for (int i = 0; i < propertyNames.size(); ++i) {
88        QString name = propertyNames.at(i);
89        QString value = propertyValues.at(i);
90        pmap.insert(name, value);
91        object.setProperty(name, QScriptValue(&engine, value));
92    }
93    QScriptValue otherObject = engine.newObject();
94    otherObject.setProperty("foo", QScriptValue(&engine, 123456));
95    otherObject.setProperty("protoProperty", QScriptValue(&engine, 654321));
96    object.setPrototype(otherObject); // should not affect iterator
97
98    QStringList lst;
99    QScriptValueIterator it(object);
100    while (!pmap.isEmpty()) {
101        QCOMPARE(it.hasNext(), true);
102        QCOMPARE(it.hasNext(), true);
103        it.next();
104        QString name = it.name();
105        QCOMPARE(pmap.contains(name), true);
106        QCOMPARE(it.name(), name);
107        QCOMPARE(it.flags(), object.propertyFlags(name));
108        QCOMPARE(it.value().strictlyEquals(QScriptValue(&engine, pmap.value(name))), true);
109        QCOMPARE(it.scriptName(), engine.toStringHandle(name));
110        pmap.remove(name);
111        lst.append(name);
112    }
113
114    QCOMPARE(it.hasNext(), false);
115    QCOMPARE(it.hasNext(), false);
116
117    it.toFront();
118    for (int i = 0; i < lst.count(); ++i) {
119        QCOMPARE(it.hasNext(), true);
120        it.next();
121        QCOMPARE(it.name(), lst.at(i));
122    }
123
124    for (int i = 0; i < lst.count(); ++i) {
125        QCOMPARE(it.hasPrevious(), true);
126        it.previous();
127        QCOMPARE(it.name(), lst.at(lst.count()-1-i));
128    }
129    QCOMPARE(it.hasPrevious(), false);
130}
131
132void tst_QScriptValueIterator::iterateBackward_data()
133{
134    iterateForward_data();
135}
136
137void tst_QScriptValueIterator::iterateBackward()
138{
139    QFETCH(QStringList, propertyNames);
140    QFETCH(QStringList, propertyValues);
141    QMap<QString, QString> pmap;
142    Q_ASSERT(propertyNames.size() == propertyValues.size());
143
144    QScriptEngine engine;
145    QScriptValue object = engine.newObject();
146    for (int i = 0; i < propertyNames.size(); ++i) {
147        QString name = propertyNames.at(i);
148        QString value = propertyValues.at(i);
149        pmap.insert(name, value);
150        object.setProperty(name, QScriptValue(&engine, value));
151    }
152
153    QStringList lst;
154    QScriptValueIterator it(object);
155    it.toBack();
156    while (!pmap.isEmpty()) {
157        QCOMPARE(it.hasPrevious(), true);
158        QCOMPARE(it.hasPrevious(), true);
159        it.previous();
160        QString name = it.name();
161        QCOMPARE(pmap.contains(name), true);
162        QCOMPARE(it.name(), name);
163        QCOMPARE(it.flags(), object.propertyFlags(name));
164        QCOMPARE(it.value().strictlyEquals(QScriptValue(&engine, pmap.value(name))), true);
165        pmap.remove(name);
166        lst.append(name);
167    }
168
169    QCOMPARE(it.hasPrevious(), false);
170    QCOMPARE(it.hasPrevious(), false);
171
172    it.toBack();
173    for (int i = 0; i < lst.count(); ++i) {
174        QCOMPARE(it.hasPrevious(), true);
175        it.previous();
176        QCOMPARE(it.name(), lst.at(i));
177    }
178
179    for (int i = 0; i < lst.count(); ++i) {
180        QCOMPARE(it.hasNext(), true);
181        it.next();
182        QCOMPARE(it.name(), lst.at(lst.count()-1-i));
183    }
184    QCOMPARE(it.hasNext(), false);
185}
186
187void tst_QScriptValueIterator::iterateArray_data()
188{
189    QTest::addColumn<QStringList>("inputPropertyNames");
190    QTest::addColumn<QStringList>("inputPropertyValues");
191    QTest::addColumn<QStringList>("propertyNames");
192    QTest::addColumn<QStringList>("propertyValues");
193    QTest::newRow("no elements") << QStringList() << QStringList() << QStringList() << QStringList();
194
195    QTest::newRow("0=foo, 1=barr")
196        << (QStringList() << "0" << "1")
197        << (QStringList() << "foo" << "bar")
198        << (QStringList() << "0" << "1")
199        << (QStringList() << "foo" << "bar");
200
201    QTest::newRow("0=foo, 3=barr")
202        << (QStringList() << "0" << "1" << "2" << "3")
203        << (QStringList() << "foo" << "" << "" << "bar")
204        << (QStringList() << "0" << "1" << "2" << "3")
205        << (QStringList() << "foo" << "" << "" << "bar");
206}
207
208void tst_QScriptValueIterator::iterateArray()
209{
210    QFETCH(QStringList, inputPropertyNames);
211    QFETCH(QStringList, inputPropertyValues);
212    QFETCH(QStringList, propertyNames);
213    QFETCH(QStringList, propertyValues);
214
215    QScriptEngine engine;
216    QScriptValue array = engine.newArray();
217    for (int i = 0; i < inputPropertyNames.size(); ++i)
218        array.setProperty(inputPropertyNames.at(i), inputPropertyValues.at(i));
219
220    int length = array.property("length").toInt32();
221    QCOMPARE(length, propertyNames.size());
222    QScriptValueIterator it(array);
223    for (int i = 0; i < length; ++i) {
224        QCOMPARE(it.hasNext(), true);
225        it.next();
226        QCOMPARE(it.name(), propertyNames.at(i));
227        QCOMPARE(it.flags(), array.propertyFlags(propertyNames.at(i)));
228        QVERIFY(it.value().strictlyEquals(array.property(propertyNames.at(i))));
229        QCOMPARE(it.value().toString(), propertyValues.at(i));
230    }
231    QVERIFY(it.hasNext());
232    it.next();
233    QCOMPARE(it.name(), QString::fromLatin1("length"));
234    QVERIFY(it.value().isNumber());
235    QCOMPARE(it.value().toInt32(), length);
236    QCOMPARE(it.flags(), QScriptValue::PropertyFlags(QScriptValue::SkipInEnumeration | QScriptValue::Undeletable));
237
238    it.previous();
239    QCOMPARE(it.hasPrevious(), length > 0);
240    for (int i = length - 1; i >= 0; --i) {
241        it.previous();
242        QCOMPARE(it.name(), propertyNames.at(i));
243        QCOMPARE(it.flags(), array.propertyFlags(propertyNames.at(i)));
244        QVERIFY(it.value().strictlyEquals(array.property(propertyNames.at(i))));
245        QCOMPARE(it.value().toString(), propertyValues.at(i));
246        QCOMPARE(it.hasPrevious(), i > 0);
247    }
248    QCOMPARE(it.hasPrevious(), false);
249
250    // hasNext() and hasPrevious() cache their result; verify that the result is in sync
251    if (length > 1) {
252        QVERIFY(it.hasNext());
253        it.next();
254        QCOMPARE(it.name(), QString::fromLatin1("0"));
255        QVERIFY(it.hasNext());
256        it.previous();
257        QCOMPARE(it.name(), QString::fromLatin1("0"));
258        QVERIFY(!it.hasPrevious());
259        it.next();
260        QCOMPARE(it.name(), QString::fromLatin1("0"));
261        QVERIFY(it.hasPrevious());
262        it.next();
263        QCOMPARE(it.name(), QString::fromLatin1("1"));
264    }
265    {
266        // same test as object:
267        QScriptValue originalArray = engine.newArray();
268        for (int i = 0; i < inputPropertyNames.size(); ++i)
269            originalArray.setProperty(inputPropertyNames.at(i), inputPropertyValues.at(i));
270
271        QScriptValue array = originalArray.toObject();
272        int length = array.property("length").toInt32();
273        QCOMPARE(length, propertyNames.size());
274        QScriptValueIterator it(array);
275        for (int i = 0; i < length; ++i) {
276            QCOMPARE(it.hasNext(), true);
277            it.next();
278            QCOMPARE(it.name(), propertyNames.at(i));
279            QCOMPARE(it.flags(), array.propertyFlags(propertyNames.at(i)));
280            QVERIFY(it.value().strictlyEquals(array.property(propertyNames.at(i))));
281            QCOMPARE(it.value().toString(), propertyValues.at(i));
282        }
283        QCOMPARE(it.hasNext(), true);
284        it.next();
285        QCOMPARE(it.name(), QString::fromLatin1("length"));
286    }
287}
288
289void tst_QScriptValueIterator::iterateBackAndForth()
290{
291    QScriptEngine engine;
292    {
293        QScriptValue object = engine.newObject();
294        object.setProperty("foo", QScriptValue(&engine, "bar"));
295        object.setProperty("rab", QScriptValue(&engine, "oof"),
296                           QScriptValue::SkipInEnumeration); // should not affect iterator
297        QScriptValueIterator it(object);
298        QVERIFY(it.hasNext());
299        it.next();
300        QCOMPARE(it.name(), QLatin1String("foo"));
301        QVERIFY(it.hasPrevious());
302        it.previous();
303        QCOMPARE(it.name(), QLatin1String("foo"));
304        QVERIFY(it.hasNext());
305        it.next();
306        QCOMPARE(it.name(), QLatin1String("foo"));
307        QVERIFY(it.hasPrevious());
308        it.previous();
309        QCOMPARE(it.name(), QLatin1String("foo"));
310        QVERIFY(it.hasNext());
311        it.next();
312        QCOMPARE(it.name(), QLatin1String("foo"));
313        QVERIFY(it.hasNext());
314        it.next();
315        QCOMPARE(it.name(), QLatin1String("rab"));
316        QVERIFY(it.hasPrevious());
317        it.previous();
318        QCOMPARE(it.name(), QLatin1String("rab"));
319        QVERIFY(it.hasNext());
320        it.next();
321        QCOMPARE(it.name(), QLatin1String("rab"));
322        QVERIFY(it.hasPrevious());
323        it.previous();
324        QCOMPARE(it.name(), QLatin1String("rab"));
325    }
326    {
327        // hasNext() and hasPrevious() cache their result; verify that the result is in sync
328        QScriptValue object = engine.newObject();
329        object.setProperty("foo", QScriptValue(&engine, "bar"));
330        object.setProperty("rab", QScriptValue(&engine, "oof"));
331        QScriptValueIterator it(object);
332        QVERIFY(it.hasNext());
333        it.next();
334        QCOMPARE(it.name(), QString::fromLatin1("foo"));
335        QVERIFY(it.hasNext());
336        it.previous();
337        QCOMPARE(it.name(), QString::fromLatin1("foo"));
338        QVERIFY(!it.hasPrevious());
339        it.next();
340        QCOMPARE(it.name(), QString::fromLatin1("foo"));
341        QVERIFY(it.hasPrevious());
342        it.next();
343        QCOMPARE(it.name(), QString::fromLatin1("rab"));
344    }
345}
346
347void tst_QScriptValueIterator::setValue()
348{
349    QScriptEngine engine;
350    QScriptValue object = engine.newObject();
351    object.setProperty("foo", QScriptValue(&engine, "bar"));
352    QScriptValueIterator it(object);
353    it.next();
354    QCOMPARE(it.name(), QLatin1String("foo"));
355    it.setValue(QScriptValue(&engine, "baz"));
356    QCOMPARE(it.value().strictlyEquals(QScriptValue(&engine, QLatin1String("baz"))), true);
357    QCOMPARE(object.property("foo").toString(), QLatin1String("baz"));
358    it.setValue(QScriptValue(&engine, "zab"));
359    QCOMPARE(it.value().strictlyEquals(QScriptValue(&engine, QLatin1String("zab"))), true);
360    QCOMPARE(object.property("foo").toString(), QLatin1String("zab"));
361}
362
363void tst_QScriptValueIterator::remove()
364{
365    QScriptEngine engine;
366    QScriptValue object = engine.newObject();
367    object.setProperty("foo", QScriptValue(&engine, "bar"),
368                       QScriptValue::SkipInEnumeration); // should not affect iterator
369    object.setProperty("rab", QScriptValue(&engine, "oof"));
370    QScriptValueIterator it(object);
371    it.next();
372    QCOMPARE(it.name(), QLatin1String("foo"));
373    it.remove();
374    QCOMPARE(it.hasPrevious(), false);
375    QCOMPARE(object.property("foo").isValid(), false);
376    QCOMPARE(object.property("rab").toString(), QLatin1String("oof"));
377    it.next();
378    QCOMPARE(it.name(), QLatin1String("rab"));
379    QCOMPARE(it.value().toString(), QLatin1String("oof"));
380    QCOMPARE(it.hasNext(), false);
381    it.remove();
382    QCOMPARE(object.property("rab").isValid(), false);
383    QCOMPARE(it.hasPrevious(), false);
384    QCOMPARE(it.hasNext(), false);
385}
386
387void tst_QScriptValueIterator::removeMixed()
388{
389    // This test checks if QScriptValueIterator behaives correctly if an object's property got deleted
390    // in different way.
391    QScriptEngine engine;
392    QScriptValue object = engine.evaluate("o = new Object; o");
393    object.setProperty("a", QScriptValue(124), QScriptValue::SkipInEnumeration);
394    object.setProperty("b", QScriptValue(816));
395    object.setProperty("c", QScriptValue(3264));
396    QScriptValueIterator it(object);
397    it.next();
398    it.next();
399    QCOMPARE(it.name(), QLatin1String("b"));
400    QCOMPARE(it.hasPrevious(), true);
401    QCOMPARE(it.hasNext(), true);
402    // Remove 'a'
403    object.setProperty("a", QScriptValue());
404    QEXPECT_FAIL("", "That would be a significant behavioral and performance change, new QtScript API should be developed (QTBUG-12087)", Abort);
405    QCOMPARE(it.hasPrevious(), false);
406    QCOMPARE(it.hasNext(), true);
407    // Remove 'c'
408    engine.evaluate("delete o.c");
409    QCOMPARE(it.hasPrevious(), false);
410    QCOMPARE(it.hasNext(), false);
411    // Remove 'b'
412    object.setProperty("b", QScriptValue());
413    QCOMPARE(it.hasPrevious(), false);
414    QCOMPARE(it.hasNext(), false);
415    QCOMPARE(it.name(), QString());
416    QCOMPARE(it.value().toString(), QString());
417
418    // Try to remove a removed property.
419    it.remove();
420    QCOMPARE(it.hasPrevious(), false);
421    QCOMPARE(it.hasNext(), false);
422    QCOMPARE(it.name(), QString());
423    QCOMPARE(it.value().toString(), QString());
424
425    for (int i = 0; i < 2; ++i) {
426        it.next();
427        QCOMPARE(it.hasPrevious(), false);
428        QCOMPARE(it.hasNext(), false);
429        QCOMPARE(it.name(), QString());
430        QCOMPARE(it.value().toString(), QString());
431    }
432
433    for (int i = 0; i < 2; ++i) {
434        it.previous();
435        QCOMPARE(it.hasPrevious(), false);
436        QCOMPARE(it.hasNext(), false);
437        QCOMPARE(it.name(), QString());
438        QCOMPARE(it.value().toString(), QString());
439    }
440}
441
442void tst_QScriptValueIterator::removeUndeletable()
443{
444    // Undeletable property can't be deleted via iterator.
445    QScriptEngine engine;
446    QScriptValue object = engine.evaluate("o = new Object; o");
447    object.setProperty("a", QScriptValue(&engine, 124));
448    object.setProperty("b", QScriptValue(&engine, 816), QScriptValue::Undeletable);
449    QVERIFY(object.property("b").isValid());
450    QScriptValueIterator it(object);
451    it.next();
452    it.next();
453    it.remove();
454    it.toFront();
455    QVERIFY(it.hasNext());
456    QVERIFY(object.property("b").isValid());
457}
458
459void tst_QScriptValueIterator::iterateString()
460{
461    QScriptEngine engine;
462    QScriptValue str = QScriptValue(&engine, QString::fromLatin1("ciao"));
463    QVERIFY(str.isString());
464    QScriptValue obj = str.toObject();
465    int length = obj.property("length").toInt32();
466    QCOMPARE(length, 4);
467    QScriptValueIterator it(obj);
468    for (int i = 0; i < length; ++i) {
469        QCOMPARE(it.hasNext(), true);
470        QString indexStr = QScriptValue(&engine, i).toString();
471        it.next();
472        QCOMPARE(it.name(), indexStr);
473        QCOMPARE(it.flags(), obj.propertyFlags(indexStr));
474        QCOMPARE(it.value().strictlyEquals(obj.property(indexStr)), true);
475    }
476    QVERIFY(it.hasNext());
477    it.next();
478    QCOMPARE(it.name(), QString::fromLatin1("length"));
479    QVERIFY(it.value().isNumber());
480    QCOMPARE(it.value().toInt32(), length);
481    QCOMPARE(it.flags(), QScriptValue::PropertyFlags(QScriptValue::ReadOnly | QScriptValue::SkipInEnumeration | QScriptValue::Undeletable));
482
483    it.previous();
484    QCOMPARE(it.hasPrevious(), length > 0);
485    for (int i = length - 1; i >= 0; --i) {
486        it.previous();
487        QString indexStr = QScriptValue(&engine, i).toString();
488        QCOMPARE(it.name(), indexStr);
489        QCOMPARE(it.flags(), obj.propertyFlags(indexStr));
490        QCOMPARE(it.value().strictlyEquals(obj.property(indexStr)), true);
491        QCOMPARE(it.hasPrevious(), i > 0);
492    }
493    QCOMPARE(it.hasPrevious(), false);
494}
495
496void tst_QScriptValueIterator::assignObjectToIterator()
497{
498    QScriptEngine eng;
499    QScriptValue obj1 = eng.newObject();
500    obj1.setProperty("foo", 123);
501    QScriptValue obj2 = eng.newObject();
502    obj2.setProperty("bar", 456);
503
504    QScriptValueIterator it(obj1);
505    QVERIFY(it.hasNext());
506    it.next();
507    it = obj2;
508    QVERIFY(it.hasNext());
509    it.next();
510    QCOMPARE(it.name(), QString::fromLatin1("bar"));
511
512    it = obj1;
513    QVERIFY(it.hasNext());
514    it.next();
515    QCOMPARE(it.name(), QString::fromLatin1("foo"));
516
517    it = obj2;
518    QVERIFY(it.hasNext());
519    it.next();
520    QCOMPARE(it.name(), QString::fromLatin1("bar"));
521
522    it = obj2;
523    QVERIFY(it.hasNext());
524    it.next();
525    QCOMPARE(it.name(), QString::fromLatin1("bar"));
526}
527
528QTEST_MAIN(tst_QScriptValueIterator)
529#include "tst_qscriptvalueiterator.moc"
530
531#endif // tst_qscriptvalueiterator_h
532