f83a1b3efd5e145dd5d7bec76fb528a575670d32
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qjsengine / tst_qjsengine.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42
43 #include <QtTest/QtTest>
44
45 #include <qjsengine.h>
46 #include <qjsvalueiterator.h>
47 #include <qgraphicsitem.h>
48 #include <qstandarditemmodel.h>
49 #include <QtCore/qnumeric.h>
50 #include <stdlib.h>
51
52 Q_DECLARE_METATYPE(QList<int>)
53 Q_DECLARE_METATYPE(QObjectList)
54
55 //TESTED_CLASS=
56 //TESTED_FILES=
57
58 // The JavaScriptCore GC marks the C stack. To try to ensure that there is
59 // no JSObject* left in stack memory by the compiler, we call this function
60 // to zap some bytes of memory before calling collectGarbage().
61 static void zapSomeStack()
62 {
63     char buf[4096];
64     memset(buf, 0, sizeof(buf));
65 }
66
67 static void collectGarbage_helper(QJSEngine &eng)
68 {
69     zapSomeStack();
70     eng.collectGarbage();
71 }
72
73 class tst_QJSEngine : public QObject
74 {
75     Q_OBJECT
76
77 public:
78     tst_QJSEngine();
79     virtual ~tst_QJSEngine();
80
81 private slots:
82     void constructWithParent();
83 #if 0 // FIXME: no QScriptContext
84     void currentContext();
85     void pushPopContext();
86 #endif
87 #if 0 // FIXME: No prototype API in QScriptEngine
88     void getSetDefaultPrototype_int();
89     void getSetDefaultPrototype_customType();
90 #endif
91 #if 0 // FIXME: no QScriptContext
92     void newFunction();
93     void newFunctionWithArg();
94     void newFunctionWithProto();
95 #endif
96     void newObject();
97     void newArray();
98     void newArray_HooliganTask218092();
99     void newArray_HooliganTask233836();
100     void newVariant();
101 #if 0 // FIXME: No prototype API in QScriptEngine
102     void newVariant_defaultPrototype();
103 #endif
104 #if 0 // ###FIXME: No QVariant object promotion API
105     void newVariant_promoteObject();
106     void newVariant_replaceValue();
107 #endif
108     void newVariant_valueOfToString();
109 #if 0 // ###FIXME: No QVariant object promotion API
110     void newVariant_promoteNonObject();
111     void newVariant_promoteNonQScriptObject();
112 #endif
113     void newRegExp();
114     void jsRegExp();
115     void newDate();
116     void jsParseDate();
117     void newQObject();
118     void newQObject_ownership();
119     void newQObject_promoteObject();
120     void newQObject_sameQObject();
121 #if 0 // FIXME: No prototype API in QScriptEngine
122     void newQObject_defaultPrototype();
123 #endif
124     void newQObject_promoteNonObject();
125     void newQObject_promoteNonQScriptObject();
126 #if 0 // ### FIXME: No QScript Metaobject support right now
127     void newQMetaObject();
128     void newActivationObject();
129 #endif
130 #if 0 // ###FIXME: No setGlobalObject support - yay
131     void getSetGlobalObjectSimple();
132     void getSetGlobalObject();
133 #endif
134     void globalObjectProperties();
135     void globalObjectEquals();
136     void globalObjectProperties_enumerate();
137     void createGlobalObjectProperty();
138     void globalObjectGetterSetterProperty();
139 #if 0 // ###FIXME: No support for setting the global object
140     void customGlobalObjectWithPrototype();
141 #endif
142     void globalObjectWithCustomPrototype();
143     void builtinFunctionNames_data();
144     void builtinFunctionNames();
145 #if 0 // ###FIXME: No syntax checking result
146     void checkSyntax_data();
147     void checkSyntax();
148 #endif
149 #if 0 // ###FIXME: No support for canEvaluate
150     void canEvaluate_data();
151     void canEvaluate();
152 #endif
153     void evaluate_data();
154     void evaluate();
155 #if 0 // ###FIXME: no support for c-style callbacks
156     void nestedEvaluate();
157 #endif
158 #if 0 // ### FIXME: No c-style callbacks
159     void uncaughtException();
160 #endif
161     void errorMessage_QT679();
162     void valueConversion_basic();
163 #if 0 // FIXME: No API for custom types
164     void valueConversion_customType();
165     void valueConversion_sequence();
166 #endif
167     void valueConversion_QVariant();
168 #if 0 // FIXME: No support for custom types
169     void valueConversion_hooliganTask248802();
170 #endif
171     void valueConversion_basic2();
172     void valueConversion_dateTime();
173     void valueConversion_regExp();
174 #if 0 // FIXME: No qScriptValueFromValue
175     void qScriptValueFromValue_noEngine();
176 #endif
177 #if 0 // ###FIXME: No QScriptContext
178     void importExtension();
179     void infiniteRecursion();
180 #endif
181 #if 0 // FIXME: No support for default prototypes
182     void castWithPrototypeChain();
183 #endif
184     void castWithMultipleInheritance();
185     void collectGarbage();
186 #if 0 // ###FIXME: no reportAdditionalMemoryCost API
187     void reportAdditionalMemoryCost();
188 #endif
189     void gcWithNestedDataStructure();
190 #if 0 // ###FIXME: No processEvents handling
191     void processEventsWhileRunning();
192     void processEventsWhileRunning_function();
193     void throwErrorFromProcessEvents_data();
194     void throwErrorFromProcessEvents();
195     void disableProcessEventsInterval();
196 #endif
197     void stacktrace();
198     void numberParsing_data();
199     void numberParsing();
200     void automaticSemicolonInsertion();
201 #if 0 // ###FIXME: no abortEvaluation API
202     void abortEvaluation_notEvaluating();
203     void abortEvaluation_data();
204     void abortEvaluation();
205     void abortEvaluation_tryCatch();
206     void abortEvaluation_fromNative();
207     void abortEvaluation_QTBUG9433();
208 #endif
209 #if 0 // ###FIXME: no QScriptEngine::isEvaluating
210     void isEvaluating_notEvaluating();
211     void isEvaluating_fromNative();
212     void isEvaluating_fromEvent();
213 #endif
214 #if 0 // ###FIXME: depracated
215     void printFunctionWithCustomHandler();
216     void printThrowsException();
217 #endif
218     void errorConstructors();
219     void argumentsProperty_globalContext();
220     void argumentsProperty_JS();
221 #if 0 // ###FIXME: no QScriptContext API
222     void argumentsProperty_evaluateInNativeFunction();
223 #endif
224     void jsNumberClass();
225     void jsForInStatement_simple();
226     void jsForInStatement_prototypeProperties();
227     void jsForInStatement_mutateWhileIterating();
228     void jsForInStatement_arrays();
229     void jsForInStatement_nullAndUndefined();
230     void jsFunctionDeclarationAsStatement();
231     void stringObjects();
232     void jsStringPrototypeReplaceBugs();
233     void getterSetterThisObject_global();
234     void getterSetterThisObject_plain();
235     void getterSetterThisObject_prototypeChain();
236 #if 0 // ###FIXME: no QScriptContext API
237     void getterSetterThisObject_activation();
238 #endif
239     void jsContinueInSwitch();
240     void jsShadowReadOnlyPrototypeProperty();
241     void toObject();
242     void jsReservedWords_data();
243     void jsReservedWords();
244     void jsFutureReservedWords_data();
245     void jsFutureReservedWords();
246     void jsThrowInsideWithStatement();
247 #if 0 // ###FIXME: No QScriptEngineAgent API
248     void getSetAgent_ownership();
249     void getSetAgent_deleteAgent();
250     void getSetAgent_differentEngine();
251 #endif
252 #if 0 // ###FIXME: No QScriptString API
253     void reentrancy_stringHandles();
254 #endif
255 #if 0 // ###FIXME: No processEventsInterval API
256     void reentrancy_processEventsInterval();
257 #endif
258 #if 0 // FIXME: No support for custom types
259     void reentrancy_typeConversion();
260 #endif
261     void reentrancy_globalObjectProperties();
262     void reentrancy_Array();
263     void reentrancy_objectCreation();
264     void jsIncDecNonObjectProperty();
265 #if 0 // ###FIXME: no installTranslatorFunctions API
266     void installTranslatorFunctions();
267     void translateScript_data();
268     void translateScript();
269     void translateScript_crossScript();
270     void translateScript_callQsTrFromNative();
271     void translateScript_trNoOp();
272     void translateScript_callQsTrFromCpp();
273     void translateWithInvalidArgs_data();
274     void translateWithInvalidArgs();
275     void translationContext_data();
276     void translationContext();
277     void translateScriptIdBased();
278     void translateScriptUnicode_data();
279     void translateScriptUnicode();
280     void translateScriptUnicodeIdBased_data();
281     void translateScriptUnicodeIdBased();
282     void translateFromBuiltinCallback();
283 #endif
284 #if 0 // ###FIXME: No QScriptValue::scope API
285     void functionScopes();
286 #endif
287 #if 0 // ###FIXME: No QScriptContext API
288     void nativeFunctionScopes();
289 #endif
290 #if 0 // ###FIXME: No QScriptProgram API
291     void evaluateProgram();
292     void evaluateProgram_customScope();
293     void evaluateProgram_closure();
294     void evaluateProgram_executeLater();
295     void evaluateProgram_multipleEngines();
296     void evaluateProgram_empty();
297 #endif
298 #if 0 // ###FIXME: No QScriptContext API
299     void collectGarbageAfterConnect();
300     void collectGarbageAfterNativeArguments();
301     void promoteThisObjectToQObjectInConstructor();
302 #endif
303 #if 0 // ###FIXME: No QScript MetaObject API
304     void scriptValueFromQMetaObject();
305 #endif
306
307     void qRegExpInport_data();
308     void qRegExpInport();
309 #if 0 // ###FIXME: No QScriptContext API
310     void reentrency();
311 #endif
312 #if 0 // ###FIXME: No QSCriptDeclarativeClass API
313     void newFixedStaticScopeObject();
314     void newGrowingStaticScopeObject();
315 #endif
316     void dateRoundtripJSQtJS();
317     void dateRoundtripQtJSQt();
318     void dateConversionJSQt();
319     void dateConversionQtJS();
320     void functionPrototypeExtensions();
321 };
322
323 tst_QJSEngine::tst_QJSEngine()
324 {
325 }
326
327 tst_QJSEngine::~tst_QJSEngine()
328 {
329 }
330
331 void tst_QJSEngine::constructWithParent()
332 {
333     QPointer<QJSEngine> ptr;
334     {
335         QObject obj;
336         QJSEngine *engine = new QJSEngine(&obj);
337         ptr = engine;
338     }
339     QVERIFY(ptr == 0);
340 }
341
342 #if 0 // FIXME: no QScriptContext
343 void tst_QJSEngine::currentContext()
344 {
345     QScriptEngine eng;
346     QScriptContext *globalCtx = eng.currentContext();
347     QVERIFY(globalCtx != 0);
348     QVERIFY(globalCtx->parentContext() == 0);
349     QCOMPARE(globalCtx->engine(), &eng);
350     QCOMPARE(globalCtx->argumentCount(), 0);
351     QCOMPARE(globalCtx->backtrace().size(), 1);
352     QVERIFY(!globalCtx->isCalledAsConstructor());
353     QVERIFY(!globalCtx->callee().isValid());
354     QCOMPARE(globalCtx->state(), QScriptContext::NormalState);
355     QVERIFY(globalCtx->thisObject().strictlyEquals(eng.globalObject()));
356     QVERIFY(globalCtx->activationObject().strictlyEquals(eng.globalObject()));
357     QVERIFY(globalCtx->argumentsObject().isObject());
358 }
359
360 void tst_QJSEngine::pushPopContext()
361 {
362     QScriptEngine eng;
363     QScriptContext *globalCtx = eng.currentContext();
364     QScriptContext *ctx = eng.pushContext();
365     QVERIFY(ctx != 0);
366     QCOMPARE(ctx->parentContext(), globalCtx);
367     QVERIFY(!ctx->isCalledAsConstructor());
368     QVERIFY(!ctx->callee().isValid());
369     QVERIFY(ctx->thisObject().strictlyEquals(eng.globalObject()));
370     QCOMPARE(ctx->argumentCount(), 0);
371     QCOMPARE(ctx->backtrace().size(), 2);
372     QCOMPARE(ctx->engine(), &eng);
373     QCOMPARE(ctx->state(), QScriptContext::NormalState);
374     QVERIFY(ctx->activationObject().isObject());
375     QVERIFY(ctx->argumentsObject().isObject());
376
377     QScriptContext *ctx2 = eng.pushContext();
378     QVERIFY(ctx2 != 0);
379     QCOMPARE(ctx2->parentContext(), ctx);
380     QVERIFY(!ctx2->activationObject().strictlyEquals(ctx->activationObject()));
381     QVERIFY(!ctx2->argumentsObject().strictlyEquals(ctx->argumentsObject()));
382
383     eng.popContext();
384     eng.popContext();
385     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
386     eng.popContext(); // ignored
387     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::popContext() doesn't match with pushContext()");
388     eng.popContext(); // ignored
389 }
390
391 static QScriptValue myFunction(QScriptContext *, QScriptEngine *eng)
392 {
393     return eng->nullValue();
394 }
395
396 static QScriptValue myFunctionWithVoidArg(QScriptContext *, QScriptEngine *eng, void *)
397 {
398     return eng->nullValue();
399 }
400
401 static QScriptValue myThrowingFunction(QScriptContext *ctx, QScriptEngine *)
402 {
403     return ctx->throwError("foo");
404 }
405
406 static QScriptValue myFunctionThatReturns(QScriptContext *, QScriptEngine *eng)
407 {
408     return QScriptValue(eng, 42);
409 }
410
411 static QScriptValue myFunctionThatReturnsWithoutEngine(QScriptContext *, QScriptEngine *)
412 {
413     return QScriptValue(1024);
414 }
415
416 static QScriptValue myFunctionThatReturnsWrongEngine(QScriptContext *, QScriptEngine *, void *arg)
417 {
418     QScriptEngine* wrongEngine = reinterpret_cast<QScriptEngine*>(arg);
419     return QScriptValue(wrongEngine, 42);
420 }
421
422 static QScriptValue sumFunction(QScriptContext *context, QScriptEngine *engine)
423 {
424     int sum = 0;
425
426     for (int i = 0; i < context->argumentCount(); i++) {
427         QScriptValue n = context->argument(i);
428         if (n.isNumber())
429             sum += n.toInteger();
430     }
431
432     return QScriptValue(engine, sum);
433 }
434
435 void tst_QJSEngine::newFunction()
436 {
437     QScriptEngine eng;
438     {
439         QScriptValue fun = eng.newFunction(myFunction);
440         QCOMPARE(fun.isValid(), true);
441         QCOMPARE(fun.isFunction(), true);
442         QCOMPARE(fun.isObject(), true);
443         QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
444         // a prototype property is automatically constructed
445         {
446             QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
447             QVERIFY(prot.isObject());
448             QVERIFY(prot.property("constructor").strictlyEquals(fun));
449             QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
450             QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
451         }
452         // prototype should be Function.prototype
453         QCOMPARE(fun.prototype().isValid(), true);
454         QCOMPARE(fun.prototype().isFunction(), true);
455         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
456
457         QCOMPARE(fun.call().isNull(), true);
458         QCOMPARE(fun.construct().isObject(), true);
459     }
460 }
461
462 void tst_QJSEngine::newFunctionWithArg()
463 {
464     QScriptEngine eng;
465     {
466         QScriptValue fun = eng.newFunction(myFunctionWithVoidArg, (void*)this);
467         QVERIFY(fun.isFunction());
468         QCOMPARE(fun.scriptClass(), (QScriptClass*)0);
469         // a prototype property is automatically constructed
470         {
471             QScriptValue prot = fun.property("prototype", QScriptValue::ResolveLocal);
472             QVERIFY(prot.isObject());
473             QVERIFY(prot.property("constructor").strictlyEquals(fun));
474             QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
475             QCOMPARE(prot.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
476         }
477         // prototype should be Function.prototype
478         QCOMPARE(fun.prototype().isValid(), true);
479         QCOMPARE(fun.prototype().isFunction(), true);
480         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
481
482         QCOMPARE(fun.call().isNull(), true);
483         QCOMPARE(fun.construct().isObject(), true);
484     }
485 }
486
487 void tst_QJSEngine::newFunctionWithProto()
488 {
489     QScriptEngine eng;
490     {
491         QScriptValue proto = eng.newObject();
492         QScriptValue fun = eng.newFunction(myFunction, proto);
493         QCOMPARE(fun.isValid(), true);
494         QCOMPARE(fun.isFunction(), true);
495         QCOMPARE(fun.isObject(), true);
496         // internal prototype should be Function.prototype
497         QCOMPARE(fun.prototype().isValid(), true);
498         QCOMPARE(fun.prototype().isFunction(), true);
499         QCOMPARE(fun.prototype().strictlyEquals(eng.evaluate("Function.prototype")), true);
500         // public prototype should be the one we passed
501         QCOMPARE(fun.property("prototype").strictlyEquals(proto), true);
502         QCOMPARE(fun.propertyFlags("prototype"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
503         QCOMPARE(proto.property("constructor").strictlyEquals(fun), true);
504         QCOMPARE(proto.propertyFlags("constructor"), QScriptValue::SkipInEnumeration);
505
506         QCOMPARE(fun.call().isNull(), true);
507         QCOMPARE(fun.construct().isObject(), true);
508     }
509     // whether the return value is correct
510     {
511         QScriptValue fun = eng.newFunction(myFunctionThatReturns);
512         QCOMPARE(fun.isValid(), true);
513         QCOMPARE(fun.isFunction(), true);
514         QCOMPARE(fun.isObject(), true);
515
516         QScriptValue result = fun.call();
517         QCOMPARE(result.isNumber(), true);
518         QCOMPARE(result.toInt32(), 42);
519     }
520     // whether the return value is assigned to the correct engine
521     {
522         QScriptValue fun = eng.newFunction(myFunctionThatReturnsWithoutEngine);
523         QCOMPARE(fun.isValid(), true);
524         QCOMPARE(fun.isFunction(), true);
525         QCOMPARE(fun.isObject(), true);
526
527         QScriptValue result = fun.call();
528         QCOMPARE(result.engine(), &eng);
529         QCOMPARE(result.isNumber(), true);
530         QCOMPARE(result.toInt32(), 1024);
531     }
532     // whether the return value is undefined when returning a value with wrong engine
533     {
534         QScriptEngine wrongEngine;
535
536         QScriptValue fun = eng.newFunction(myFunctionThatReturnsWrongEngine, reinterpret_cast<void *>(&wrongEngine));
537         QCOMPARE(fun.isValid(), true);
538         QCOMPARE(fun.isFunction(), true);
539         QCOMPARE(fun.isObject(), true);
540
541         QTest::ignoreMessage(QtWarningMsg, "QScriptValue::call(): Value from different engine returned from native function, returning undefined value instead.");
542         QScriptValue result = fun.call();
543         QCOMPARE(result.isValid(), true);
544         QCOMPARE(result.isUndefined(), true);
545     }
546     // checking if arguments are passed correctly
547     {
548         QScriptEngine wrongEngine;
549
550         QScriptValue fun = eng.newFunction(sumFunction);
551         QCOMPARE(fun.isValid(), true);
552         QCOMPARE(fun.isFunction(), true);
553         QCOMPARE(fun.isObject(), true);
554
555         QScriptValue result = fun.call();
556         QCOMPARE(result.isNumber(), true);
557         QCOMPARE(result.toInt32(), 0);
558
559         result = fun.call(QScriptValue(), QScriptValueList() << 1);
560         QCOMPARE(result.isNumber(), true);
561         QCOMPARE(result.toInt32(), 1);
562
563         result = fun.call(QScriptValue(), QScriptValueList() << 1 << 2 << 3);
564         QCOMPARE(result.isNumber(), true);
565         QCOMPARE(result.toInt32(), 6);
566
567         result = fun.call(QScriptValue(), QScriptValueList() << 1 << 2 << 3 << 4);
568         QCOMPARE(result.isNumber(), true);
569         QCOMPARE(result.toInt32(), 10);
570     }
571 }
572 #endif
573
574 void tst_QJSEngine::newObject()
575 {
576     QJSEngine eng;
577     QJSValue object = eng.newObject();
578     QCOMPARE(object.isValid(), true);
579     QCOMPARE(object.isObject(), true);
580     QCOMPARE(object.isFunction(), false);
581 // ###FIXME: No QScriptClass    QCOMPARE(object.scriptClass(), (QScriptClass*)0);
582     // prototype should be Object.prototype
583     QCOMPARE(object.prototype().isValid(), true);
584     QCOMPARE(object.prototype().isObject(), true);
585     QCOMPARE(object.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
586 }
587
588 void tst_QJSEngine::newArray()
589 {
590     QJSEngine eng;
591     QJSValue array = eng.newArray();
592     QCOMPARE(array.isValid(), true);
593     QCOMPARE(array.isArray(), true);
594     QCOMPARE(array.isObject(), true);
595     QVERIFY(!array.isFunction());
596 // ###FIXME: No QScriptClass    QCOMPARE(array.scriptClass(), (QScriptClass*)0);
597     // prototype should be Array.prototype
598     QCOMPARE(array.prototype().isValid(), true);
599     QCOMPARE(array.prototype().isArray(), true);
600     QCOMPARE(array.prototype().strictlyEquals(eng.evaluate("Array.prototype")), true);
601 }
602
603 void tst_QJSEngine::newArray_HooliganTask218092()
604 {
605     QJSEngine eng;
606     {
607         QJSValue ret = eng.evaluate("[].splice(0, 0, 'a')");
608         QVERIFY(ret.isArray());
609         QCOMPARE(ret.property("length").toInt32(), 0);
610     }
611     {
612         QJSValue ret = eng.evaluate("['a'].splice(0, 1, 'b')");
613         QVERIFY(ret.isArray());
614         QCOMPARE(ret.property("length").toInt32(), 1);
615     }
616     {
617         QJSValue ret = eng.evaluate("['a', 'b'].splice(0, 1, 'c')");
618         QVERIFY(ret.isArray());
619         QCOMPARE(ret.property("length").toInt32(), 1);
620     }
621     {
622         QJSValue ret = eng.evaluate("['a', 'b', 'c'].splice(0, 2, 'd')");
623         QVERIFY(ret.isArray());
624         QCOMPARE(ret.property("length").toInt32(), 2);
625     }
626     {
627         QJSValue ret = eng.evaluate("['a', 'b', 'c'].splice(1, 2, 'd', 'e', 'f')");
628         QVERIFY(ret.isArray());
629         QCOMPARE(ret.property("length").toInt32(), 2);
630     }
631 }
632
633 void tst_QJSEngine::newArray_HooliganTask233836()
634 {
635     QJSEngine eng;
636     {
637         // According to ECMA-262, this should cause a RangeError.
638         QJSValue ret = eng.evaluate("a = new Array(4294967295); a.push('foo')");
639         QVERIFY(ret.isError() && ret.toString().contains(QLatin1String("RangeError")));
640     }
641     {
642         QJSValue ret = eng.newArray(0xFFFFFFFF);
643         QEXPECT_FAIL("", "The maximum length of arrays is defined by v8 currently and differs from QtScript", Abort);
644         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
645         ret.setProperty(0xFFFFFFFF, 123);
646         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
647         QVERIFY(ret.property(0xFFFFFFFF).isNumber());
648         QCOMPARE(ret.property(0xFFFFFFFF).toInt32(), 123);
649         ret.setProperty(123, 456);
650         QCOMPARE(ret.property("length").toUInt32(), uint(0xFFFFFFFF));
651         QVERIFY(ret.property(123).isNumber());
652         QCOMPARE(ret.property(123).toInt32(), 456);
653     }
654 }
655
656 void tst_QJSEngine::newVariant()
657 {
658     QJSEngine eng;
659     {
660         QJSValue opaque = eng.newVariant(QVariant());
661         QCOMPARE(opaque.isValid(), true);
662         QCOMPARE(opaque.isVariant(), true);
663         QVERIFY(!opaque.isFunction());
664         QCOMPARE(opaque.isObject(), true);
665         QCOMPARE(opaque.prototype().isValid(), true);
666         QEXPECT_FAIL("", "FIXME: newly created QObject's prototype is an JS Object", Continue);
667         QCOMPARE(opaque.prototype().isVariant(), true);
668         QVERIFY(opaque.property("valueOf").call(opaque).isUndefined());
669     }
670 }
671
672 #if 0 // FIXME: No prototype API in QScriptEngine
673 void tst_QJSEngine::newVariant_defaultPrototype()
674 {
675     // default prototype should be set automatically
676     QScriptEngine eng;
677     {
678         QScriptValue proto = eng.newObject();
679         eng.setDefaultPrototype(qMetaTypeId<QString>(), proto);
680         QScriptValue ret = eng.newVariant(QVariant(QString::fromLatin1("hello")));
681         QVERIFY(ret.isVariant());
682 // ###FIXME: No QScriptClass        QCOMPARE(ret.scriptClass(), (QScriptClass*)0);
683         QVERIFY(ret.prototype().strictlyEquals(proto));
684         eng.setDefaultPrototype(qMetaTypeId<QString>(), QScriptValue());
685         QScriptValue ret2 = eng.newVariant(QVariant(QString::fromLatin1("hello")));
686         QVERIFY(ret2.isVariant());
687         QVERIFY(!ret2.prototype().strictlyEquals(proto));
688     }
689 }
690 #endif
691
692 #if 0 // ###FIXME: No QVariant object promotion API
693 void tst_QJSEngine::newVariant_promoteObject()
694 {
695     // "promote" plain object to variant
696     QScriptEngine eng;
697     {
698         QScriptValue object = eng.newObject();
699         object.setProperty("foo", eng.newObject());
700         object.setProperty("bar", object.property("foo"));
701         QVERIFY(object.property("foo").isObject());
702         QVERIFY(!object.property("foo").isVariant());
703         QScriptValue originalProto = object.property("foo").prototype();
704         QSKIP("It is not possible to promote plain object to a wrapper");
705         QScriptValue ret = eng.newVariant(object.property("foo"), QVariant(123));
706         QVERIFY(ret.isValid());
707         QVERIFY(ret.strictlyEquals(object.property("foo")));
708         QVERIFY(ret.isVariant());
709         QVERIFY(object.property("foo").isVariant());
710         QVERIFY(object.property("bar").isVariant());
711         QCOMPARE(ret.toVariant(), QVariant(123));
712         QVERIFY(ret.prototype().strictlyEquals(originalProto));
713     }
714 }
715
716 void tst_QJSEngine::newVariant_replaceValue()
717 {
718     // replace value of existing object
719     QScriptEngine eng;
720     {
721         QScriptValue object = eng.newVariant(QVariant(123));
722         for (int x = 0; x < 2; ++x) {
723             QScriptValue ret = eng.newVariant(object, QVariant(456));
724             QVERIFY(ret.isValid());
725             QVERIFY(ret.strictlyEquals(object));
726             QVERIFY(ret.isVariant());
727             QCOMPARE(ret.toVariant(), QVariant(456));
728         }
729     }
730 }
731 #endif
732
733 void tst_QJSEngine::newVariant_valueOfToString()
734 {
735     // valueOf() and toString()
736     QJSEngine eng;
737     {
738         QJSValue object = eng.newVariant(QVariant(123));
739         QJSValue value = object.property("valueOf").call(object);
740         QVERIFY(value.isNumber());
741         QCOMPARE(value.toInt32(), 123);
742         QCOMPARE(object.toString(), QString::fromLatin1("123"));
743         QCOMPARE(object.toVariant().toString(), object.toString());
744     }
745     {
746         QJSValue object = eng.newVariant(QVariant(QString::fromLatin1("hello")));
747         QJSValue value = object.property("valueOf").call(object);
748         QVERIFY(value.isString());
749         QCOMPARE(value.toString(), QString::fromLatin1("hello"));
750         QCOMPARE(object.toString(), QString::fromLatin1("hello"));
751         QCOMPARE(object.toVariant().toString(), object.toString());
752     }
753     {
754         QJSValue object = eng.newVariant(QVariant(false));
755         QJSValue value = object.property("valueOf").call(object);
756         QVERIFY(value.isBoolean());
757         QCOMPARE(value.toBoolean(), false);
758         QCOMPARE(object.toString(), QString::fromLatin1("false"));
759         QCOMPARE(object.toVariant().toString(), object.toString());
760     }
761     {
762         QJSValue object = eng.newVariant(QVariant(QPoint(10, 20)));
763         QJSValue value = object.property("valueOf").call(object);
764         QVERIFY(value.isObject());
765         QVERIFY(value.strictlyEquals(object));
766         QCOMPARE(object.toString(), QString::fromLatin1("QVariant(QPoint)"));
767     }
768 }
769
770 #if 0 // ###FIXME: No QVariant object promotion API
771 void tst_QJSEngine::newVariant_promoteNonObject()
772 {
773     QScriptEngine eng;
774     {
775         QVariant var(456);
776         QScriptValue ret = eng.newVariant(123, var);
777         QVERIFY(ret.isVariant());
778         QCOMPARE(ret.toVariant(), var);
779     }
780 }
781
782 void tst_QJSEngine::newVariant_promoteNonQScriptObject()
783 {
784     QSKIP("This test relay on limitation of QtScript JSC implementation");
785     QScriptEngine eng;
786     {
787         QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newVariant(): changing class of non-QScriptObject not supported");
788         QScriptValue ret = eng.newVariant(eng.newArray(), 123);
789         QVERIFY(!ret.isValid());
790     }
791 }
792 #endif
793
794 void tst_QJSEngine::newRegExp()
795 {
796     QSKIP("Test failing - QTBUG-22238", SkipAll);
797     QJSEngine eng;
798     for (int x = 0; x < 2; ++x) {
799         QJSValue rexp;
800         if (x == 0)
801             rexp = eng.newRegExp("foo", "bar");
802         else
803             rexp = eng.newRegExp(QRegExp("foo"));
804         QCOMPARE(rexp.isValid(), true);
805         QCOMPARE(rexp.isRegExp(), true);
806         QCOMPARE(rexp.isObject(), true);
807         QVERIFY(rexp.isFunction()); // in JSC, RegExp objects are callable
808         // prototype should be RegExp.prototype
809         QCOMPARE(rexp.prototype().isValid(), true);
810         QCOMPARE(rexp.prototype().isObject(), true);
811         QCOMPARE(rexp.prototype().isRegExp(), false);
812         QCOMPARE(rexp.prototype().strictlyEquals(eng.evaluate("RegExp.prototype")), true);
813
814         QCOMPARE(rexp.toRegExp().pattern(), QRegExp("foo").pattern());
815     }
816 }
817
818 void tst_QJSEngine::jsRegExp()
819 {
820     QSKIP("Test failing - QTBUG-22238", SkipAll);
821
822     // See ECMA-262 Section 15.10, "RegExp Objects".
823     // These should really be JS-only tests, as they test the implementation's
824     // ECMA-compliance, not the C++ API. Compliance should already be covered
825     // by the Mozilla tests (qscriptjstestsuite).
826     // We can consider updating the expected results of this test if the
827     // RegExp implementation changes.
828
829     QJSEngine eng;
830     QJSValue r = eng.evaluate("/foo/gim");
831     QVERIFY(r.isRegExp());
832     QCOMPARE(r.toString(), QString::fromLatin1("/foo/gim"));
833
834     QJSValue rxCtor = eng.globalObject().property("RegExp");
835     QJSValue r2 = rxCtor.call(QJSValue(), QJSValueList() << r);
836     QVERIFY(r2.isRegExp());
837     QVERIFY(r2.strictlyEquals(r));
838
839     QJSValue r3 = rxCtor.call(QJSValue(), QJSValueList() << r << "gim");
840     QVERIFY(r3.isError());
841     QVERIFY(r3.toString().contains(QString::fromLatin1("TypeError"))); // Cannot supply flags when constructing one RegExp from another
842
843     QJSValue r4 = rxCtor.call(QJSValue(), QJSValueList() << "foo" << "gim");
844     QVERIFY(r4.isRegExp());
845
846     QJSValue r5 = rxCtor.construct(QJSValueList() << r);
847     QVERIFY(r5.isRegExp());
848     QCOMPARE(r5.toString(), QString::fromLatin1("/foo/gim"));
849     // In JSC, constructing a RegExp from another produces the same identical object.
850     // This is different from SpiderMonkey and old back-end.
851     QVERIFY(!r5.strictlyEquals(r));
852
853     QEXPECT_FAIL("", "V8 and jsc ignores invalid flags", Continue); //https://bugs.webkit.org/show_bug.cgi?id=41614
854     QJSValue r6 = rxCtor.construct(QJSValueList() << "foo" << "bar");
855     QVERIFY(r6.isError());
856     // QVERIFY(r6.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
857
858
859     QJSValue r7 = eng.evaluate("/foo/gimp");
860     /*  v8 and jsc ignores invalid flags
861     QVERIFY(r7.isError());
862     QVERIFY(r7.toString().contains(QString::fromLatin1("SyntaxError"))); // Invalid regular expression flag
863     */
864
865     // JSC doesn't complain about duplicate flags.
866     QJSValue r8 = eng.evaluate("/foo/migmigmig");
867     QVERIFY(r8.isRegExp());
868     QCOMPARE(r8.toString(), QString::fromLatin1("/foo/gim"));
869
870     QJSValue r9 = rxCtor.construct();
871     QVERIFY(r9.isRegExp());
872     QCOMPARE(r9.toString(), QString::fromLatin1("/(?:)/"));
873
874     QJSValue r10 = rxCtor.construct(QJSValueList() << "" << "gim");
875     QVERIFY(r10.isRegExp());
876     QCOMPARE(r10.toString(), QString::fromLatin1("/(?:)/gim"));
877
878     QJSValue r11 = rxCtor.construct(QJSValueList() << "{1.*}" << "g");
879     QVERIFY(r11.isRegExp());
880     QCOMPARE(r11.toString(), QString::fromLatin1("/{1.*}/g"));
881 }
882
883 void tst_QJSEngine::newDate()
884 {
885     QJSEngine eng;
886
887     {
888         QJSValue date = eng.newDate(0);
889         QCOMPARE(date.isValid(), true);
890         QCOMPARE(date.isDate(), true);
891         QCOMPARE(date.isObject(), true);
892         QVERIFY(!date.isFunction());
893         // prototype should be Date.prototype
894         QCOMPARE(date.prototype().isValid(), true);
895         QCOMPARE(date.prototype().isDate(), true);
896         QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
897     }
898
899     {
900         QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::LocalTime);
901         QJSValue date = eng.newDate(dt);
902         QCOMPARE(date.isValid(), true);
903         QCOMPARE(date.isDate(), true);
904         QCOMPARE(date.isObject(), true);
905         // prototype should be Date.prototype
906         QCOMPARE(date.prototype().isValid(), true);
907         QCOMPARE(date.prototype().isDate(), true);
908         QCOMPARE(date.prototype().strictlyEquals(eng.evaluate("Date.prototype")), true);
909
910         QCOMPARE(date.toDateTime(), dt);
911     }
912
913     {
914         QDateTime dt = QDateTime(QDate(1, 2, 3), QTime(4, 5, 6, 7), Qt::UTC);
915         QJSValue date = eng.newDate(dt);
916         // toDateTime() result should be in local time
917         QCOMPARE(date.toDateTime(), dt.toLocalTime());
918     }
919 }
920
921 void tst_QJSEngine::jsParseDate()
922 {
923     QJSEngine eng;
924     // Date.parse() should return NaN when it fails
925     {
926         QJSValue ret = eng.evaluate("Date.parse()");
927         QVERIFY(ret.isNumber());
928         QVERIFY(qIsNaN(ret.toNumber()));
929     }
930
931     // Date.parse() should be able to parse the output of Date().toString()
932 #ifndef Q_WS_WIN // TODO: Test and remove this since 169701 has been fixed
933     {
934         QJSValue ret = eng.evaluate("var x = new Date(); var s = x.toString(); s == new Date(Date.parse(s)).toString()");
935         QVERIFY(ret.isBoolean());
936         QCOMPARE(ret.toBoolean(), true);
937     }
938 #endif
939 }
940
941 void tst_QJSEngine::newQObject()
942 {
943     QJSEngine eng;
944
945     {
946         QJSValue qobject = eng.newQObject(0);
947         QCOMPARE(qobject.isValid(), true);
948         QCOMPARE(qobject.isNull(), true);
949         QCOMPARE(qobject.isObject(), false);
950         QCOMPARE(qobject.toQObject(), (QObject *)0);
951     }
952     {
953         QJSValue qobject = eng.newQObject(this);
954         QCOMPARE(qobject.isValid(), true);
955         QCOMPARE(qobject.isQObject(), true);
956         QCOMPARE(qobject.isObject(), true);
957         QCOMPARE(qobject.toQObject(), (QObject *)this);
958         QVERIFY(!qobject.isFunction());
959         // prototype should be QObject.prototype
960         QCOMPARE(qobject.prototype().isValid(), true);
961         QEXPECT_FAIL("", "FIXME: newly created QObject's prototype is an JS Object", Continue);
962         QCOMPARE(qobject.prototype().isQObject(), true);
963 // ###FIXME: No QScriptClass        QCOMPARE(qobject.scriptClass(), (QScriptClass*)0);
964     }
965 }
966
967 void tst_QJSEngine::newQObject_ownership()
968 {
969     QJSEngine eng;
970     {
971         QPointer<QObject> ptr = new QObject();
972         QVERIFY(ptr != 0);
973         {
974             QJSValue v = eng.newQObject(ptr);
975         }
976         collectGarbage_helper(eng);
977         if (ptr)
978             QApplication::sendPostedEvents(ptr, QEvent::DeferredDelete);
979         QVERIFY(ptr == 0);
980     }
981     {
982         QPointer<QObject> ptr = new QObject(this);
983         QVERIFY(ptr != 0);
984         {
985             QJSValue v = eng.newQObject(ptr);
986         }
987         QObject *before = ptr;
988         collectGarbage_helper(eng);
989         QVERIFY(ptr == before);
990         delete ptr;
991     }
992     {
993         QObject *parent = new QObject();
994         QObject *child = new QObject(parent);
995         QJSValue v = eng.newQObject(child);
996         QCOMPARE(v.toQObject(), child);
997         delete parent;
998         QCOMPARE(v.toQObject(), (QObject *)0);
999     }
1000     {
1001         QPointer<QObject> ptr = new QObject();
1002         QVERIFY(ptr != 0);
1003         {
1004             QJSValue v = eng.newQObject(ptr);
1005         }
1006         collectGarbage_helper(eng);
1007         // no parent, so it should be like ScriptOwnership
1008         if (ptr)
1009             QApplication::sendPostedEvents(ptr, QEvent::DeferredDelete);
1010         QVERIFY(ptr == 0);
1011     }
1012     {
1013         QObject *parent = new QObject();
1014         QPointer<QObject> child = new QObject(parent);
1015         QVERIFY(child != 0);
1016         {
1017             QJSValue v = eng.newQObject(child);
1018         }
1019         collectGarbage_helper(eng);
1020         // has parent, so it should be like QtOwnership
1021         QVERIFY(child != 0);
1022         delete parent;
1023     }
1024 }
1025
1026 void tst_QJSEngine::newQObject_promoteObject()
1027 {
1028 #if 0 // ### FIXME: object promotion is not supported
1029     QScriptEngine eng;
1030     // "promote" plain object to QObject
1031     {
1032         QScriptValue obj = eng.newObject();
1033         QScriptValue originalProto = obj.prototype();
1034         QScriptValue ret = eng.newQObject(obj, this);
1035         QVERIFY(ret.isValid());
1036         QVERIFY(ret.isQObject());
1037         QVERIFY(ret.strictlyEquals(obj));
1038         QVERIFY(obj.isQObject());
1039         QCOMPARE(ret.toQObject(), (QObject *)this);
1040         QVERIFY(ret.prototype().strictlyEquals(originalProto));
1041         QScriptValue val = ret.property("objectName");
1042         QVERIFY(val.isString());
1043     }
1044     // "promote" variant object to QObject
1045     {
1046         QScriptValue obj = eng.newVariant(123);
1047         QVERIFY(obj.isVariant());
1048         QScriptValue originalProto = obj.prototype();
1049         QScriptValue ret = eng.newQObject(obj, this);
1050         QVERIFY(ret.isQObject());
1051         QVERIFY(ret.strictlyEquals(obj));
1052         QVERIFY(obj.isQObject());
1053         QCOMPARE(ret.toQObject(), (QObject *)this);
1054         QVERIFY(ret.prototype().strictlyEquals(originalProto));
1055     }
1056     // replace QObject* of existing object
1057     {
1058         QScriptValue object = eng.newVariant(123);
1059         QScriptValue originalProto = object.prototype();
1060         QObject otherQObject;
1061         for (int x = 0; x < 2; ++x) {
1062             QScriptValue ret = eng.newQObject(object, &otherQObject);
1063             QVERIFY(ret.isValid());
1064             QVERIFY(ret.isQObject());
1065             QVERIFY(ret.strictlyEquals(object));
1066             QCOMPARE(ret.toQObject(), (QObject *)&otherQObject);
1067             QVERIFY(ret.prototype().strictlyEquals(originalProto));
1068         }
1069     }
1070 #endif
1071 }
1072
1073 void tst_QJSEngine::newQObject_sameQObject()
1074 {
1075 #if 0 // ###FIXME: No QObjectWrapOptions API
1076     QSKIP("This test stongly relay on strictlyEquals feature that would change in near future");
1077     QScriptEngine eng;
1078     // calling newQObject() several times with same object
1079     for (int x = 0; x < 2; ++x) {
1080         QObject qobj;
1081         // the default is to create a new wrapper object
1082         QScriptValue obj1 = eng.newQObject(&qobj);
1083         QScriptValue obj2 = eng.newQObject(&qobj);
1084         QVERIFY(!obj2.strictlyEquals(obj1));
1085
1086         QScriptEngine::QObjectWrapOptions opt = 0;
1087         bool preferExisting = (x != 0);
1088         if (preferExisting)
1089             opt |= QScriptEngine::PreferExistingWrapperObject;
1090
1091         QScriptValue obj3 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
1092         QVERIFY(!obj3.strictlyEquals(obj2));
1093         QScriptValue obj4 = eng.newQObject(&qobj, QScriptEngine::AutoOwnership, opt);
1094         QCOMPARE(obj4.strictlyEquals(obj3), preferExisting);
1095
1096         QScriptValue obj5 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
1097         QVERIFY(!obj5.strictlyEquals(obj4));
1098         QScriptValue obj6 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership, opt);
1099         QCOMPARE(obj6.strictlyEquals(obj5), preferExisting);
1100
1101         QScriptValue obj7 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
1102                                            QScriptEngine::ExcludeSuperClassMethods | opt);
1103         QVERIFY(!obj7.strictlyEquals(obj6));
1104         QScriptValue obj8 = eng.newQObject(&qobj, QScriptEngine::ScriptOwnership,
1105                                            QScriptEngine::ExcludeSuperClassMethods | opt);
1106         QCOMPARE(obj8.strictlyEquals(obj7), preferExisting);
1107     }
1108 #endif
1109 }
1110
1111 #if 0 // FIXME: No prototype API in QScriptEngine
1112 void tst_QJSEngine::newQObject_defaultPrototype()
1113 {
1114     QScriptEngine eng;
1115     // newQObject() should set the default prototype, if one has been registered
1116     {
1117         QScriptValue oldQObjectProto = eng.defaultPrototype(qMetaTypeId<QObject*>());
1118
1119         QScriptValue qobjectProto = eng.newObject();
1120         eng.setDefaultPrototype(qMetaTypeId<QObject*>(), qobjectProto);
1121         {
1122             QScriptValue ret = eng.newQObject(this);
1123             QVERIFY(ret.prototype().equals(qobjectProto));
1124         }
1125         QScriptValue tstProto = eng.newObject();
1126         int typeId = qRegisterMetaType<tst_QJSEngine*>("tst_QJSEngine*");
1127         eng.setDefaultPrototype(typeId, tstProto);
1128         {
1129             QScriptValue ret = eng.newQObject(this);
1130             QVERIFY(ret.prototype().equals(tstProto));
1131         }
1132
1133         eng.setDefaultPrototype(qMetaTypeId<QObject*>(), oldQObjectProto);
1134         eng.setDefaultPrototype(typeId, QScriptValue());
1135     }
1136 }
1137 #endif
1138
1139 void tst_QJSEngine::newQObject_promoteNonObject()
1140 {
1141 #if 0 // ### FIXME: object promotion is not supported
1142     QScriptEngine eng;
1143     {
1144         QScriptValue ret = eng.newQObject(123, this);
1145         QVERIFY(ret.isQObject());
1146         QCOMPARE(ret.toQObject(), this);
1147     }
1148 #endif
1149 }
1150
1151 void tst_QJSEngine::newQObject_promoteNonQScriptObject()
1152 {
1153 #if 0 // ### FIXME: object promotion is not supported
1154     QSKIP("Promotion of non QScriptObjects kind of works (there is not difference between Object and Array, look at comments in newQObject implementation).");
1155     QScriptEngine eng;
1156     {
1157         QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::newQObject(): changing class of non-QScriptObject not supported");
1158         QScriptValue ret = eng.newQObject(eng.newArray(), this);
1159         QVERIFY(!ret.isValid());
1160     }
1161 #endif
1162 }
1163
1164 #if 0 // ### FIXME: No QScript Metaobject support right now
1165 QT_BEGIN_NAMESPACE
1166 Q_SCRIPT_DECLARE_QMETAOBJECT(QObject, QObject*)
1167 Q_SCRIPT_DECLARE_QMETAOBJECT(QWidget, QWidget*)
1168 QT_END_NAMESPACE
1169
1170 static QScriptValue myConstructor(QScriptContext *ctx, QScriptEngine *eng)
1171 {
1172     QScriptValue obj;
1173     if (ctx->isCalledAsConstructor()) {
1174         obj = ctx->thisObject();
1175     } else {
1176         obj = eng->newObject();
1177         obj.setPrototype(ctx->callee().property("prototype"));
1178     }
1179     obj.setProperty("isCalledAsConstructor", QScriptValue(eng, ctx->isCalledAsConstructor()));
1180     return obj;
1181 }
1182
1183 static QScriptValue instanceofJS(const QScriptValue &inst, const QScriptValue &ctor)
1184 {
1185     return inst.engine()->evaluate("(function(inst, ctor) { return inst instanceof ctor; })")
1186         .call(QScriptValue(), QScriptValueList() << inst << ctor);
1187 }
1188
1189 void tst_QJSEngine::newQMetaObject()
1190 {
1191     QScriptEngine eng;
1192 #if 0
1193     QScriptValue qclass = eng.newQMetaObject<QObject>();
1194     QScriptValue qclass2 = eng.newQMetaObject<QWidget>();
1195 #else
1196     QScriptValue qclass = qScriptValueFromQMetaObject<QObject>(&eng);
1197     QScriptValue qclass2 = qScriptValueFromQMetaObject<QWidget>(&eng);
1198 #endif
1199     QCOMPARE(qclass.isValid(), true);
1200     QCOMPARE(qclass.isQMetaObject(), true);
1201     QCOMPARE(qclass.toQMetaObject(), &QObject::staticMetaObject);
1202     QCOMPARE(qclass.isFunction(), true);
1203     QVERIFY(qclass.property("prototype").isObject());
1204
1205     QCOMPARE(qclass2.isValid(), true);
1206     QCOMPARE(qclass2.isQMetaObject(), true);
1207     QCOMPARE(qclass2.toQMetaObject(), &QWidget::staticMetaObject);
1208     QCOMPARE(qclass2.isFunction(), true);
1209     QVERIFY(qclass2.property("prototype").isObject());
1210
1211     // prototype should be QMetaObject.prototype
1212     QCOMPARE(qclass.prototype().isObject(), true);
1213     QCOMPARE(qclass2.prototype().isObject(), true);
1214
1215     QScriptValue instance = qclass.construct();
1216     QCOMPARE(instance.isQObject(), true);
1217     QCOMPARE(instance.toQObject()->metaObject(), qclass.toQMetaObject());
1218     QEXPECT_FAIL("", "FIXME:  newQMetaObject not implemented properly yet", Abort);
1219     QVERIFY(instance.instanceOf(qclass));
1220     QVERIFY(instanceofJS(instance, qclass).strictlyEquals(true));
1221
1222     QScriptValue instance2 = qclass2.construct();
1223     QCOMPARE(instance2.isQObject(), true);
1224     QCOMPARE(instance2.toQObject()->metaObject(), qclass2.toQMetaObject());
1225     QVERIFY(instance2.instanceOf(qclass2));
1226     QVERIFY(instanceofJS(instance2, qclass2).strictlyEquals(true));
1227     QVERIFY(!instance2.instanceOf(qclass));
1228     QVERIFY(instanceofJS(instance2, qclass).strictlyEquals(false));
1229
1230     QScriptValueList args;
1231     args << instance;
1232     QScriptValue instance3 = qclass.construct(args);
1233     QCOMPARE(instance3.isQObject(), true);
1234     QCOMPARE(instance3.toQObject()->parent(), instance.toQObject());
1235     QVERIFY(instance3.instanceOf(qclass));
1236     QVERIFY(instanceofJS(instance3, qclass).strictlyEquals(true));
1237     QVERIFY(!instance3.instanceOf(qclass2));
1238     QVERIFY(instanceofJS(instance3, qclass2).strictlyEquals(false));
1239     args.clear();
1240
1241     QPointer<QObject> qpointer1 = instance.toQObject();
1242     QPointer<QObject> qpointer2 = instance2.toQObject();
1243     QPointer<QObject> qpointer3 = instance3.toQObject();
1244
1245     QVERIFY(qpointer1);
1246     QVERIFY(qpointer2);
1247     QVERIFY(qpointer3);
1248
1249     // verify that AutoOwnership is in effect
1250     instance = QScriptValue();
1251     collectGarbage_helper(eng);
1252
1253     QVERIFY(!qpointer1);
1254     QVERIFY(qpointer2);
1255     QVERIFY(!qpointer3); // was child of instance
1256
1257     QVERIFY(instance.toQObject() == 0);
1258     QVERIFY(instance3.toQObject() == 0); // was child of instance
1259     QVERIFY(instance2.toQObject() != 0);
1260     instance2 = QScriptValue();
1261     collectGarbage_helper(eng);
1262     QVERIFY(instance2.toQObject() == 0);
1263
1264     // with custom constructor
1265     QScriptValue ctor = eng.newFunction(myConstructor);
1266     QScriptValue qclass3 = eng.newQMetaObject(&QObject::staticMetaObject, ctor);
1267     QVERIFY(qclass3.property("prototype").equals(ctor.property("prototype")));
1268     {
1269         QScriptValue ret = qclass3.call();
1270         QVERIFY(ret.isObject());
1271         QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
1272         QVERIFY(!ret.property("isCalledAsConstructor").toBoolean());
1273         QVERIFY(ret.instanceOf(qclass3));
1274         QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
1275         QVERIFY(!ret.instanceOf(qclass));
1276         QVERIFY(instanceofJS(ret, qclass).strictlyEquals(false));
1277     }
1278     {
1279         QScriptValue ret = qclass3.construct();
1280         QVERIFY(ret.isObject());
1281         QVERIFY(ret.property("isCalledAsConstructor").isBoolean());
1282         QVERIFY(ret.property("isCalledAsConstructor").toBoolean());
1283         QVERIFY(ret.instanceOf(qclass3));
1284         QVERIFY(instanceofJS(ret, qclass3).strictlyEquals(true));
1285         QVERIFY(!ret.instanceOf(qclass2));
1286         QVERIFY(instanceofJS(ret, qclass2).strictlyEquals(false));
1287     }
1288
1289     // subclassing
1290     qclass2.setProperty("prototype", qclass.construct());
1291     QVERIFY(qclass2.construct().instanceOf(qclass));
1292     QVERIFY(instanceofJS(qclass2.construct(), qclass).strictlyEquals(true));
1293
1294     // with meta-constructor
1295     QScriptValue qclass4 = eng.newQMetaObject(&QObject::staticMetaObject);
1296     {
1297         QScriptValue inst = qclass4.construct();
1298         QVERIFY(inst.isQObject());
1299         QVERIFY(inst.toQObject() != 0);
1300         QCOMPARE(inst.toQObject()->parent(), (QObject*)0);
1301         QVERIFY(inst.instanceOf(qclass4));
1302         QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
1303         QVERIFY(!inst.instanceOf(qclass3));
1304         QVERIFY(instanceofJS(inst, qclass3).strictlyEquals(false));
1305     }
1306     {
1307         QScriptValue inst = qclass4.construct(QScriptValueList() << eng.newQObject(this));
1308         QVERIFY(inst.isQObject());
1309         QVERIFY(inst.toQObject() != 0);
1310         QCOMPARE(inst.toQObject()->parent(), (QObject*)this);
1311         QVERIFY(inst.instanceOf(qclass4));
1312         QVERIFY(instanceofJS(inst, qclass4).strictlyEquals(true));
1313         QVERIFY(!inst.instanceOf(qclass2));
1314         QVERIFY(instanceofJS(inst, qclass2).strictlyEquals(false));
1315     }
1316 }
1317 #endif
1318
1319 #if 0 // ###FIXME: No activation object support
1320 void tst_QJSEngine::newActivationObject()
1321 {
1322     QSKIP("internal function not implemented in JSC-based back-end");
1323     QScriptEngine eng;
1324     QScriptValue act = eng.newActivationObject();
1325     QEXPECT_FAIL("", "", Continue);
1326     QCOMPARE(act.isValid(), true);
1327     QEXPECT_FAIL("", "", Continue);
1328     QCOMPARE(act.isObject(), true);
1329     QVERIFY(!act.isFunction());
1330     QScriptValue v(&eng, 123);
1331     act.setProperty("prop", v);
1332     QEXPECT_FAIL("", "", Continue);
1333     QCOMPARE(act.property("prop").strictlyEquals(v), true);
1334     QCOMPARE(act.scope().isValid(), false);
1335     QEXPECT_FAIL("", "", Continue);
1336     QVERIFY(act.prototype().isNull());
1337 }
1338 #endif
1339
1340 #if 0 // ###FIXME: No setGlobalObject support - yay
1341 void tst_QJSEngine::getSetGlobalObjectSimple()
1342 {
1343     QScriptEngine engine;
1344     QScriptValue object = engine.newObject();
1345     object.setProperty("foo", 123);
1346     engine.evaluate("var bar = 100");
1347     engine.setGlobalObject(object);
1348     engine.evaluate("rab = 100");
1349     QVERIFY(engine.globalObject().property("rab").isValid());
1350     QVERIFY(engine.globalObject().property("foo").isValid());
1351     QVERIFY(!engine.globalObject().property("bar").isValid());
1352 }
1353
1354 void tst_QJSEngine::getSetGlobalObject()
1355 {
1356     QScriptEngine eng;
1357     QScriptValue glob = eng.globalObject();
1358     glob = QScriptValue(); // kill reference to old global object
1359     collectGarbage_helper(eng);
1360
1361     glob = eng.globalObject();
1362     QCOMPARE(glob.isValid(), true);
1363     QCOMPARE(glob.isObject(), true);
1364     QVERIFY(!glob.isFunction());
1365     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(glob));
1366     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(glob));
1367     QEXPECT_FAIL("", "FIXME: Do we really want to enforce this? ECMA standard says that it is implementation dependent, skipping for now", Continue);
1368     QCOMPARE(glob.toString(), QString::fromLatin1("[object global]"));
1369     // prototype should be Object.prototype
1370     QCOMPARE(glob.prototype().isValid(), true);
1371     QCOMPARE(glob.prototype().isObject(), true);
1372     QEXPECT_FAIL("", "FIXME: Do we really want to enforce this? ECMA standard says that it is implementation dependent, skipping for now", Continue);
1373     QCOMPARE(glob.prototype().strictlyEquals(eng.evaluate("Object.prototype")), true);
1374
1375     eng.setGlobalObject(glob);
1376     QVERIFY(eng.globalObject().equals(glob));
1377     eng.setGlobalObject(123);
1378     QVERIFY(eng.globalObject().equals(glob));
1379
1380     QScriptValue obj = eng.newObject();
1381     eng.setGlobalObject(obj);
1382     QVERIFY(eng.globalObject().strictlyEquals(obj));
1383     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1384     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1385     QVERIFY(eng.evaluate("this").strictlyEquals(obj));
1386     QEXPECT_FAIL("", "FIXME: Do we really want to enforce this? ECMA standard says that it is implementation dependent, skipping for now", Continue);
1387     QCOMPARE(eng.globalObject().toString(), QString::fromLatin1("[object global]"));
1388
1389     collectGarbage_helper(eng);
1390     glob = QScriptValue(); // kill reference to old global object
1391     collectGarbage_helper(eng);
1392     obj = eng.newObject();
1393     eng.setGlobalObject(obj);
1394     QVERIFY(eng.globalObject().strictlyEquals(obj));
1395     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1396     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1397
1398     collectGarbage_helper(eng);
1399     QVERIFY(eng.globalObject().strictlyEquals(obj));
1400     QVERIFY(eng.currentContext()->thisObject().strictlyEquals(obj));
1401     QVERIFY(eng.currentContext()->activationObject().strictlyEquals(obj));
1402
1403     QVERIFY(!obj.property("foo").isValid());
1404     eng.evaluate("var foo = 123");
1405     {
1406         QScriptValue ret = obj.property("foo");
1407         QVERIFY(ret.isNumber());
1408         QCOMPARE(ret.toInt32(), 123);
1409     }
1410
1411     QVERIFY(!obj.property("bar").isValid());
1412     eng.evaluate("bar = 456");
1413     {
1414         QScriptValue ret = obj.property("bar");
1415         QVERIFY(ret.isNumber());
1416         QCOMPARE(ret.toInt32(), 456);
1417     }
1418
1419     QVERIFY(!obj.property("baz").isValid());
1420     eng.evaluate("this['baz'] = 789");
1421     {
1422         QScriptValue ret = obj.property("baz");
1423         QVERIFY(ret.isNumber());
1424         QCOMPARE(ret.toInt32(), 789);
1425     }
1426
1427     {
1428         QScriptValue ret = eng.evaluate("(function() { return this; })()");
1429         QVERIFY(ret.strictlyEquals(obj));
1430     }
1431
1432     // Delete property.
1433     {
1434         QScriptValue ret = eng.evaluate("delete foo");
1435         QVERIFY(ret.isBool());
1436         QVERIFY(ret.toBool());
1437         QVERIFY(!obj.property("foo").isValid());
1438     }
1439
1440     // Getter/setter property.
1441     //the custom global object have an interceptor
1442     QVERIFY(eng.evaluate("this.__defineGetter__('oof', function() { return this.bar; })").isUndefined());
1443     QVERIFY(eng.evaluate("this.__defineSetter__('oof', function(v) { this.bar = v; })").isUndefined());
1444     QVERIFY(eng.evaluate("this.__lookupGetter__('oof')").isFunction());
1445     QVERIFY(eng.evaluate("this.__lookupSetter__('oof')").isFunction());
1446     eng.evaluate("oof = 123");
1447     QVERIFY(eng.evaluate("oof").equals(obj.property("bar")));
1448
1449     // Enumeration.
1450     {
1451         QScriptValue ret = eng.evaluate("a = []; for (var p in this) a.push(p); a");
1452         QCOMPARE(ret.toString(), QString::fromLatin1("bar,baz,oof,p,a"));
1453     }
1454 }
1455 #endif
1456
1457 #if 0 // ###FIXME: no c-style callbacks
1458 static QScriptValue getSetFoo(QScriptContext *ctx, QScriptEngine *)
1459 {
1460     if (ctx->argumentCount() > 0)
1461         ctx->thisObject().setProperty("foo", ctx->argument(0));
1462     return ctx->thisObject().property("foo");
1463 }
1464 #endif
1465
1466 void tst_QJSEngine::globalObjectProperties()
1467 {
1468     QSKIP("Test failing - QTBUG-22238", SkipAll);
1469     // See ECMA-262 Section 15.1, "The Global Object".
1470
1471     QJSEngine eng;
1472     QJSValue global = eng.globalObject();
1473
1474     QVERIFY(global.property("NaN").isNumber());
1475     QVERIFY(qIsNaN(global.property("NaN").toNumber()));
1476     QCOMPARE(global.propertyFlags("NaN"), QJSValue::SkipInEnumeration | QJSValue::Undeletable);
1477
1478     QVERIFY(global.property("Infinity").isNumber());
1479     QVERIFY(qIsInf(global.property("Infinity").toNumber()));
1480     QCOMPARE(global.propertyFlags("NaN"), QJSValue::SkipInEnumeration | QJSValue::Undeletable);
1481
1482     QVERIFY(global.property("undefined").isUndefined());
1483     QCOMPARE(global.propertyFlags("undefined"), QJSValue::SkipInEnumeration | QJSValue::Undeletable);
1484
1485     QVERIFY(global.property("eval").isFunction());
1486     QCOMPARE(global.propertyFlags("eval"), QJSValue::SkipInEnumeration);
1487
1488     QVERIFY(global.property("parseInt").isFunction());
1489     QCOMPARE(global.propertyFlags("parseInt"), QJSValue::SkipInEnumeration);
1490
1491     QVERIFY(global.property("parseFloat").isFunction());
1492     QCOMPARE(global.propertyFlags("parseFloat"), QJSValue::SkipInEnumeration);
1493
1494     QVERIFY(global.property("isNaN").isFunction());
1495     QCOMPARE(global.propertyFlags("isNaN"), QJSValue::SkipInEnumeration);
1496
1497     QVERIFY(global.property("isFinite").isFunction());
1498     QCOMPARE(global.propertyFlags("isFinite"), QJSValue::SkipInEnumeration);
1499
1500     QVERIFY(global.property("decodeURI").isFunction());
1501     QCOMPARE(global.propertyFlags("decodeURI"), QJSValue::SkipInEnumeration);
1502
1503     QVERIFY(global.property("decodeURIComponent").isFunction());
1504     QCOMPARE(global.propertyFlags("decodeURIComponent"), QJSValue::SkipInEnumeration);
1505
1506     QVERIFY(global.property("encodeURI").isFunction());
1507     QCOMPARE(global.propertyFlags("encodeURI"), QJSValue::SkipInEnumeration);
1508
1509     QVERIFY(global.property("encodeURIComponent").isFunction());
1510     QCOMPARE(global.propertyFlags("encodeURIComponent"), QJSValue::SkipInEnumeration);
1511
1512     QVERIFY(global.property("Object").isFunction());
1513     QCOMPARE(global.propertyFlags("Object"), QJSValue::SkipInEnumeration);
1514     QVERIFY(global.property("Function").isFunction());
1515     QCOMPARE(global.propertyFlags("Function"), QJSValue::SkipInEnumeration);
1516     QVERIFY(global.property("Array").isFunction());
1517     QCOMPARE(global.propertyFlags("Array"), QJSValue::SkipInEnumeration);
1518     QVERIFY(global.property("String").isFunction());
1519     QCOMPARE(global.propertyFlags("String"), QJSValue::SkipInEnumeration);
1520     QVERIFY(global.property("Boolean").isFunction());
1521     QCOMPARE(global.propertyFlags("Boolean"), QJSValue::SkipInEnumeration);
1522     QVERIFY(global.property("Number").isFunction());
1523     QCOMPARE(global.propertyFlags("Number"), QJSValue::SkipInEnumeration);
1524     QVERIFY(global.property("Date").isFunction());
1525     QCOMPARE(global.propertyFlags("Date"), QJSValue::SkipInEnumeration);
1526     QVERIFY(global.property("RegExp").isFunction());
1527     QCOMPARE(global.propertyFlags("RegExp"), QJSValue::SkipInEnumeration);
1528     QVERIFY(global.property("Error").isFunction());
1529     QCOMPARE(global.propertyFlags("Error"), QJSValue::SkipInEnumeration);
1530     QVERIFY(global.property("EvalError").isFunction());
1531     QCOMPARE(global.propertyFlags("EvalError"), QJSValue::SkipInEnumeration);
1532     QVERIFY(global.property("RangeError").isFunction());
1533     QCOMPARE(global.propertyFlags("RangeError"), QJSValue::SkipInEnumeration);
1534     QVERIFY(global.property("ReferenceError").isFunction());
1535     QCOMPARE(global.propertyFlags("ReferenceError"), QJSValue::SkipInEnumeration);
1536     QVERIFY(global.property("SyntaxError").isFunction());
1537     QCOMPARE(global.propertyFlags("SyntaxError"), QJSValue::SkipInEnumeration);
1538     QVERIFY(global.property("TypeError").isFunction());
1539     QCOMPARE(global.propertyFlags("TypeError"), QJSValue::SkipInEnumeration);
1540     QVERIFY(global.property("URIError").isFunction());
1541     QCOMPARE(global.propertyFlags("URIError"), QJSValue::SkipInEnumeration);
1542     QVERIFY(global.property("Math").isObject());
1543     QVERIFY(!global.property("Math").isFunction());
1544     QCOMPARE(global.propertyFlags("Math"), QJSValue::SkipInEnumeration);
1545 }
1546
1547 void tst_QJSEngine::globalObjectEquals()
1548 {
1549     QJSEngine eng;
1550     QJSValue o = eng.globalObject();
1551     QVERIFY(o.strictlyEquals(eng.globalObject()));
1552     QVERIFY(o.equals(eng.globalObject()));
1553 }
1554
1555 void tst_QJSEngine::globalObjectProperties_enumerate()
1556 {
1557     QSKIP("Test failing - QTBUG-22238", SkipAll);
1558     QJSEngine eng;
1559     QJSValue global = eng.globalObject();
1560
1561     QSet<QString> expectedNames;
1562     expectedNames
1563         << "isNaN"
1564         << "parseFloat"
1565         << "String"
1566         << "EvalError"
1567         << "URIError"
1568         << "Math"
1569         << "encodeURIComponent"
1570         << "RangeError"
1571         << "eval"
1572         << "isFinite"
1573         << "ReferenceError"
1574         << "Infinity"
1575         << "Function"
1576         << "RegExp"
1577         << "Number"
1578         << "parseInt"
1579         << "Object"
1580         << "decodeURI"
1581         << "TypeError"
1582         << "Boolean"
1583         << "encodeURI"
1584         << "NaN"
1585         << "Error"
1586         << "decodeURIComponent"
1587         << "Date"
1588         << "Array"
1589         << "escape"
1590         << "unescape"
1591         << "SyntaxError"
1592         << "undefined"
1593         // JavaScriptCore
1594         << "JSON"
1595         // V8
1596         << "execScript" //execScript for IE compatibility.
1597         ;
1598     QSet<QString> actualNames;
1599     {
1600         QJSValueIterator it(global);
1601         while (it.hasNext()) {
1602             it.next();
1603             actualNames.insert(it.name());
1604         }
1605     }
1606
1607     QSet<QString> remainingNames = actualNames;
1608     {
1609         QSet<QString>::const_iterator it;
1610         for (it = expectedNames.constBegin(); it != expectedNames.constEnd(); ++it) {
1611             QString name = *it;
1612             QVERIFY(actualNames.contains(name));
1613             remainingNames.remove(name);
1614         }
1615     }
1616     QVERIFY(remainingNames.isEmpty());
1617 }
1618
1619 void tst_QJSEngine::createGlobalObjectProperty()
1620 {
1621     QJSEngine eng;
1622     QJSValue global = eng.globalObject();
1623     // create property with no attributes
1624     {
1625         QString name = QString::fromLatin1("foo");
1626         QVERIFY(!global.property(name).isValid());
1627         QJSValue val(123);
1628         global.setProperty(name, val);
1629         QVERIFY(global.property(name).equals(val));
1630         QVERIFY(global.propertyFlags(name) == 0);
1631         global.setProperty(name, QJSValue());
1632         QVERIFY(!global.property(name).isValid());
1633     }
1634     // create property with attributes
1635 #if 0 // ###FIXME: setProperty with flags is not supported
1636     {
1637         QString name = QString::fromLatin1("bar");
1638         QVERIFY(!global.property(name).isValid());
1639         QScriptValue val(QString::fromLatin1("ciao"));
1640         QScriptValue::PropertyFlags flags = QScriptValue::ReadOnly | QScriptValue::SkipInEnumeration;
1641         global.setProperty(name, val, flags);
1642         QVERIFY(global.property(name).equals(val));
1643         //QEXPECT_FAIL("", "QTBUG-6134: custom Global Object properties don't retain attributes", Continue);
1644         QCOMPARE(global.propertyFlags(name), flags);
1645         global.setProperty(name, QScriptValue());
1646         QVERIFY(!global.property(name).isValid());
1647     }
1648 #endif
1649 }
1650
1651 void tst_QJSEngine::globalObjectGetterSetterProperty()
1652 {
1653 #if 0 // ###FIXME: No c-style callbacks
1654     QScriptEngine engine;
1655     QScriptValue global = engine.globalObject();
1656     global.setProperty("bar", engine.newFunction(getSetFoo),
1657                        QScriptValue::PropertySetter | QScriptValue::PropertyGetter);
1658     global.setProperty("foo", 123);
1659     QVERIFY(global.property("bar").equals(global.property("foo")));
1660     QVERIFY(engine.evaluate("bar").equals(global.property("foo")));
1661     global.setProperty("bar", 456);
1662     QVERIFY(global.property("bar").equals(global.property("foo")));
1663
1664     engine.evaluate("__defineGetter__('baz', function() { return 789; })");
1665     QVERIFY(engine.evaluate("baz").equals(789));
1666     QVERIFY(global.property("baz").equals(789));
1667 #endif
1668 }
1669
1670 #if 0 // ###FIXME: No support for setting the global object
1671 void tst_QJSEngine::customGlobalObjectWithPrototype()
1672 {
1673     for (int x = 0; x < 2; ++x) {
1674         QScriptEngine engine;
1675         QScriptValue wrap = engine.newObject();
1676         QScriptValue global = engine.globalObject();
1677         QScriptValue originalGlobalProto = global.prototype();
1678         if (!x) {
1679             // Set prototype before setting global object
1680             wrap.setPrototype(global);
1681             QVERIFY(wrap.prototype().strictlyEquals(global));
1682             engine.setGlobalObject(wrap);
1683         } else {
1684             // Set prototype after setting global object
1685             engine.setGlobalObject(wrap);
1686             wrap.setPrototype(global);
1687             QVERIFY(wrap.prototype().strictlyEquals(global));
1688         }
1689         {
1690             QScriptValue ret = engine.evaluate("print");
1691             QVERIFY(ret.isFunction());
1692             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1693         }
1694         {
1695             QScriptValue ret = engine.evaluate("this.print");
1696             QVERIFY(ret.isFunction());
1697             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1698         }
1699         {
1700             QScriptValue ret = engine.evaluate("hasOwnProperty('print')");
1701             QVERIFY(ret.isBool());
1702             if (x) QEXPECT_FAIL("", "Why?", Continue);
1703             QVERIFY(!ret.toBool());
1704         }
1705         {
1706             QScriptValue ret = engine.evaluate("this.hasOwnProperty('print')");
1707             QVERIFY(ret.isBool());
1708             if (x) QEXPECT_FAIL("", "Why?", Continue);
1709             QVERIFY(!ret.toBool());
1710         }
1711
1712         QScriptValue anotherProto = engine.newObject();
1713         anotherProto.setProperty("anotherProtoProperty", 123);
1714         global.setPrototype(anotherProto);
1715         {
1716             QScriptValue ret = engine.evaluate("print");
1717             QVERIFY(ret.isFunction());
1718             QVERIFY(ret.strictlyEquals(wrap.property("print")));
1719         }
1720         {
1721             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1722             QVERIFY(ret.isNumber());
1723             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1724         }
1725         {
1726             QScriptValue ret = engine.evaluate("this.anotherProtoProperty");
1727             QVERIFY(ret.isNumber());
1728             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1729         }
1730
1731         wrap.setPrototype(anotherProto);
1732         {
1733             QScriptValue ret = engine.evaluate("print");
1734             QVERIFY(ret.isError());
1735             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: print is not defined"));
1736         }
1737         {
1738             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1739             QVERIFY(ret.isNumber());
1740             QVERIFY(ret.strictlyEquals(wrap.property("anotherProtoProperty")));
1741         }
1742         QVERIFY(global.prototype().strictlyEquals(anotherProto));
1743
1744         global.setPrototype(originalGlobalProto);
1745         engine.setGlobalObject(global);
1746         {
1747             QScriptValue ret = engine.evaluate("anotherProtoProperty");
1748             QVERIFY(ret.isError());
1749             QVERIFY(ret.toString().startsWith("ReferenceError: "));
1750         }
1751         {
1752             QScriptValue ret = engine.evaluate("print");
1753             QVERIFY(ret.isFunction());
1754             QVERIFY(ret.strictlyEquals(global.property("print")));
1755         }
1756         QVERIFY(!anotherProto.property("print").isValid());
1757     }
1758 }
1759 #endif
1760
1761 void tst_QJSEngine::globalObjectWithCustomPrototype()
1762 {
1763     QJSEngine engine;
1764     QJSValue proto = engine.newObject();
1765     proto.setProperty("protoProperty", 123);
1766     QJSValue global = engine.globalObject();
1767     QJSValue originalProto = global.prototype();
1768     global.setPrototype(proto);
1769     {
1770         QJSValue ret = engine.evaluate("protoProperty");
1771         QEXPECT_FAIL("", "Replacing the prototype of the global object is currently unsupported (see also v8 issue 1078)", Abort);
1772         QVERIFY(ret.isNumber());
1773         QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1774     }
1775     {
1776         QJSValue ret = engine.evaluate("this.protoProperty");
1777         QVERIFY(ret.isNumber());
1778         QVERIFY(ret.strictlyEquals(global.property("protoProperty")));
1779     }
1780     {
1781         QJSValue ret = engine.evaluate("hasOwnProperty('protoProperty')");
1782         QVERIFY(ret.isBool());
1783         QVERIFY(!ret.toBool());
1784     }
1785     {
1786         QJSValue ret = engine.evaluate("this.hasOwnProperty('protoProperty')");
1787         QVERIFY(ret.isBool());
1788         QVERIFY(!ret.toBool());
1789     }
1790
1791     // Custom prototype set from JS
1792     {
1793         QJSValue ret = engine.evaluate("this.__proto__ = { 'a': 123 }; a");
1794         QVERIFY(ret.isNumber());
1795         QVERIFY(ret.strictlyEquals(global.property("a")));
1796     }
1797 }
1798
1799 void tst_QJSEngine::builtinFunctionNames_data()
1800 {
1801     QTest::addColumn<QString>("expression");
1802     QTest::addColumn<QString>("expectedName");
1803
1804     // See ECMA-262 Chapter 15, "Standard Built-in ECMAScript Objects".
1805
1806     QTest::newRow("parseInt") << QString("parseInt") << QString("parseInt");
1807     QTest::newRow("parseFloat") << QString("parseFloat") << QString("parseFloat");
1808     QTest::newRow("isNaN") << QString("isNaN") << QString("isNaN");
1809     QTest::newRow("isFinite") << QString("isFinite") << QString("isFinite");
1810     QTest::newRow("decodeURI") << QString("decodeURI") << QString("decodeURI");
1811     QTest::newRow("decodeURIComponent") << QString("decodeURIComponent") << QString("decodeURIComponent");
1812     QTest::newRow("encodeURI") << QString("encodeURI") << QString("encodeURI");
1813     QTest::newRow("encodeURIComponent") << QString("encodeURIComponent") << QString("encodeURIComponent");
1814     QTest::newRow("escape") << QString("escape") << QString("escape");
1815     QTest::newRow("unescape") << QString("unescape") << QString("unescape");
1816
1817     QTest::newRow("Array") << QString("Array") << QString("Array");
1818     QTest::newRow("Array.prototype.toString") << QString("Array.prototype.toString") << QString("toString");
1819     QTest::newRow("Array.prototype.toLocaleString") << QString("Array.prototype.toLocaleString") << QString("toLocaleString");
1820     QTest::newRow("Array.prototype.concat") << QString("Array.prototype.concat") << QString("concat");
1821     QTest::newRow("Array.prototype.join") << QString("Array.prototype.join") << QString("join");
1822     QTest::newRow("Array.prototype.pop") << QString("Array.prototype.pop") << QString("pop");
1823     QTest::newRow("Array.prototype.push") << QString("Array.prototype.push") << QString("push");
1824     QTest::newRow("Array.prototype.reverse") << QString("Array.prototype.reverse") << QString("reverse");
1825     QTest::newRow("Array.prototype.shift") << QString("Array.prototype.shift") << QString("shift");
1826     QTest::newRow("Array.prototype.slice") << QString("Array.prototype.slice") << QString("slice");
1827     QTest::newRow("Array.prototype.sort") << QString("Array.prototype.sort") << QString("sort");
1828     QTest::newRow("Array.prototype.splice") << QString("Array.prototype.splice") << QString("splice");
1829     QTest::newRow("Array.prototype.unshift") << QString("Array.prototype.unshift") << QString("unshift");
1830
1831     QTest::newRow("Boolean") << QString("Boolean") << QString("Boolean");
1832     QTest::newRow("Boolean.prototype.toString") << QString("Boolean.prototype.toString") << QString("toString");
1833
1834     QTest::newRow("Date") << QString("Date") << QString("Date");
1835     QTest::newRow("Date.prototype.toString") << QString("Date.prototype.toString") << QString("toString");
1836     QTest::newRow("Date.prototype.toDateString") << QString("Date.prototype.toDateString") << QString("toDateString");
1837     QTest::newRow("Date.prototype.toTimeString") << QString("Date.prototype.toTimeString") << QString("toTimeString");
1838     QTest::newRow("Date.prototype.toLocaleString") << QString("Date.prototype.toLocaleString") << QString("toLocaleString");
1839     QTest::newRow("Date.prototype.toLocaleDateString") << QString("Date.prototype.toLocaleDateString") << QString("toLocaleDateString");
1840     QTest::newRow("Date.prototype.toLocaleTimeString") << QString("Date.prototype.toLocaleTimeString") << QString("toLocaleTimeString");
1841     QTest::newRow("Date.prototype.valueOf") << QString("Date.prototype.valueOf") << QString("valueOf");
1842     QTest::newRow("Date.prototype.getTime") << QString("Date.prototype.getTime") << QString("getTime");
1843     QTest::newRow("Date.prototype.getYear") << QString("Date.prototype.getYear") << QString("getYear");
1844     QTest::newRow("Date.prototype.getFullYear") << QString("Date.prototype.getFullYear") << QString("getFullYear");
1845     QTest::newRow("Date.prototype.getUTCFullYear") << QString("Date.prototype.getUTCFullYear") << QString("getUTCFullYear");
1846     QTest::newRow("Date.prototype.getMonth") << QString("Date.prototype.getMonth") << QString("getMonth");
1847     QTest::newRow("Date.prototype.getUTCMonth") << QString("Date.prototype.getUTCMonth") << QString("getUTCMonth");
1848     QTest::newRow("Date.prototype.getDate") << QString("Date.prototype.getDate") << QString("getDate");
1849     QTest::newRow("Date.prototype.getUTCDate") << QString("Date.prototype.getUTCDate") << QString("getUTCDate");
1850     QTest::newRow("Date.prototype.getDay") << QString("Date.prototype.getDay") << QString("getDay");
1851     QTest::newRow("Date.prototype.getUTCDay") << QString("Date.prototype.getUTCDay") << QString("getUTCDay");
1852     QTest::newRow("Date.prototype.getHours") << QString("Date.prototype.getHours") << QString("getHours");
1853     QTest::newRow("Date.prototype.getUTCHours") << QString("Date.prototype.getUTCHours") << QString("getUTCHours");
1854     QTest::newRow("Date.prototype.getMinutes") << QString("Date.prototype.getMinutes") << QString("getMinutes");
1855     QTest::newRow("Date.prototype.getUTCMinutes") << QString("Date.prototype.getUTCMinutes") << QString("getUTCMinutes");
1856     QTest::newRow("Date.prototype.getSeconds") << QString("Date.prototype.getSeconds") << QString("getSeconds");
1857     QTest::newRow("Date.prototype.getUTCSeconds") << QString("Date.prototype.getUTCSeconds") << QString("getUTCSeconds");
1858     QTest::newRow("Date.prototype.getMilliseconds") << QString("Date.prototype.getMilliseconds") << QString("getMilliseconds");
1859     QTest::newRow("Date.prototype.getUTCMilliseconds") << QString("Date.prototype.getUTCMilliseconds") << QString("getUTCMilliseconds");
1860     QTest::newRow("Date.prototype.getTimezoneOffset") << QString("Date.prototype.getTimezoneOffset") << QString("getTimezoneOffset");
1861     QTest::newRow("Date.prototype.setTime") << QString("Date.prototype.setTime") << QString("setTime");
1862     QTest::newRow("Date.prototype.setMilliseconds") << QString("Date.prototype.setMilliseconds") << QString("setMilliseconds");
1863     QTest::newRow("Date.prototype.setUTCMilliseconds") << QString("Date.prototype.setUTCMilliseconds") << QString("setUTCMilliseconds");
1864     QTest::newRow("Date.prototype.setSeconds") << QString("Date.prototype.setSeconds") << QString("setSeconds");
1865     QTest::newRow("Date.prototype.setUTCSeconds") << QString("Date.prototype.setUTCSeconds") << QString("setUTCSeconds");
1866     QTest::newRow("Date.prototype.setMinutes") << QString("Date.prototype.setMinutes") << QString("setMinutes");
1867     QTest::newRow("Date.prototype.setUTCMinutes") << QString("Date.prototype.setUTCMinutes") << QString("setUTCMinutes");
1868     QTest::newRow("Date.prototype.setHours") << QString("Date.prototype.setHours") << QString("setHours");
1869     QTest::newRow("Date.prototype.setUTCHours") << QString("Date.prototype.setUTCHours") << QString("setUTCHours");
1870     QTest::newRow("Date.prototype.setDate") << QString("Date.prototype.setDate") << QString("setDate");
1871     QTest::newRow("Date.prototype.setUTCDate") << QString("Date.prototype.setUTCDate") << QString("setUTCDate");
1872     QTest::newRow("Date.prototype.setMonth") << QString("Date.prototype.setMonth") << QString("setMonth");
1873     QTest::newRow("Date.prototype.setUTCMonth") << QString("Date.prototype.setUTCMonth") << QString("setUTCMonth");
1874     QTest::newRow("Date.prototype.setYear") << QString("Date.prototype.setYear") << QString("setYear");
1875     QTest::newRow("Date.prototype.setFullYear") << QString("Date.prototype.setFullYear") << QString("setFullYear");
1876     QTest::newRow("Date.prototype.setUTCFullYear") << QString("Date.prototype.setUTCFullYear") << QString("setUTCFullYear");
1877     QTest::newRow("Date.prototype.toUTCString") << QString("Date.prototype.toUTCString") << QString("toUTCString");
1878     QTest::newRow("Date.prototype.toGMTString") << QString("Date.prototype.toGMTString") << QString("toGMTString");
1879
1880     QTest::newRow("Error") << QString("Error") << QString("Error");
1881 //    QTest::newRow("Error.prototype.backtrace") << QString("Error.prototype.backtrace") << QString("backtrace");
1882     QTest::newRow("Error.prototype.toString") << QString("Error.prototype.toString") << QString("toString");
1883
1884     QTest::newRow("EvalError") << QString("EvalError") << QString("EvalError");
1885     QTest::newRow("RangeError") << QString("RangeError") << QString("RangeError");
1886     QTest::newRow("ReferenceError") << QString("ReferenceError") << QString("ReferenceError");
1887     QTest::newRow("SyntaxError") << QString("SyntaxError") << QString("SyntaxError");
1888     QTest::newRow("TypeError") << QString("TypeError") << QString("TypeError");
1889     QTest::newRow("URIError") << QString("URIError") << QString("URIError");
1890
1891     QTest::newRow("Function") << QString("Function") << QString("Function");
1892     QTest::newRow("Function.prototype.toString") << QString("Function.prototype.toString") << QString("toString");
1893     QTest::newRow("Function.prototype.apply") << QString("Function.prototype.apply") << QString("apply");
1894     QTest::newRow("Function.prototype.call") << QString("Function.prototype.call") << QString("call");
1895 /*  In V8, those function are only there for signals
1896     QTest::newRow("Function.prototype.connect") << QString("Function.prototype.connect") << QString("connect");
1897     QTest::newRow("Function.prototype.disconnect") << QString("Function.prototype.disconnect") << QString("disconnect");*/
1898
1899     QTest::newRow("Math.abs") << QString("Math.abs") << QString("abs");
1900     QTest::newRow("Math.acos") << QString("Math.acos") << QString("acos");
1901     QTest::newRow("Math.asin") << QString("Math.asin") << QString("asin");
1902     QTest::newRow("Math.atan") << QString("Math.atan") << QString("atan");
1903     QTest::newRow("Math.atan2") << QString("Math.atan2") << QString("atan2");
1904     QTest::newRow("Math.ceil") << QString("Math.ceil") << QString("ceil");
1905     QTest::newRow("Math.cos") << QString("Math.cos") << QString("cos");
1906     QTest::newRow("Math.exp") << QString("Math.exp") << QString("exp");
1907     QTest::newRow("Math.floor") << QString("Math.floor") << QString("floor");
1908     QTest::newRow("Math.log") << QString("Math.log") << QString("log");
1909     QTest::newRow("Math.max") << QString("Math.max") << QString("max");
1910     QTest::newRow("Math.min") << QString("Math.min") << QString("min");
1911     QTest::newRow("Math.pow") << QString("Math.pow") << QString("pow");
1912     QTest::newRow("Math.random") << QString("Math.random") << QString("random");
1913     QTest::newRow("Math.round") << QString("Math.round") << QString("round");
1914     QTest::newRow("Math.sin") << QString("Math.sin") << QString("sin");
1915     QTest::newRow("Math.sqrt") << QString("Math.sqrt") << QString("sqrt");
1916     QTest::newRow("Math.tan") << QString("Math.tan") << QString("tan");
1917
1918     QTest::newRow("Number") << QString("Number") << QString("Number");
1919     QTest::newRow("Number.prototype.toString") << QString("Number.prototype.toString") << QString("toString");
1920     QTest::newRow("Number.prototype.toLocaleString") << QString("Number.prototype.toLocaleString") << QString("toLocaleString");
1921     QTest::newRow("Number.prototype.valueOf") << QString("Number.prototype.valueOf") << QString("valueOf");
1922     QTest::newRow("Number.prototype.toFixed") << QString("Number.prototype.toFixed") << QString("toFixed");
1923     QTest::newRow("Number.prototype.toExponential") << QString("Number.prototype.toExponential") << QString("toExponential");
1924     QTest::newRow("Number.prototype.toPrecision") << QString("Number.prototype.toPrecision") << QString("toPrecision");
1925
1926     QTest::newRow("Object") << QString("Object") << QString("Object");
1927     QTest::newRow("Object.prototype.toString") << QString("Object.prototype.toString") << QString("toString");
1928     QTest::newRow("Object.prototype.toLocaleString") << QString("Object.prototype.toLocaleString") << QString("toLocaleString");
1929     QTest::newRow("Object.prototype.valueOf") << QString("Object.prototype.valueOf") << QString("valueOf");
1930     QTest::newRow("Object.prototype.hasOwnProperty") << QString("Object.prototype.hasOwnProperty") << QString("hasOwnProperty");
1931     QTest::newRow("Object.prototype.isPrototypeOf") << QString("Object.prototype.isPrototypeOf") << QString("isPrototypeOf");
1932     QTest::newRow("Object.prototype.propertyIsEnumerable") << QString("Object.prototype.propertyIsEnumerable") << QString("propertyIsEnumerable");
1933     QTest::newRow("Object.prototype.__defineGetter__") << QString("Object.prototype.__defineGetter__") << QString("__defineGetter__");
1934     QTest::newRow("Object.prototype.__defineSetter__") << QString("Object.prototype.__defineSetter__") << QString("__defineSetter__");
1935
1936     QTest::newRow("RegExp") << QString("RegExp") << QString("RegExp");
1937     QTest::newRow("RegExp.prototype.exec") << QString("RegExp.prototype.exec") << QString("exec");
1938     QTest::newRow("RegExp.prototype.test") << QString("RegExp.prototype.test") << QString("test");
1939     QTest::newRow("RegExp.prototype.toString") << QString("RegExp.prototype.toString") << QString("toString");
1940
1941     QTest::newRow("String") << QString("String") << QString("String");
1942     QTest::newRow("String.prototype.toString") << QString("String.prototype.toString") << QString("toString");
1943     QTest::newRow("String.prototype.valueOf") << QString("String.prototype.valueOf") << QString("valueOf");
1944     QTest::newRow("String.prototype.charAt") << QString("String.prototype.charAt") << QString("charAt");
1945     QTest::newRow("String.prototype.charCodeAt") << QString("String.prototype.charCodeAt") << QString("charCodeAt");
1946     QTest::newRow("String.prototype.concat") << QString("String.prototype.concat") << QString("concat");
1947     QTest::newRow("String.prototype.indexOf") << QString("String.prototype.indexOf") << QString("indexOf");
1948     QTest::newRow("String.prototype.lastIndexOf") << QString("String.prototype.lastIndexOf") << QString("lastIndexOf");
1949     QTest::newRow("String.prototype.localeCompare") << QString("String.prototype.localeCompare") << QString("localeCompare");
1950     QTest::newRow("String.prototype.match") << QString("String.prototype.match") << QString("match");
1951     QTest::newRow("String.prototype.replace") << QString("String.prototype.replace") << QString("replace");
1952     QTest::newRow("String.prototype.search") << QString("String.prototype.search") << QString("search");
1953     QTest::newRow("String.prototype.slice") << QString("String.prototype.slice") << QString("slice");
1954     QTest::newRow("String.prototype.split") << QString("String.prototype.split") << QString("split");
1955     QTest::newRow("String.prototype.substring") << QString("String.prototype.substring") << QString("substring");
1956     QTest::newRow("String.prototype.toLowerCase") << QString("String.prototype.toLowerCase") << QString("toLowerCase");
1957     QTest::newRow("String.prototype.toLocaleLowerCase") << QString("String.prototype.toLocaleLowerCase") << QString("toLocaleLowerCase");
1958     QTest::newRow("String.prototype.toUpperCase") << QString("String.prototype.toUpperCase") << QString("toUpperCase");
1959     QTest::newRow("String.prototype.toLocaleUpperCase") << QString("String.prototype.toLocaleUpperCase") << QString("toLocaleUpperCase");
1960 }
1961
1962 void tst_QJSEngine::builtinFunctionNames()
1963 {
1964     QFETCH(QString, expression);
1965     QFETCH(QString, expectedName);
1966     QJSEngine eng;
1967     // The "name" property is actually non-standard, but JSC supports it.
1968     QJSValue ret = eng.evaluate(QString::fromLatin1("%0.name").arg(expression));
1969     QVERIFY(ret.isString());
1970     QCOMPARE(ret.toString(), expectedName);
1971 }
1972
1973 #if 0 // ###FIXME: No syntax checking result
1974 void tst_QJSEngine::checkSyntax_data()
1975 {
1976     QTest::addColumn<QString>("code");
1977     QTest::addColumn<int>("expectedState");
1978     QTest::addColumn<int>("errorLineNumber");
1979     QTest::addColumn<int>("errorColumnNumber");
1980     QTest::addColumn<QString>("errorMessage");
1981
1982     QTest::newRow("0")
1983         << QString("0") << int(QScriptSyntaxCheckResult::Valid)
1984         << -1 << -1 << "";
1985     QTest::newRow("if (")
1986         << QString("if (\n") << int(QScriptSyntaxCheckResult::Intermediate)
1987         << 0 << -1 << "Uncaught SyntaxError: Unexpected end of input";
1988     QTest::newRow("if else")
1989         << QString("\nif else") << int(QScriptSyntaxCheckResult::Error)
1990         << 2 << 3 << "Uncaught SyntaxError: Unexpected token else";
1991     QTest::newRow("foo[")
1992         << QString("foo[") << int(QScriptSyntaxCheckResult::Intermediate)
1993         << 1 << 4 << "Uncaught SyntaxError: Unexpected end of input";
1994     QTest::newRow("foo['bar']")
1995         << QString("foo['bar']") << int(QScriptSyntaxCheckResult::Valid)
1996         << -1 << -1 << "";
1997
1998     QTest::newRow("/*")
1999         << QString("/*") << int(QScriptSyntaxCheckResult::Error)
2000         << 1 << 0 << "Uncaught SyntaxError: Unexpected token ILLEGAL";
2001     QTest::newRow("/*\nMy comment")
2002         << QString("/*\nMy comment") << int(QScriptSyntaxCheckResult::Error)
2003         << 1 << 0 << "Uncaught SyntaxError: Unexpected token ILLEGAL";
2004     QTest::newRow("/*\nMy comment */\nfoo = 10")
2005         << QString("/*\nMy comment */\nfoo = 10") << int(QScriptSyntaxCheckResult::Valid)
2006         << -1 << -1 << "";
2007     QTest::newRow("foo = 10 /*")
2008         << QString("foo = 10 /*") << int(QScriptSyntaxCheckResult::Error)
2009         << 1 << 9 << "Uncaught SyntaxError: Unexpected token ILLEGAL";
2010     QTest::newRow("foo = 10; /*")
2011         << QString("foo = 10; /*") << int(QScriptSyntaxCheckResult::Error)
2012         << 1 << 10 << "Uncaught SyntaxError: Unexpected token ILLEGAL";
2013     QTest::newRow("foo = 10 /* My comment */")
2014         << QString("foo = 10 /* My comment */") << int(QScriptSyntaxCheckResult::Valid)
2015         << -1 << -1 << "";
2016
2017     QTest::newRow("/=/")
2018         << QString("/=/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
2019     QTest::newRow("/=/g")
2020         << QString("/=/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
2021     QTest::newRow("/a/")
2022         << QString("/a/") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
2023     QTest::newRow("/a/g")
2024         << QString("/a/g") << int(QScriptSyntaxCheckResult::Valid) << -1 << -1 << "";
2025 }
2026
2027 void tst_QJSEngine::checkSyntax()
2028 {
2029     QFETCH(QString, code);
2030     QFETCH(int, expectedState);
2031     QFETCH(int, errorLineNumber);
2032     QFETCH(int, errorColumnNumber);
2033     QFETCH(QString, errorMessage);
2034
2035     QScriptSyntaxCheckResult result = QScriptEngine::checkSyntax(code);
2036     QCOMPARE(int(result.state()), expectedState);
2037     QCOMPARE(result.errorLineNumber(), errorLineNumber);
2038     QCOMPARE(result.errorColumnNumber(), errorColumnNumber);
2039     QCOMPARE(result.errorMessage(), errorMessage);
2040
2041     // assignment
2042     {
2043         QScriptSyntaxCheckResult copy = result;
2044         QCOMPARE(copy.state(), result.state());
2045         QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
2046         QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
2047         QCOMPARE(copy.errorMessage(), result.errorMessage());
2048     }
2049     {
2050         QScriptSyntaxCheckResult copy(result);
2051         QCOMPARE(copy.state(), result.state());
2052         QCOMPARE(copy.errorLineNumber(), result.errorLineNumber());
2053         QCOMPARE(copy.errorColumnNumber(), result.errorColumnNumber());
2054         QCOMPARE(copy.errorMessage(), result.errorMessage());
2055     }
2056 }
2057 #endif
2058
2059 #if 0 // ###FIXME: No support for canEvaluate
2060 void tst_QJSEngine::canEvaluate_data()
2061 {
2062     QTest::addColumn<QString>("code");
2063     QTest::addColumn<bool>("expectSuccess");
2064
2065     QTest::newRow("") << QString("") << true;
2066     QTest::newRow("0") << QString("0") << true;
2067     QTest::newRow("!") << QString("!\n") << false;
2068     QTest::newRow("if (") << QString("if (\n") << false;
2069     QTest::newRow("if (10) //") << QString("if (10) //\n") << false;
2070     QTest::newRow("a = 1; if (") << QString("a = 1;\nif (\n") << false;
2071     QTest::newRow("./test.js") << QString("./test.js\n") << true;
2072     QTest::newRow("if (0) print(1)") << QString("if (0)\nprint(1)\n") << true;
2073     QTest::newRow("0 = ") << QString("0 = \n") << false;
2074     QTest::newRow("0 = 0") << QString("0 = 0\n") << true;
2075     QTest::newRow("foo[") << QString("foo[") << false;
2076     QTest::newRow("foo[") << QString("foo[\n") << false;
2077     QTest::newRow("foo['bar']") << QString("foo['bar']") << true;
2078
2079     //v8 does thinks unterminated comments are error rather than unfinished
2080 //    QTest::newRow("/*") << QString("/*") << false;
2081 //    QTest::newRow("/*\nMy comment") << QString("/*\nMy comment") << false;
2082     QTest::newRow("/*\nMy comment */\nfoo = 10") << QString("/*\nMy comment */\nfoo = 10") << true;
2083 //    QTest::newRow("foo = 10 /*") << QString("foo = 10 /*") << false;
2084 //    QTest::newRow("foo = 10; /*") << QString("foo = 10; /*") << false;
2085     QTest::newRow("foo = 10 /* My comment */") << QString("foo = 10 /* My comment */") << true;
2086
2087     QTest::newRow("/=/") << QString("/=/") << true;
2088     QTest::newRow("/=/g") << QString("/=/g") << true;
2089     QTest::newRow("/a/") << QString("/a/") << true;
2090     QTest::newRow("/a/g") << QString("/a/g") << true;
2091 }
2092
2093 void tst_QJSEngine::canEvaluate()
2094 {
2095     QFETCH(QString, code);
2096     QFETCH(bool, expectSuccess);
2097
2098     QScriptEngine eng;
2099     QCOMPARE(eng.canEvaluate(code), expectSuccess);
2100 }
2101 #endif
2102
2103 void tst_QJSEngine::evaluate_data()
2104 {
2105     QTest::addColumn<QString>("code");
2106     QTest::addColumn<int>("lineNumber");
2107     QTest::addColumn<bool>("expectHadError");
2108     QTest::addColumn<int>("expectErrorLineNumber");
2109
2110     QTest::newRow("(newline)") << QString("\n") << -1 << false << -1;
2111     QTest::newRow("0 //")   << QString("0 //") << -1 << false << -1;
2112     QTest::newRow("/* */")   << QString("/* */") << -1 << false << -1;
2113     QTest::newRow("//") << QString("//") << -1 << false << -1;
2114     QTest::newRow("(spaces)")  << QString("  ") << -1 << false << -1;
2115     QTest::newRow("(empty)")   << QString("") << -1 << false << -1;
2116     QTest::newRow("0")     << QString("0")       << -1 << false << -1;
2117     QTest::newRow("0=1")   << QString("\n0=1;\n") << -1 << true  << 2;
2118     QTest::newRow("a=1")   << QString("a=1\n")   << -1 << false << -1;
2119     QTest::newRow("a=1;K") << QString("a=1;\nK") << -1 << true  << 2;
2120
2121     QTest::newRow("f()") << QString("function f()\n"
2122                                     "{\n"
2123                                     "  var a;\n"
2124                                     "  var b=\";\n" // here's the error
2125                                     "}\n"
2126                                     "f();\n")
2127                          << -1 << true << 4;
2128
2129     QTest::newRow("0")     << QString("0")       << 10 << false << -1;
2130     QTest::newRow("0=1")   << QString("\n\n0=1\n") << 10 << true  << 12;
2131     QTest::newRow("a=1")   << QString("a=1\n")   << 10 << false << -1;
2132     QTest::newRow("a=1;K") << QString("a=1;\n\nK") << 10 << true  << 12;
2133
2134     QTest::newRow("f()") << QString("function f()\n"
2135                                     "{\n"
2136                                     "  var a;\n"
2137                                     "\n\n"
2138                                     "  var b=\";\n" // here's the error
2139                                     "}\n"
2140                                     "f();\n")
2141                          << 10 << true << 15;
2142     QTest::newRow("functionThatDoesntExist()")
2143         << QString(";\n;\n;\nfunctionThatDoesntExist()")
2144         << -1 << true << 4;
2145     QTest::newRow("for (var p in this) { continue labelThatDoesntExist; }")
2146         << QString("for (var p in this) {\ncontinue labelThatDoesntExist; }")
2147         << 4 << true << 5;
2148     QTest::newRow("duplicateLabel: { duplicateLabel: ; }")
2149         << QString("duplicateLabel: { duplicateLabel: ; }")
2150         << 12 << true << 12;
2151
2152     QTest::newRow("/=/") << QString("/=/") << -1 << false << -1;
2153     QTest::newRow("/=/g") << QString("/=/g") << -1 << false << -1;
2154     QTest::newRow("/a/") << QString("/a/") << -1 << false << -1;
2155     QTest::newRow("/a/g") << QString("/a/g") << -1 << false << -1;
2156     QTest::newRow("/a/gim") << QString("/a/gim") << -1 << false << -1;
2157     QTest::newRow("/a/gimp") << QString("/a/gimp") << 1 << true << 1;
2158 }
2159
2160 void tst_QJSEngine::evaluate()
2161 {
2162     QFETCH(QString, code);
2163     QFETCH(int, lineNumber);
2164     QFETCH(bool, expectHadError);
2165     QFETCH(int, expectErrorLineNumber);
2166
2167     QJSEngine eng;
2168     QJSValue ret;
2169     if (lineNumber != -1)
2170         ret = eng.evaluate(code, /*fileName =*/QString(), lineNumber);
2171     else
2172         ret = eng.evaluate(code);
2173     QEXPECT_FAIL("/a/gimp", "v8 ignore invalid flags", Abort);
2174     QCOMPARE(eng.hasUncaughtException(), expectHadError);
2175 #if 0 // ###FIXME: No support for the line number of an uncaught exception
2176     QEXPECT_FAIL("f()", "SyntaxError do not report line number", Continue);
2177     QEXPECT_FAIL("duplicateLabel: { duplicateLabel: ; }", "SyntaxError do not report line number", Continue);
2178     QCOMPARE(eng.uncaughtExceptionLineNumber(), expectErrorLineNumber);
2179 #endif
2180     if (eng.hasUncaughtException() && ret.isError()) {
2181         QEXPECT_FAIL("", "we have no more lineNumber property ", Continue);
2182         QVERIFY(ret.property("lineNumber").strictlyEquals(QJSValue(&eng, expectErrorLineNumber)));
2183     } else {
2184 #if 0 // ###FIXME: No support for the backtrace of an uncaught exception
2185         QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
2186 #endif
2187     }
2188 }
2189
2190 #if 0 // ###FIXME: no support for c-style callbacks
2191 static QScriptValue eval_nested(QScriptContext *ctx, QScriptEngine *eng)
2192 {
2193     QScriptValue result = eng->newObject();
2194     eng->evaluate("var bar = 'local';");
2195     result.setProperty("thisObjectIdBefore", ctx->thisObject().property("id"));
2196     QScriptValue evaluatedThisObject = eng->evaluate("this");
2197     result.setProperty("thisObjectIdAfter", ctx->thisObject().property("id"));
2198     result.setProperty("evaluatedThisObjectId", evaluatedThisObject.property("id"));
2199     result.setProperty("local_bar", eng->evaluate("bar"));
2200
2201     return result;
2202 }
2203
2204 // Tests that nested evaluate uses the "this" that was passed.
2205 void tst_QJSEngine::nestedEvaluate()
2206 {
2207     QScriptEngine eng;
2208     QScriptValue fun = eng.newFunction(eval_nested);
2209     eng.globalObject().setProperty("fun", fun);
2210     // From JS function call
2211     {
2212         QScriptValue result = eng.evaluate("o = { id:'foo'}; o.fun = fun; o.fun()");
2213         QCOMPARE(result.property("local_bar").toString(), QString("local"));
2214         QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
2215         QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
2216         QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
2217         QScriptValue bar = eng.evaluate("bar"); // Was introduced in local scope.
2218         QVERIFY(bar.isError());
2219         QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
2220     }
2221     // From QScriptValue::call()
2222     {
2223         QScriptValue result = fun.call(eng.evaluate("p = { id:'foo' }") , QScriptValueList() );
2224         QCOMPARE(result.property("local_bar").toString(), QString("local"));
2225         QCOMPARE(result.property("thisObjectIdBefore").toString(), QString("foo"));
2226         QCOMPARE(result.property("thisObjectIdAfter").toString(), QString("foo"));
2227         QCOMPARE(result.property("evaluatedThisObjectId").toString(), QString("foo"));
2228         QScriptValue bar = eng.evaluate("bar");
2229         QVERIFY(bar.isError());
2230         QVERIFY(bar.toString().contains(QString::fromLatin1("ReferenceError")));
2231     }
2232 }
2233 #endif
2234
2235 #if 0 // ### FIXME: No c-style callbacks
2236 void tst_QJSEngine::uncaughtException()
2237 {
2238     QScriptEngine eng;
2239     QScriptValue fun = eng.newFunction(myFunction);
2240     QScriptValue throwFun = eng.newFunction(myThrowingFunction);
2241     for (int x = -1; x < 2; ++x) {
2242         {
2243             QScriptValue ret = eng.evaluate("a = 10;\nb = 20;\n0 = 0;\n", /*fileName=*/QString(), /*lineNumber=*/x);
2244             QVERIFY(eng.hasUncaughtException());
2245             QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
2246             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
2247             (void)ret.toString();
2248             QVERIFY(eng.hasUncaughtException());
2249             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
2250             QVERIFY(fun.call().isNull());
2251             QVERIFY(eng.hasUncaughtException());
2252             QCOMPARE(eng.uncaughtExceptionLineNumber(), x+2);
2253             QVERIFY(eng.uncaughtException().strictlyEquals(ret));
2254             eng.clearExceptions();
2255             QVERIFY(!eng.hasUncaughtException());
2256             QCOMPARE(eng.uncaughtExceptionLineNumber(), -1);
2257             QVERIFY(!eng.uncaughtException().isValid());
2258
2259             eng.evaluate("2 = 3");
2260             QVERIFY(eng.hasUncaughtException());
2261             QScriptValue ret2 = throwFun.call();
2262             QVERIFY(ret2.isError());
2263             QVERIFY(eng.hasUncaughtException());
2264             QVERIFY(eng.uncaughtException().strictlyEquals(ret2));
2265             QCOMPARE(eng.uncaughtExceptionLineNumber(), 0);
2266             eng.clearExceptions();
2267             QVERIFY(!eng.hasUncaughtException());
2268             eng.evaluate("1 + 2");
2269             QVERIFY(!eng.hasUncaughtException());
2270         }
2271         {
2272             QScriptValue ret = eng.evaluate("a = 10");
2273             QVERIFY(!eng.hasUncaughtException());
2274             QVERIFY(!eng.uncaughtException().isValid());
2275         }
2276         {
2277             QScriptValue ret = eng.evaluate("1 = 2");
2278             QVERIFY(eng.hasUncaughtException());
2279             eng.clearExceptions();
2280             QVERIFY(!eng.hasUncaughtException());
2281         }
2282         {
2283             eng.globalObject().setProperty("throwFun", throwFun);
2284             eng.evaluate("1;\nthrowFun();");
2285             QVERIFY(eng.hasUncaughtException());
2286             QCOMPARE(eng.uncaughtExceptionLineNumber(), 2);
2287             eng.clearExceptions();
2288             QVERIFY(!eng.hasUncaughtException());
2289         }
2290     }
2291 }
2292 #endif
2293
2294 void tst_QJSEngine::errorMessage_QT679()
2295 {
2296     QJSEngine engine;
2297     engine.globalObject().setProperty("foo", 15);
2298     QJSValue error = engine.evaluate("'hello world';\nfoo.bar.blah");
2299     QVERIFY(error.isError());
2300     QVERIFY(error.toString().startsWith(QString::fromLatin1("TypeError: ")));
2301 }
2302
2303 struct Foo {
2304 public:
2305     int x, y;
2306     Foo() : x(-1), y(-1) { }
2307 };
2308
2309 Q_DECLARE_METATYPE(Foo)
2310 Q_DECLARE_METATYPE(Foo*)
2311
2312 #if 0 // FIXME: No prototype API in QScriptEngine
2313 void tst_QJSEngine::getSetDefaultPrototype_int()
2314 {
2315     QScriptEngine eng;
2316
2317     QScriptValue object = eng.newObject();
2318     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
2319     eng.setDefaultPrototype(qMetaTypeId<int>(), object);
2320     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).strictlyEquals(object), true);
2321     QScriptValue value = eng.newVariant(int(123));
2322     QCOMPARE(value.prototype().isObject(), true);
2323     QCOMPARE(value.prototype().strictlyEquals(object), true);
2324
2325     eng.setDefaultPrototype(qMetaTypeId<int>(), QScriptValue());
2326     QCOMPARE(eng.defaultPrototype(qMetaTypeId<int>()).isValid(), false);
2327     QScriptValue value2 = eng.newVariant(int(123));
2328     QCOMPARE(value2.prototype().strictlyEquals(object), false);
2329 }
2330
2331 void tst_QJSEngine::getSetDefaultPrototype_customType()
2332 {
2333     QScriptEngine eng;
2334
2335     QScriptValue object = eng.newObject();
2336     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
2337     eng.setDefaultPrototype(qMetaTypeId<Foo>(), object);
2338     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(object), true);
2339     QScriptValue value = eng.newVariant(qVariantFromValue(Foo()));
2340     QCOMPARE(value.prototype().isObject(), true);
2341     QCOMPARE(value.prototype().strictlyEquals(object), true);
2342
2343     eng.setDefaultPrototype(qMetaTypeId<Foo>(), QScriptValue());
2344     QCOMPARE(eng.defaultPrototype(qMetaTypeId<Foo>()).isValid(), false);
2345     QScriptValue value2 = eng.newVariant(qVariantFromValue(Foo()));
2346     QCOMPARE(value2.prototype().strictlyEquals(object), false);
2347 }
2348 #endif
2349
2350 static QJSValue fooToScriptValue(QJSEngine *eng, const Foo &foo)
2351 {
2352     QJSValue obj = eng->newObject();
2353     obj.setProperty("x", QJSValue(eng, foo.x));
2354     obj.setProperty("y", QJSValue(eng, foo.y));
2355     return obj;
2356 }
2357
2358 static void fooFromScriptValue(const QJSValue &value, Foo &foo)
2359 {
2360     foo.x = value.property("x").toInt32();
2361     foo.y = value.property("y").toInt32();
2362 }
2363
2364 static QJSValue fooToScriptValueV2(QJSEngine *eng, const Foo &foo)
2365 {
2366     return QJSValue(eng, foo.x);
2367 }
2368
2369 static void fooFromScriptValueV2(const QJSValue &value, Foo &foo)
2370 {
2371     foo.x = value.toInt32();
2372 }
2373
2374 Q_DECLARE_METATYPE(QLinkedList<QString>)
2375 Q_DECLARE_METATYPE(QList<Foo>)
2376 Q_DECLARE_METATYPE(QVector<QChar>)
2377 Q_DECLARE_METATYPE(QStack<int>)
2378 Q_DECLARE_METATYPE(QQueue<char>)
2379 Q_DECLARE_METATYPE(QLinkedList<QStack<int> >)
2380
2381 void tst_QJSEngine::valueConversion_basic()
2382 {
2383     QJSEngine eng;
2384     {
2385         QJSValue num = eng.toScriptValue(123);
2386         QCOMPARE(num.isNumber(), true);
2387         QCOMPARE(num.strictlyEquals(QJSValue(&eng, 123)), true);
2388
2389         int inum = eng.fromScriptValue<int>(num);
2390         QCOMPARE(inum, 123);
2391
2392         QString snum = eng.fromScriptValue<QString>(num);
2393         QCOMPARE(snum, QLatin1String("123"));
2394     }
2395     {
2396         QJSValue num = eng.toScriptValue(123);
2397         QCOMPARE(num.isNumber(), true);
2398         QCOMPARE(num.strictlyEquals(QJSValue(&eng, 123)), true);
2399
2400         int inum = eng.fromScriptValue<int>(num);
2401         QCOMPARE(inum, 123);
2402
2403         QString snum = eng.fromScriptValue<QString>(num);
2404         QCOMPARE(snum, QLatin1String("123"));
2405     }
2406     {
2407         QJSValue num(&eng, 123);
2408         QCOMPARE(eng.fromScriptValue<char>(num), char(123));
2409         QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123));
2410         QCOMPARE(eng.fromScriptValue<short>(num), short(123));
2411         QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123));
2412         QCOMPARE(eng.fromScriptValue<float>(num), float(123));
2413         QCOMPARE(eng.fromScriptValue<double>(num), double(123));
2414         QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123));
2415         QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123));
2416     }
2417     {
2418         QJSValue num(123);
2419         QCOMPARE(eng.fromScriptValue<char>(num), char(123));
2420         QCOMPARE(eng.fromScriptValue<unsigned char>(num), (unsigned char)(123));
2421         QCOMPARE(eng.fromScriptValue<short>(num), short(123));
2422         QCOMPARE(eng.fromScriptValue<unsigned short>(num), (unsigned short)(123));
2423         QCOMPARE(eng.fromScriptValue<float>(num), float(123));
2424         QCOMPARE(eng.fromScriptValue<double>(num), double(123));
2425         QCOMPARE(eng.fromScriptValue<qlonglong>(num), qlonglong(123));
2426         QCOMPARE(eng.fromScriptValue<qulonglong>(num), qulonglong(123));
2427     }
2428
2429     {
2430         QJSValue num = eng.toScriptValue(Q_INT64_C(0x100000000));
2431         QCOMPARE(eng.fromScriptValue<qlonglong>(num), Q_INT64_C(0x100000000));
2432         QCOMPARE(eng.fromScriptValue<qulonglong>(num), Q_UINT64_C(0x100000000));
2433     }
2434
2435     {
2436         QChar c = QLatin1Char('c');
2437         QJSValue str = QJSValue(&eng, QLatin1String("ciao"));
2438         QCOMPARE(eng.fromScriptValue<QChar>(str), c);
2439         QJSValue code = QJSValue(&eng, c.unicode());
2440         QCOMPARE(eng.fromScriptValue<QChar>(code), c);
2441         QCOMPARE(eng.fromScriptValue<QChar>(eng.toScriptValue(c)), c);
2442     }
2443 }
2444
2445 #if 0 // FIXME: No API for custom types
2446 void tst_QJSEngine::valueConversion_customType()
2447 {
2448     QScriptEngine eng;
2449     {
2450         // a type that we don't have built-in conversion of
2451         // (it's stored as a variant)
2452         QTime tm(1, 2, 3, 4);
2453         QScriptValue val = eng.toScriptValue(tm);
2454         QCOMPARE(eng.fromScriptValue<QTime>(val), tm);
2455     }
2456
2457     {
2458         Foo foo;
2459         foo.x = 12;
2460         foo.y = 34;
2461         QScriptValue fooVal = eng.toScriptValue(foo);
2462         QCOMPARE(fooVal.isVariant(), true);
2463
2464         Foo foo2 = eng.fromScriptValue<Foo>(fooVal);
2465         QCOMPARE(foo2.x, foo.x);
2466         QCOMPARE(foo2.y, foo.y);
2467     }
2468
2469     qScriptRegisterMetaType<Foo>(&eng, fooToScriptValue, fooFromScriptValue);
2470
2471     {
2472         Foo foo;
2473         foo.x = 12;
2474         foo.y = 34;
2475         QScriptValue fooVal = eng.toScriptValue(foo);
2476         QCOMPARE(fooVal.isObject(), true);
2477         QVERIFY(fooVal.prototype().strictlyEquals(eng.evaluate("Object.prototype")));
2478         QCOMPARE(fooVal.property("x").strictlyEquals(QScriptValue(&eng, 12)), true);
2479         QCOMPARE(fooVal.property("y").strictlyEquals(QScriptValue(&eng, 34)), true);
2480         fooVal.setProperty("x", QScriptValue(&eng, 56));
2481         fooVal.setProperty("y", QScriptValue(&eng, 78));
2482
2483         Foo foo2 = eng.fromScriptValue<Foo>(fooVal);
2484         QCOMPARE(foo2.x, 56);
2485         QCOMPARE(foo2.y, 78);
2486
2487         QScriptValue fooProto = eng.newObject();
2488         eng.setDefaultPrototype(qMetaTypeId<Foo>(), fooProto);
2489         QScriptValue fooVal2 = eng.toScriptValue(foo2);
2490         QVERIFY(fooVal2.prototype().strictlyEquals(fooProto));
2491         QVERIFY(fooVal2.property("x").strictlyEquals(QScriptValue(&eng, 56)));
2492         QVERIFY(fooVal2.property("y").strictlyEquals(QScriptValue(&eng, 78)));
2493     }
2494 }
2495
2496 void tst_QJSEngine::valueConversion_sequence()
2497 {
2498     QScriptEngine eng;
2499     qScriptRegisterSequenceMetaType<QLinkedList<QString> >(&eng);
2500
2501     {
2502         QLinkedList<QString> lst;
2503         lst << QLatin1String("foo") << QLatin1String("bar");
2504         QScriptValue lstVal = eng.toScriptValue(lst);
2505         QCOMPARE(lstVal.isArray(), true);
2506         QCOMPARE(lstVal.property("length").toInt32(), 2);
2507         QCOMPARE(lstVal.property("0").isString(), true);
2508         QCOMPARE(lstVal.property("0").toString(), QLatin1String("foo"));
2509         QCOMPARE(lstVal.property("1").isString(), true);
2510         QCOMPARE(lstVal.property("1").toString(), QLatin1String("bar"));
2511     }
2512
2513     qScriptRegisterSequenceMetaType<QList<Foo> >(&eng);
2514     qScriptRegisterSequenceMetaType<QStack<int> >(&eng);
2515     qScriptRegisterSequenceMetaType<QVector<QChar> >(&eng);
2516     qScriptRegisterSequenceMetaType<QQueue<char> >(&eng);
2517     qScriptRegisterSequenceMetaType<QLinkedList<QStack<int> > >(&eng);
2518
2519     {
2520         QLinkedList<QStack<int> > lst;
2521         QStack<int> first; first << 13 << 49; lst << first;
2522         QStack<int> second; second << 99999;lst << second;
2523         QScriptValue lstVal = eng.toScriptValue(lst);
2524         QCOMPARE(lstVal.isArray(), true);
2525         QCOMPARE(lstVal.property("length").toInt32(), 2);
2526         QCOMPARE(lstVal.property("0").isArray(), true);
2527         QCOMPARE(lstVal.property("0").property("length").toInt32(), 2);
2528         QCOMPARE(lstVal.property("0").property("0").toInt32(), first.at(0));
2529         QCOMPARE(lstVal.property("0").property("1").toInt32(), first.at(1));
2530         QCOMPARE(lstVal.property("1").isArray(), true);
2531         QCOMPARE(lstVal.property("1").property("length").toInt32(), 1);
2532         QCOMPARE(lstVal.property("1").property("0").toInt32(), second.at(0));
2533         QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("0")), first);
2534         QCOMPARE(qscriptvalue_cast<QStack<int> >(lstVal.property("1")), second);
2535         QCOMPARE(qscriptvalue_cast<QLinkedList<QStack<int> > >(lstVal), lst);
2536     }
2537
2538     // pointers
2539     {
2540         Foo foo;
2541         {
2542             QScriptValue v = eng.toScriptValue(&foo);
2543             Foo *pfoo = qscriptvalue_cast<Foo*>(v);
2544             QCOMPARE(pfoo, &foo);
2545         }
2546         {
2547             Foo *pfoo = 0;
2548             QScriptValue v = eng.toScriptValue(pfoo);
2549             QCOMPARE(v.isNull(), true);
2550             QVERIFY(qscriptvalue_cast<Foo*>(v) == 0);
2551         }
2552     }
2553
2554     // QList<int> and QObjectList should be converted from/to arrays by default
2555     {
2556         QList<int> lst;
2557         lst << 1 << 2 << 3;
2558         QScriptValue val = eng.toScriptValue(lst);
2559         QVERIFY(val.isArray());
2560         QCOMPARE(val.property("length").toInt32(), lst.size());
2561         QCOMPARE(val.property(0).toInt32(), lst.at(0));
2562         QCOMPARE(val.property(1).toInt32(), lst.at(1));
2563         QCOMPARE(val.property(2).toInt32(), lst.at(2));
2564
2565         QCOMPARE(qscriptvalue_cast<QList<int> >(val), lst);
2566     }
2567     {
2568         QObjectList lst;
2569         lst << this;
2570         QScriptValue val = eng.toScriptValue(lst);
2571         QVERIFY(val.isArray());
2572         QCOMPARE(val.property("length").toInt32(), lst.size());
2573         QCOMPARE(val.property(0).toQObject(), (QObject *)this);
2574
2575         QCOMPARE(qscriptvalue_cast<QObjectList>(val), lst);
2576     }
2577 }
2578 #endif
2579
2580 void tst_QJSEngine::valueConversion_QVariant()
2581 {
2582     QJSEngine eng;
2583     // qScriptValueFromValue() should be "smart" when the argument is a QVariant
2584     {
2585         QJSValue val = eng.toScriptValue(QVariant());
2586         QVERIFY(!val.isVariant());
2587         QVERIFY(val.isUndefined());
2588     }
2589     // Checking nested QVariants
2590     {
2591         QVariant tmp1;
2592         QVariant tmp2(QMetaType::QVariant, &tmp1);
2593         QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);
2594
2595         QJSValue val1 = eng.toScriptValue(tmp1);
2596         QJSValue val2 = eng.toScriptValue(tmp2);
2597         QVERIFY(val1.isValid());
2598         QVERIFY(val2.isValid());
2599         QVERIFY(val1.isUndefined());
2600         QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
2601         QVERIFY(!val2.isUndefined());
2602         QVERIFY(!val1.isVariant());
2603         QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
2604         QVERIFY(val2.isVariant());
2605     }
2606     {
2607         QVariant tmp1(123);
2608         QVariant tmp2(QMetaType::QVariant, &tmp1);
2609         QVariant tmp3(QMetaType::QVariant, &tmp2);
2610         QVERIFY(QMetaType::Type(tmp1.type()) == QMetaType::Int);
2611         QVERIFY(QMetaType::Type(tmp2.type()) == QMetaType::QVariant);
2612         QVERIFY(QMetaType::Type(tmp3.type()) == QMetaType::QVariant);
2613
2614         QJSValue val1 = eng.toScriptValue(tmp2);
2615         QJSValue val2 = eng.toScriptValue(tmp3);
2616         QVERIFY(val1.isValid());
2617         QVERIFY(val2.isValid());
2618         QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
2619         QVERIFY(val1.isVariant());
2620         QEXPECT_FAIL("", "Variant are unrwapped, maybe we should not...", Continue);
2621         QVERIFY(val2.isVariant());
2622         QVERIFY(val1.toVariant().toInt() == 123);
2623         QVERIFY(eng.toScriptValue(val2.toVariant()).toVariant().toInt() == 123);
2624     }
2625     {
2626         QJSValue val = eng.toScriptValue(QVariant(true));
2627         QVERIFY(!val.isVariant());
2628         QVERIFY(val.isBoolean());
2629         QCOMPARE(val.toBoolean(), true);
2630     }
2631     {
2632         QJSValue val = eng.toScriptValue(QVariant(int(123)));
2633         QVERIFY(!val.isVariant());
2634         QVERIFY(val.isNumber());
2635         QCOMPARE(val.toNumber(), qreal(123));
2636     }
2637     {
2638         QJSValue val = eng.toScriptValue(QVariant(qreal(1.25)));
2639         QVERIFY(!val.isVariant());
2640         QVERIFY(val.isNumber());
2641         QCOMPARE(val.toNumber(), qreal(1.25));
2642     }
2643     {
2644         QString str = QString::fromLatin1("ciao");
2645         QJSValue val = eng.toScriptValue(QVariant(str));
2646         QVERIFY(!val.isVariant());
2647         QVERIFY(val.isString());
2648         QCOMPARE(val.toString(), str);
2649     }
2650     {
2651         QJSValue val = eng.toScriptValue(qVariantFromValue((QObject*)this));
2652         QVERIFY(!val.isVariant());
2653         QVERIFY(val.isQObject());
2654         QCOMPARE(val.toQObject(), (QObject*)this);
2655     }
2656     {
2657         QVariant var = qVariantFromValue(QPoint(123, 456));
2658         QJSValue val = eng.toScriptValue(var);
2659         QVERIFY(val.isVariant());
2660         QCOMPARE(val.toVariant(), var);
2661     }
2662
2663     QCOMPARE(qjsvalue_cast<QVariant>(QJSValue(123)), QVariant(123));
2664 }
2665
2666 #if 0 // FIXME: No support for custom types
2667 void tst_QJSEngine::valueConversion_hooliganTask248802()
2668 {
2669     QScriptEngine eng;
2670     qScriptRegisterMetaType<Foo>(&eng, fooToScriptValueV2, fooFromScriptValueV2);
2671     {
2672         QScriptValue num(&eng, 123);
2673         Foo foo = eng.fromScriptValue<Foo>(num);
2674         QCOMPARE(foo.x, 123);
2675     }
2676     {
2677         QScriptValue num(123);
2678         Foo foo = eng.fromScriptValue<Foo>(num);
2679         QCOMPARE(foo.x, -1);
2680     }
2681     {
2682         QScriptValue str(&eng, QLatin1String("123"));
2683         Foo foo = eng.fromScriptValue<Foo>(str);
2684         QCOMPARE(foo.x, 123);
2685     }
2686
2687 }
2688 #endif
2689
2690 void tst_QJSEngine::valueConversion_basic2()
2691 {
2692     QJSEngine eng;
2693     // more built-in types
2694     {
2695         QJSValue val = eng.toScriptValue(uint(123));
2696         QVERIFY(val.isNumber());
2697         QCOMPARE(val.toInt32(), 123);
2698     }
2699     {
2700         QJSValue val = eng.toScriptValue(qulonglong(123));
2701         QVERIFY(val.isNumber());
2702         QCOMPARE(val.toInt32(), 123);
2703     }
2704     {
2705         QJSValue val = eng.toScriptValue(float(123));
2706         QVERIFY(val.isNumber());
2707         QCOMPARE(val.toInt32(), 123);
2708     }
2709     {
2710         QJSValue val = eng.toScriptValue(short(123));
2711         QVERIFY(val.isNumber());
2712         QCOMPARE(val.toInt32(), 123);
2713     }
2714     {
2715         QJSValue val = eng.toScriptValue(ushort(123));
2716         QVERIFY(val.isNumber());
2717         QCOMPARE(val.toInt32(), 123);
2718     }
2719     {
2720         QJSValue val = eng.toScriptValue(char(123));
2721         QVERIFY(val.isNumber());
2722         QCOMPARE(val.toInt32(), 123);
2723     }
2724     {
2725         QJSValue val = eng.toScriptValue(uchar(123));
2726         QVERIFY(val.isNumber());
2727         QCOMPARE(val.toInt32(), 123);
2728     }
2729 }
2730
2731 void tst_QJSEngine::valueConversion_dateTime()
2732 {
2733     QJSEngine eng;
2734     {
2735         QDateTime in = QDateTime::currentDateTime();
2736         QJSValue val = eng.toScriptValue(in);
2737         QVERIFY(val.isDate());
2738         QCOMPARE(val.toDateTime(), in);
2739     }
2740     {
2741         QDate in = QDate::currentDate();
2742         QJSValue val = eng.toScriptValue(in);
2743         QVERIFY(val.isDate());
2744         QCOMPARE(val.toDateTime().date(), in);
2745     }
2746 }
2747
2748 void tst_QJSEngine::valueConversion_regExp()
2749 {
2750     QJSEngine eng;
2751     {
2752         QRegExp in = QRegExp("foo");
2753         QJSValue val = eng.toScriptValue(in);
2754         QVERIFY(val.isRegExp());
2755         QRegExp out = val.toRegExp();
2756         QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::patternSyntax (always uses RegExp2)", Continue);
2757         QCOMPARE(out.patternSyntax(), in.patternSyntax());
2758         QCOMPARE(out.pattern(), in.pattern());
2759         QCOMPARE(out.caseSensitivity(), in.caseSensitivity());
2760         QCOMPARE(out.isMinimal(), in.isMinimal());
2761     }
2762     {
2763         QRegExp in = QRegExp("foo", Qt::CaseSensitive, QRegExp::RegExp2);
2764         QJSValue val = eng.toScriptValue(in);
2765         QVERIFY(val.isRegExp());
2766         QCOMPARE(val.toRegExp(), in);
2767     }
2768     {
2769         QRegExp in = QRegExp("foo");
2770         in.setMinimal(true);
2771         QJSValue val = eng.toScriptValue(in);
2772         QVERIFY(val.isRegExp());
2773         QEXPECT_FAIL("", "QTBUG-6136: JSC-based back-end doesn't preserve QRegExp::minimal (always false)", Continue);
2774         QCOMPARE(val.toRegExp().isMinimal(), in.isMinimal());
2775     }
2776 }
2777
2778 #if 0 // FIXME: No qScriptValueFromValue
2779 void tst_QJSEngine::qScriptValueFromValue_noEngine()
2780 {
2781     QVERIFY(!qScriptValueFromValue(0, 123).isValid());
2782     QVERIFY(!qScriptValueFromValue(0, QVariant(123)).isValid());
2783 }
2784 #endif
2785
2786 #if 0 // ###FIXME: No QScriptContext
2787 static QScriptValue __import__(QScriptContext *ctx, QScriptEngine *eng)
2788 {
2789     return eng->importExtension(ctx->argument(0).toString());
2790 }
2791
2792 void tst_QJSEngine::importExtension()
2793 {
2794     QStringList libPaths = QCoreApplication::instance()->libraryPaths();
2795     QCoreApplication::instance()->setLibraryPaths(QStringList() << SRCDIR);
2796
2797     QStringList availableExtensions;
2798     {
2799         QScriptEngine eng;
2800         QVERIFY(eng.importedExtensions().isEmpty());
2801         QStringList ret = eng.availableExtensions();
2802         QCOMPARE(ret.size(), 4);
2803         QCOMPARE(ret.at(0), QString::fromLatin1("com"));
2804         QCOMPARE(ret.at(1), QString::fromLatin1("com.trolltech"));
2805         QCOMPARE(ret.at(2), QString::fromLatin1("com.trolltech.recursive"));
2806         QCOMPARE(ret.at(3), QString::fromLatin1("com.trolltech.syntaxerror"));
2807         availableExtensions = ret;
2808     }
2809
2810     // try to import something that doesn't exist
2811     {
2812         QScriptEngine eng;
2813         QScriptValue ret = eng.importExtension("this.extension.does.not.exist");
2814         QCOMPARE(eng.hasUncaughtException(), true);
2815         QCOMPARE(ret.isError(), true);
2816         QCOMPARE(ret.toString(), QString::fromLatin1("Error: Unable to import this.extension.does.not.exist: no such extension"));
2817     }
2818
2819     {
2820         QScriptEngine eng;
2821         for (int x = 0; x < 2; ++x) {
2822             QCOMPARE(eng.globalObject().property("com").isValid(), x == 1);
2823             QScriptValue ret = eng.importExtension("com.trolltech");
2824             QCOMPARE(eng.hasUncaughtException(), false);
2825             QCOMPARE(ret.isUndefined(), true);
2826
2827             QScriptValue com = eng.globalObject().property("com");
2828             QCOMPARE(com.isObject(), true);
2829             QCOMPARE(com.property("wasDefinedAlready")
2830                      .strictlyEquals(QScriptValue(&eng, false)), true);
2831             QCOMPARE(com.property("name")
2832                      .strictlyEquals(QScriptValue(&eng, "com")), true);
2833             QCOMPARE(com.property("level")
2834                      .strictlyEquals(QScriptValue(&eng, 1)), true);
2835             QVERIFY(com.property("originalPostInit").isUndefined());
2836             QVERIFY(com.property("postInitCallCount").strictlyEquals(1));
2837
2838             QScriptValue trolltech = com.property("trolltech");
2839             QCOMPARE(trolltech.isObject(), true);
2840             QCOMPARE(trolltech.property("wasDefinedAlready")
2841                      .strictlyEquals(QScriptValue(&eng, false)), true);
2842             QCOMPARE(trolltech.property("name")
2843                      .strictlyEquals(QScriptValue(&eng, "com.trolltech")), true);
2844             QCOMPARE(trolltech.property("level")
2845                      .strictlyEquals(QScriptValue(&eng, 2)), true);
2846             QVERIFY(trolltech.property("originalPostInit").isUndefined());
2847             QVERIFY(trolltech.property("postInitCallCount").strictlyEquals(1));
2848         }
2849         QStringList imp = eng.importedExtensions();
2850         QCOMPARE(imp.size(), 2);
2851         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2852         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2853         QCOMPARE(eng.availableExtensions(), availableExtensions);
2854     }
2855
2856     // recursive import should throw an error
2857     {
2858         QScriptEngine eng;
2859         QVERIFY(eng.importedExtensions().isEmpty());
2860         eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
2861         QScriptValue ret = eng.importExtension("com.trolltech.recursive");
2862         QCOMPARE(eng.hasUncaughtException(), true);
2863         QVERIFY(ret.isError());
2864         QCOMPARE(ret.toString(), QString::fromLatin1("Error: recursive import of com.trolltech.recursive"));
2865         QStringList imp = eng.importedExtensions();
2866         QCOMPARE(imp.size(), 2);
2867         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2868         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2869         QCOMPARE(eng.availableExtensions(), availableExtensions);
2870     }
2871
2872     {
2873         QScriptEngine eng;
2874         eng.globalObject().setProperty("__import__", eng.newFunction(__import__));
2875         for (int x = 0; x < 2; ++x) {
2876             if (x == 0)
2877                 QVERIFY(eng.importedExtensions().isEmpty());
2878             QScriptValue ret = eng.importExtension("com.trolltech.syntaxerror");
2879             QVERIFY(eng.hasUncaughtException());
2880             QEXPECT_FAIL("", "JSC throws syntax error eagerly", Continue);
2881             QCOMPARE(eng.uncaughtExceptionLineNumber(), 4);
2882             QVERIFY(ret.isError());
2883             QVERIFY(ret.toString().contains(QLatin1String("SyntaxError")));
2884         }
2885         QStringList imp = eng.importedExtensions();
2886         QCOMPARE(imp.size(), 2);
2887         QCOMPARE(imp.at(0), QString::fromLatin1("com"));
2888         QCOMPARE(imp.at(1), QString::fromLatin1("com.trolltech"));
2889         QCOMPARE(eng.availableExtensions(), availableExtensions);
2890     }
2891
2892     QCoreApplication::instance()->setLibraryPaths(libPaths);
2893 }
2894
2895 static QScriptValue recurse(QScriptContext *ctx, QScriptEngine *eng)
2896 {
2897     Q_UNUSED(eng);
2898     return ctx->callee().call();
2899 }
2900
2901 static QScriptValue recurse2(QScriptContext *ctx, QScriptEngine *eng)
2902 {
2903     Q_UNUSED(eng);
2904     return ctx->callee().construct();
2905 }
2906
2907 void tst_QJSEngine::infiniteRecursion()
2908 {
2909     // Infinite recursion in JS should cause the VM to throw an error
2910     // when the JS stack is exhausted.
2911     // The exact error is back-end specific and subject to change.
2912     const QString stackOverflowError = QString::fromLatin1("RangeError: Maximum call stack size exceeded");
2913     QScriptEngine eng;
2914     {
2915         QScriptValue ret = eng.evaluate("function foo() { foo(); }; foo();");
2916         QCOMPARE(ret.isError(), true);
2917         QVERIFY(ret.toString().startsWith(stackOverflowError));
2918     }
2919 #if 0 //The native C++ stack overflow before the JS stack
2920     {
2921         QScriptValue fun = eng.newFunction(recurse);
2922         QScriptValue ret = fun.call();
2923         QCOMPARE(ret.isError(), true);
2924         QCOMPARE(ret.toString(), stackOverflowError);
2925     }
2926     {
2927         QScriptValue fun = eng.newFunction(recurse2);
2928         QScriptValue ret = fun.construct();
2929         QCOMPARE(ret.isError(), true);
2930         QCOMPARE(ret.toString(), stackOverflowError);
2931     }
2932 #endif
2933 }
2934 #endif
2935
2936 struct Bar {
2937     int a;
2938 };
2939
2940 struct Baz : public Bar {
2941     int b;
2942 };
2943
2944 Q_DECLARE_METATYPE(Bar*)
2945 Q_DECLARE_METATYPE(Baz*)
2946
2947 Q_DECLARE_METATYPE(QGradient)
2948 Q_DECLARE_METATYPE(QGradient*)
2949 Q_DECLARE_METATYPE(QLinearGradient)
2950
2951 #if 0 // FIXME: No support for default prototypes
2952 class Zoo : public QObject
2953 {
2954     Q_OBJECT
2955 public:
2956     Zoo() { }
2957 public slots:
2958     Baz *toBaz(Bar *b) { return reinterpret_cast<Baz*>(b); }
2959 };
2960
2961 void tst_QJSEngine::castWithPrototypeChain()
2962 {
2963     QScriptEngine eng;
2964     Bar bar;
2965     Baz baz;
2966     QScriptValue barProto = eng.toScriptValue(&bar);
2967     QScriptValue bazProto = eng.toScriptValue(&baz);
2968     eng.setDefaultPrototype(qMetaTypeId<Bar*>(), barProto);
2969     eng.setDefaultPrototype(qMetaTypeId<Baz*>(), bazProto);
2970
2971     Baz baz2;
2972     baz2.a = 123;
2973     baz2.b = 456;
2974     QScriptValue baz2Value = eng.toScriptValue(&baz2);
2975     {
2976         // qscriptvalue_cast() does magic; if the QScriptValue contains
2977         // t of type T, and the target type is T*, &t is returned.
2978         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
2979         QVERIFY(pbaz != 0);
2980         QCOMPARE(pbaz->b, baz2.b);
2981
2982         Zoo zoo;
2983         QScriptValue scriptZoo = eng.newQObject(&zoo);
2984         QScriptValue toBaz = scriptZoo.property("toBaz");
2985         QVERIFY(toBaz.isFunction());
2986
2987         // no relation between Bar and Baz's proto --> casting fails
2988         {
2989             Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
2990             QVERIFY(pbar == 0);
2991         }
2992
2993         {
2994             QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
2995             QVERIFY(ret.isError());
2996             QCOMPARE(ret.toString(), QLatin1String("TypeError: incompatible type of argument(s) in call to toBaz(); candidates were\n    toBaz(Bar*)"));
2997         }
2998
2999         // establish chain -- now casting should work
3000         // Why? because qscriptvalue_cast() does magic again.
3001         // It the instance itself is not of type T, qscriptvalue_cast()
3002         // searches the prototype chain for T, and if it finds one, it infers
3003         // that the instance can also be casted to that type. This cast is
3004         // _not_ safe and thus relies on the developer doing the right thing.
3005         // This is an undocumented feature to enable qscriptvalue_cast() to
3006         // be used by prototype functions to cast the JS this-object to C++.
3007         bazProto.setPrototype(barProto);
3008
3009         {
3010             Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
3011             QVERIFY(pbar != 0);
3012             QCOMPARE(pbar->a, baz2.a);
3013         }
3014
3015         {
3016             QScriptValue ret = toBaz.call(scriptZoo, QScriptValueList() << baz2Value);
3017             QEXPECT_FAIL("", "Cannot convert Baz* to Bar*", Continue);
3018             QVERIFY(!ret.isError());
3019             QEXPECT_FAIL("", "Cannot convert Baz* to Bar*", Continue);
3020             QCOMPARE(qscriptvalue_cast<Baz*>(ret), pbaz);
3021         }
3022     }
3023
3024     bazProto.setPrototype(barProto.prototype()); // kill chain
3025     {
3026         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
3027         QVERIFY(pbaz != 0);
3028         // should not work anymore
3029         Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
3030         QVERIFY(pbar == 0);
3031     }
3032
3033     bazProto.setPrototype(eng.newQObject(this));
3034     {
3035         Baz *pbaz = qscriptvalue_cast<Baz*>(baz2Value);
3036         QVERIFY(pbaz != 0);
3037         // should not work now either
3038         Bar *pbar = qscriptvalue_cast<Bar*>(baz2Value);
3039         QVERIFY(pbar == 0);
3040     }
3041
3042     {
3043         QScriptValue b = eng.toScriptValue(QBrush());
3044         b.setPrototype(barProto);
3045         // this shows that a "wrong" cast is possible, if you
3046         // don't play by the rules (the pointer is actually a QBrush*)...
3047         Bar *pbar = qscriptvalue_cast<Bar*>(b);
3048         QVERIFY(pbar != 0);
3049     }
3050
3051     {
3052         QScriptValue gradientProto = eng.toScriptValue(QGradient());
3053         QScriptValue linearGradientProto = eng.toScriptValue(QLinearGradient());
3054         linearGradientProto.setPrototype(gradientProto);
3055         QLinearGradient lg(10, 20, 30, 40);
3056         QScriptValue linearGradient = eng.toScriptValue(lg);
3057         {
3058             QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
3059             QVERIFY(pgrad == 0);
3060         }
3061         linearGradient.setPrototype(linearGradientProto);
3062         {
3063             QGradient *pgrad = qscriptvalue_cast<QGradient*>(linearGradient);
3064             QVERIFY(pgrad != 0);
3065             QCOMPARE(pgrad->type(), QGradient::LinearGradient);
3066             QLinearGradient *plingrad = static_cast<QLinearGradient*>(pgrad);
3067             QCOMPARE(plingrad->start(), lg.start());
3068             QCOMPARE(plingrad->finalStop(), lg.finalStop());
3069         }
3070     }
3071 }
3072 #endif
3073
3074 class Klazz : public QWidget,
3075               public QStandardItem,
3076               public QGraphicsItem
3077 {
3078     Q_OBJECT
3079 public:
3080     Klazz(QWidget *parent = 0) : QWidget(parent) { }
3081     virtual QRectF boundingRect() const { return QRectF(); }
3082     virtual void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) { }
3083 };
3084
3085 Q_DECLARE_METATYPE(Klazz*)
3086 Q_DECLARE_METATYPE(QStandardItem*)
3087
3088 void tst_QJSEngine::castWithMultipleInheritance()
3089 {
3090     QJSEngine eng;
3091     Klazz klz;
3092     QJSValue v = eng.newQObject(&klz);
3093
3094     QCOMPARE(qjsvalue_cast<Klazz*>(v), &klz);
3095     QCOMPARE(qjsvalue_cast<QWidget*>(v), (QWidget *)&klz);
3096     QCOMPARE(qjsvalue_cast<QObject*>(v), (QObject *)&klz);
3097     QCOMPARE(qjsvalue_cast<QStandardItem*>(v), (QStandardItem *)&klz);
3098     QCOMPARE(qjsvalue_cast<QGraphicsItem*>(v), (QGraphicsItem *)&klz);
3099 }
3100
3101 void tst_QJSEngine::collectGarbage()
3102 {
3103     QJSEngine eng;
3104     eng.evaluate("a = new Object(); a = new Object(); a = new Object()");
3105     QJSValue a = eng.newObject();
3106     a = eng.newObject();
3107     a = eng.newObject();
3108     QPointer<QObject> ptr = new QObject();
3109     QVERIFY(ptr != 0);
3110     (void)eng.newQObject(ptr);
3111     collectGarbage_helper(eng);
3112     if (ptr)
3113         QApplication::sendPostedEvents(ptr, QEvent::DeferredDelete);
3114     QVERIFY(ptr == 0);
3115 }
3116
3117 #if 0 // ###FIXME: no reportAdditionalMemoryCost API
3118 void tst_QJSEngine::reportAdditionalMemoryCost()
3119 {
3120     QScriptEngine eng;
3121     // There isn't any reliable way to test whether calling
3122     // this function affects garbage collection responsiveness;
3123     // the best we can do is call it with a few different values.
3124     for (int x = 0; x < 100; ++x) {
3125         eng.reportAdditionalMemoryCost(0);
3126         eng.reportAdditionalMemoryCost(10);
3127         eng.reportAdditionalMemoryCost(1000);
3128         eng.reportAdditionalMemoryCost(10000);
3129         eng.reportAdditionalMemoryCost(100000);
3130         eng.reportAdditionalMemoryCost(1000000);
3131         eng.reportAdditionalMemoryCost(10000000);
3132         eng.reportAdditionalMemoryCost(-1);
3133         eng.reportAdditionalMemoryCost(-1000);
3134         QScriptValue obj = eng.newObject();
3135         eng.collectGarbage();
3136     }
3137 }
3138 #endif
3139
3140 void tst_QJSEngine::gcWithNestedDataStructure()
3141 {
3142     // The GC must be able to traverse deeply nested objects, otherwise this
3143     // test would crash.
3144     QJSEngine eng;
3145     eng.evaluate(
3146         "function makeList(size)"
3147         "{"
3148         "  var head = { };"
3149         "  var l = head;"
3150         "  for (var i = 0; i < size; ++i) {"
3151         "    l.data = i + \"\";"
3152         "    l.next = { }; l = l.next;"
3153         "  }"
3154         "  l.next = null;"
3155         "  return head;"
3156         "}");
3157     QCOMPARE(eng.hasUncaughtException(), false);
3158     const int size = 200;
3159     QJSValue head = eng.evaluate(QString::fromLatin1("makeList(%0)").arg(size));
3160     QCOMPARE(eng.hasUncaughtException(), false);
3161     for (int x = 0; x < 2; ++x) {
3162         if (x == 1)
3163             eng.evaluate("gc()");
3164         QJSValue l = head;
3165         // Make sure all the nodes are still alive.
3166         for (int i = 0; i < 200; ++i) {
3167             QCOMPARE(l.property("data").toString(), QString::number(i));
3168             l = l.property("next");
3169         }
3170     }
3171 }
3172
3173 #if 0 // ###FIXME: No processEvents handling
3174 class EventReceiver : public QObject
3175 {
3176 public:
3177     EventReceiver() {
3178         received = false;
3179     }
3180
3181     bool event(QEvent *e) {
3182         received |= (e->type() == QEvent::User + 1);
3183         return QObject::event(e);
3184     }
3185
3186     bool received;
3187 };
3188
3189 void tst_QJSEngine::processEventsWhileRunning()
3190 {
3191     for (int x = 0; x < 2; ++x) {
3192         QScriptEngine eng;
3193         if (x == 0)
3194             eng.pushContext();
3195
3196         // This is running for a silly amount of time just to make sure
3197         // the script doesn't finish before event processing is triggered.
3198         QString script = QString::fromLatin1(
3199             "var end = Number(new Date()) + 2000;"
3200             "var x = 0;"
3201             "while (Number(new Date()) < end) {"
3202             "    ++x;"
3203             "}");
3204
3205         EventReceiver receiver;
3206         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3207
3208         eng.evaluate(script);
3209         QVERIFY(!eng.hasUncaughtException());
3210         QVERIFY(!receiver.received);
3211
3212         QCOMPARE(eng.processEventsInterval(), -1);
3213         eng.setProcessEventsInterval(100);
3214         eng.evaluate(script);
3215         QVERIFY(!eng.hasUncaughtException());
3216         QVERIFY(receiver.received);
3217
3218         if (x == 0)
3219             eng.popContext();
3220     }
3221 }
3222
3223 void tst_QJSEngine::processEventsWhileRunning_function()
3224 {
3225     QScriptEngine eng;
3226     QScriptValue script = eng.evaluate(QString::fromLatin1(
3227         "(function() { var end = Number(new Date()) + 2000;"
3228         "var x = 0;"
3229         "while (Number(new Date()) < end) {"
3230         "    ++x;"
3231         "} })"));
3232
3233     eng.setProcessEventsInterval(100);
3234
3235     for (int x = 0; x < 2; ++x) {
3236         EventReceiver receiver;
3237         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3238         QVERIFY(!eng.hasUncaughtException());
3239         QVERIFY(!receiver.received);
3240         QCOMPARE(eng.processEventsInterval(), 100);
3241
3242         if (x) script.call();
3243         else script.construct();
3244
3245         QVERIFY(!eng.hasUncaughtException());
3246         QVERIFY(receiver.received);
3247     }
3248 }
3249
3250
3251 class EventReceiver2 : public QObject
3252 {
3253 public:
3254     EventReceiver2(QScriptEngine *eng) {
3255         engine = eng;
3256     }
3257
3258     bool event(QEvent *e) {
3259         if (e->type() == QEvent::User + 1) {
3260             engine->currentContext()->throwError("Killed");
3261         }
3262         return QObject::event(e);
3263     }
3264
3265     QScriptEngine *engine;
3266 };
3267
3268 void tst_QJSEngine::throwErrorFromProcessEvents_data()
3269 {
3270     QTest::addColumn<QString>("script");
3271     QTest::addColumn<QString>("error");
3272
3273     QTest::newRow("while (1)")
3274         << QString::fromLatin1("while (1) { }")
3275         << QString::fromLatin1("Error: Killed");
3276     QTest::newRow("while (1) i++")
3277         << QString::fromLatin1("i = 0; while (1) { i++; }")
3278         << QString::fromLatin1("Error: Killed");
3279     // Unlike abortEvaluation(), scripts should be able to catch the
3280     // exception.
3281     QTest::newRow("try catch")
3282         << QString::fromLatin1("try {"
3283                                "    while (1) { }"
3284                                "} catch(e) {"
3285                                "    throw new Error('Caught');"
3286                                "}")
3287         << QString::fromLatin1("Error: Caught");
3288 }
3289
3290 void tst_QJSEngine::throwErrorFromProcessEvents()
3291 {
3292     QFETCH(QString, script);
3293     QFETCH(QString, error);
3294
3295     QScriptEngine eng;
3296
3297     EventReceiver2 receiver(&eng);
3298     QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3299
3300     eng.setProcessEventsInterval(100);
3301     QScriptValue ret = eng.evaluate(script);
3302     QVERIFY(ret.isError());
3303     QCOMPARE(ret.toString(), error);
3304 }
3305
3306 void tst_QJSEngine::disableProcessEventsInterval()
3307 {
3308     QScriptEngine eng;
3309     eng.setProcessEventsInterval(100);
3310     QCOMPARE(eng.processEventsInterval(), 100);
3311     eng.setProcessEventsInterval(0);
3312     QCOMPARE(eng.processEventsInterval(), 0);
3313     eng.setProcessEventsInterval(-1);
3314     QCOMPARE(eng.processEventsInterval(), -1);
3315     eng.setProcessEventsInterval(-100);
3316     QCOMPARE(eng.processEventsInterval(), -100);
3317 }
3318 #endif
3319
3320
3321 void tst_QJSEngine::stacktrace()
3322 {
3323     QString script = QString::fromLatin1(
3324         "function foo(counter) {\n"
3325         "    switch (counter) {\n"
3326         "        case 0: foo(counter+1); break;\n"
3327         "        case 1: foo(counter+1); break;\n"
3328         "        case 2: foo(counter+1); break;\n"
3329         "        case 3: foo(counter+1); break;\n"
3330         "        case 4: foo(counter+1); break;\n"
3331         "        default:\n"
3332         "        throw new Error('blah');\n"
3333         "    }\n"
3334         "}\n"
3335         "foo(0);");
3336
3337     const QString fileName("testfile");
3338
3339     QStringList backtrace;
3340     backtrace << "foo(5)@testfile:9"
3341               << "foo(4)@testfile:7"
3342               << "foo(3)@testfile:6"
3343               << "foo(2)@testfile:5"
3344               << "foo(1)@testfile:4"
3345               << "foo(0)@testfile:3"
3346               << "<global>()@testfile:12";
3347
3348     QJSEngine eng;
3349     QJSValue result = eng.evaluate(script, fileName);
3350     QVERIFY(eng.hasUncaughtException());
3351     QVERIFY(result.isError());
3352
3353     // QEXPECT_FAIL("", "QTBUG-6139: uncaughtExceptionBacktrace() doesn't give the full backtrace", Abort);
3354     // ###FIXME: no uncahgutExceptionBacktrace: QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
3355     QVERIFY(eng.hasUncaughtException());
3356     QVERIFY(result.strictlyEquals(eng.uncaughtException()));
3357
3358     // FIXME? it is not standard.
3359     //QCOMPARE(result.property("fileName").toString(), fileName);
3360     //QCOMPARE(result.property("lineNumber").toInt32(), 9);
3361
3362     QJSValue stack = result.property("stack");
3363
3364     // FIXME? it is not standard.
3365     // QVERIFY(stack.isArray());
3366     //QCOMPARE(stack.property("length").toInt32(), 7);
3367
3368     QJSValueIterator it(stack);
3369     int counter = 5;
3370     while (it.hasNext()) {
3371         it.next();
3372         QJSValue obj = it.value();
3373         QJSValue frame = obj.property("frame");
3374
3375         QCOMPARE(obj.property("fileName").toString(), fileName);
3376         if (counter >= 0) {
3377             QJSValue callee = frame.property("arguments").property("callee");
3378             QVERIFY(callee.strictlyEquals(eng.globalObject().property("foo")));
3379             QCOMPARE(obj.property("functionName").toString(), QString("foo"));
3380             int line = obj.property("lineNumber").toInt32();
3381             if (counter == 5)
3382                 QCOMPARE(line, 9);
3383             else
3384                 QCOMPARE(line, 3 + counter);
3385         } else {
3386             QVERIFY(frame.strictlyEquals(eng.globalObject()));
3387             QVERIFY(obj.property("functionName").toString().isEmpty());
3388         }
3389
3390         --counter;
3391     }
3392
3393 //    FIXME? it is not standard.
3394 //    {
3395 //        QJSValue bt = result.property("backtrace").call(result);
3396 //        QCOMPARE(qjsvalue_cast<QStringList>(bt), backtrace);
3397 //    }
3398
3399     // throw something that isn't an Error object
3400     eng.clearExceptions();
3401     // ###FIXME: No uncaughtExceptionBacktrace: QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
3402     QString script2 = QString::fromLatin1(
3403         "function foo(counter) {\n"
3404         "    switch (counter) {\n"
3405         "        case 0: foo(counter+1); break;\n"
3406         "        case 1: foo(counter+1); break;\n"
3407         "        case 2: foo(counter+1); break;\n"
3408         "        case 3: foo(counter+1); break;\n"
3409         "        case 4: foo(counter+1); break;\n"
3410         "        default:\n"
3411         "        throw 'just a string';\n"
3412         "    }\n"
3413         "}\n"
3414         "foo(0);");
3415
3416     QJSValue result2 = eng.evaluate(script2, fileName);
3417     QVERIFY(eng.hasUncaughtException());
3418     QVERIFY(!result2.isError());
3419     QVERIFY(result2.isString());
3420
3421     // ###FIXME: No uncaughtExceptionBacktrace:  QCOMPARE(eng.uncaughtExceptionBacktrace(), backtrace);
3422     QVERIFY(eng.hasUncaughtException());
3423
3424     eng.clearExceptions();
3425     QVERIFY(!eng.hasUncaughtException());
3426     // ###FIXME: No uncaughtExceptionBacktrace:  QVERIFY(eng.uncaughtExceptionBacktrace().isEmpty());
3427 }
3428
3429 void tst_QJSEngine::numberParsing_data()
3430 {
3431     QTest::addColumn<QString>("string");
3432     QTest::addColumn<qreal>("expect");
3433
3434     QTest::newRow("decimal 0") << QString("0") << qreal(0);
3435     QTest::newRow("octal 0") << QString("00") << qreal(00);
3436     QTest::newRow("hex 0") << QString("0x0") << qreal(0x0);
3437     QTest::newRow("decimal 100") << QString("100") << qreal(100);
3438     QTest::newRow("hex 100") << QString("0x100") << qreal(0x100);
3439     QTest::newRow("octal 100") << QString("0100") << qreal(0100);
3440     QTest::newRow("decimal 4G") << QString("4294967296") << qreal(Q_UINT64_C(4294967296));
3441     QTest::newRow("hex 4G") << QString("0x100000000") << qreal(Q_UINT64_C(0x100000000));
3442     QTest::newRow("octal 4G") << QString("040000000000") << qreal(Q_UINT64_C(040000000000));
3443     QTest::newRow("0.5") << QString("0.5") << qreal(0.5);
3444     QTest::newRow("1.5") << QString("1.5") << qreal(1.5);
3445     QTest::newRow("1e2") << QString("1e2") << qreal(100);
3446 }
3447
3448 void tst_QJSEngine::numberParsing()
3449 {
3450     QFETCH(QString, string);
3451     QFETCH(qreal, expect);
3452
3453     QJSEngine eng;
3454     QJSValue ret = eng.evaluate(string);
3455     QVERIFY(ret.isNumber());
3456     qreal actual = ret.toNumber();
3457     QCOMPARE(actual, expect);
3458 }
3459
3460 // see ECMA-262, section 7.9
3461 // This is testing ECMA compliance, not our C++ API, but it's important that
3462 // the back-end is conformant in this regard.
3463 void tst_QJSEngine::automaticSemicolonInsertion()
3464 {
3465     QJSEngine eng;
3466     {
3467         QJSValue ret = eng.evaluate("{ 1 2 } 3");
3468         QVERIFY(ret.isError());
3469         QVERIFY(ret.toString().contains("SyntaxError"));
3470     }
3471     {
3472         QJSValue ret = eng.evaluate("{ 1\n2 } 3");
3473         QVERIFY(ret.isNumber());
3474         QCOMPARE(ret.toInt32(), 3);
3475     }
3476     {
3477         QJSValue ret = eng.evaluate("for (a; b\n)");
3478         QVERIFY(ret.isError());
3479         QVERIFY(ret.toString().contains("SyntaxError"));
3480     }
3481     {
3482         QJSValue ret = eng.evaluate("(function() { return\n1 + 2 })()");
3483         QVERIFY(ret.isUndefined());
3484     }
3485     {
3486         eng.evaluate("c = 2; b = 1");
3487         QJSValue ret = eng.evaluate("a = b\n++c");
3488         QVERIFY(ret.isNumber());
3489         QCOMPARE(ret.toInt32(), 3);
3490     }
3491     {
3492         QJSValue ret = eng.evaluate("if (a > b)\nelse c = d");
3493         QVERIFY(ret.isError());
3494         QVERIFY(ret.toString().contains("SyntaxError"));
3495     }
3496     {
3497         eng.evaluate("function c() { return { foo: function() { return 5; } } }");
3498         eng.evaluate("b = 1; d = 2; e = 3");
3499         QJSValue ret = eng.evaluate("a = b + c\n(d + e).foo()");
3500         QVERIFY(ret.isNumber());
3501         QCOMPARE(ret.toInt32(), 6);
3502     }
3503     {
3504         QJSValue ret = eng.evaluate("throw\n1");
3505         QVERIFY(ret.isError());
3506         QVERIFY(ret.toString().contains("SyntaxError"));
3507     }
3508     {
3509         QJSValue ret = eng.evaluate("a = Number(1)\n++a");
3510         QVERIFY(ret.isNumber());
3511         QCOMPARE(ret.toInt32(), 2);
3512     }
3513
3514     // "a semicolon is never inserted automatically if the semicolon
3515     // would then be parsed as an empty statement"
3516     {
3517         eng.evaluate("a = 123");
3518         QJSValue ret = eng.evaluate("if (0)\n ++a; a");
3519         QVERIFY(ret.isNumber());
3520         QCOMPARE(ret.toInt32(), 123);
3521     }
3522     {
3523         eng.evaluate("a = 123");
3524         QJSValue ret = eng.evaluate("if (0)\n --a; a");
3525         QVERIFY(ret.isNumber());
3526         QCOMPARE(ret.toInt32(), 123);
3527     }
3528     {
3529         eng.evaluate("a = 123");
3530         QJSValue ret = eng.evaluate("if ((0))\n ++a; a");
3531         QVERIFY(ret.isNumber());
3532         QCOMPARE(ret.toInt32(), 123);
3533     }
3534     {
3535         eng.evaluate("a = 123");
3536         QJSValue ret = eng.evaluate("if ((0))\n --a; a");
3537         QVERIFY(ret.isNumber());
3538         QCOMPARE(ret.toInt32(), 123);
3539     }
3540     {
3541         eng.evaluate("a = 123");
3542         QJSValue ret = eng.evaluate("if (0\n)\n ++a; a");
3543         QVERIFY(ret.isNumber());
3544         QCOMPARE(ret.toInt32(), 123);
3545     }
3546     {
3547         eng.evaluate("a = 123");
3548         QJSValue ret = eng.evaluate("if (0\n ++a; a");
3549         QVERIFY(ret.isError());
3550     }
3551     {
3552         eng.evaluate("a = 123");
3553         QJSValue ret = eng.evaluate("if (0))\n ++a; a");
3554         QVERIFY(ret.isError());
3555     }
3556     {
3557         QJSValue ret = eng.evaluate("n = 0; for (i = 0; i < 10; ++i)\n ++n; n");
3558         QVERIFY(ret.isNumber());
3559         QCOMPARE(ret.toInt32(), 10);
3560     }
3561     {
3562         QJSValue ret = eng.evaluate("n = 30; for (i = 0; i < 10; ++i)\n --n; n");
3563         QVERIFY(ret.isNumber());
3564         QCOMPARE(ret.toInt32(), 20);
3565     }
3566     {
3567         QJSValue ret = eng.evaluate("n = 0; for (var i = 0; i < 10; ++i)\n ++n; n");
3568         QVERIFY(ret.isNumber());
3569         QCOMPARE(ret.toInt32(), 10);
3570     }
3571     {
3572         QJSValue ret = eng.evaluate("n = 30; for (var i = 0; i < 10; ++i)\n --n; n");
3573         QVERIFY(ret.isNumber());
3574         QCOMPARE(ret.toInt32(), 20);
3575     }
3576     {
3577         QJSValue ret = eng.evaluate("n = 0; i = 0; while (i++ < 10)\n ++n; n");
3578         QVERIFY(ret.isNumber());
3579         QCOMPARE(ret.toInt32(), 10);
3580     }
3581     {
3582         QJSValue ret = eng.evaluate("n = 30; i = 0; while (i++ < 10)\n --n; n");
3583         QVERIFY(ret.isNumber());
3584         QCOMPARE(ret.toInt32(), 20);
3585     }
3586     {
3587         QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (i in o)\n ++n; n");
3588         QVERIFY(ret.isNumber());
3589         QCOMPARE(ret.toInt32(), 3);
3590     }
3591     {
3592         QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (i in o)\n --n; n");
3593         QVERIFY(ret.isNumber());
3594         QCOMPARE(ret.toInt32(), 6);
3595     }
3596     {
3597         QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 0; for (var i in o)\n ++n; n");
3598         QVERIFY(ret.isNumber());
3599         QCOMPARE(ret.toInt32(), 3);
3600     }
3601     {
3602         QJSValue ret = eng.evaluate("o = { a: 0, b: 1, c: 2 }; n = 9; for (var i in o)\n --n; n");
3603         QVERIFY(ret.isNumber());
3604         QCOMPARE(ret.toInt32(), 6);
3605     }
3606     {
3607         QJSValue ret = eng.evaluate("o = { n: 3 }; n = 5; with (o)\n ++n; n");
3608         QVERIFY(ret.isNumber());
3609         QCOMPARE(ret.toInt32(), 5);
3610     }
3611     {
3612         QJSValue ret = eng.evaluate("o = { n: 3 }; n = 10; with (o)\n --n; n");
3613         QVERIFY(ret.isNumber());
3614         QCOMPARE(ret.toInt32(), 10);
3615     }
3616     {
3617         QJSValue ret = eng.evaluate("n = 5; i = 0; do\n ++n; while (++i < 10); n");
3618         QVERIFY(ret.isNumber());
3619         QCOMPARE(ret.toInt32(), 15);
3620     }
3621     {
3622         QJSValue ret = eng.evaluate("n = 20; i = 0; do\n --n; while (++i < 10); n");
3623         QVERIFY(ret.isNumber());
3624         QCOMPARE(ret.toInt32(), 10);
3625     }
3626
3627     {
3628         QJSValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n++n; n");
3629         QVERIFY(ret.isNumber());
3630         QCOMPARE(ret.toInt32(), 2);
3631     }
3632     {
3633         QJSValue ret = eng.evaluate("n = 1; i = 0; if (n) i\n--n; n");
3634         QVERIFY(ret.isNumber());
3635         QCOMPARE(ret.toInt32(), 0);
3636     }
3637
3638     {
3639         QJSValue ret = eng.evaluate("if (0)");
3640         QVERIFY(ret.isError());
3641     }
3642     {
3643         QJSValue ret = eng.evaluate("while (0)");
3644         QVERIFY(ret.isError());
3645     }
3646     {
3647         QJSValue ret = eng.evaluate("for (;;)");
3648         QVERIFY(ret.isError());
3649     }
3650     {
3651         QJSValue ret = eng.evaluate("for (p in this)");
3652         QVERIFY(ret.isError());
3653     }
3654     {
3655         QJSValue ret = eng.evaluate("with (this)");
3656         QVERIFY(ret.isError());
3657     }
3658     {
3659         QJSValue ret = eng.evaluate("do");
3660         QVERIFY(ret.isError());
3661     }
3662 }
3663
3664 #if 0 // ###FIXME: no abortEvaluation API
3665 class EventReceiver3 : public QObject
3666 {
3667 public:
3668     enum AbortionResult {
3669         None = 0,
3670         String = 1,
3671         Error = 2,
3672         Number = 3
3673     };
3674
3675     EventReceiver3(QScriptEngine *eng) {
3676         engine = eng;
3677         resultType = None;
3678     }
3679
3680     bool event(QEvent *e) {
3681         if (e->type() == QEvent::User + 1) {
3682             switch (resultType) {
3683             case None:
3684                 engine->abortEvaluation();
3685                 break;
3686             case String:
3687                 engine->abortEvaluation(QScriptValue(engine, QString::fromLatin1("Aborted")));
3688                 break;
3689             case Error:
3690                 engine->abortEvaluation(engine->currentContext()->throwError("AbortedWithError"));
3691                 break;
3692             case Number:
3693                 engine->abortEvaluation(QScriptValue(1234));
3694             }
3695         }
3696         return QObject::event(e);
3697     }
3698
3699     AbortionResult resultType;
3700     QScriptEngine *engine;
3701 };
3702
3703 static QScriptValue myFunctionAbortingEvaluation(QScriptContext *, QScriptEngine *eng)
3704 {
3705     eng->abortEvaluation();
3706     return eng->nullValue(); // should be ignored
3707 }
3708
3709 void tst_QJSEngine::abortEvaluation_notEvaluating()
3710 {
3711     QScriptEngine eng;
3712
3713     eng.abortEvaluation();
3714     QVERIFY(!eng.hasUncaughtException());
3715
3716     eng.abortEvaluation(123);
3717     {
3718         QScriptValue ret = eng.evaluate("'ciao'");
3719         QVERIFY(ret.isString());
3720         QCOMPARE(ret.toString(), QString::fromLatin1("ciao"));
3721     }
3722 }
3723
3724 void tst_QJSEngine::abortEvaluation_data()
3725 {
3726     QTest::addColumn<QString>("script");
3727
3728     QTest::newRow("while (1)")
3729         << QString::fromLatin1("while (1) { }");
3730     QTest::newRow("while (1) i++")
3731         << QString::fromLatin1("i = 0; while (1) { i++; }");
3732     QTest::newRow("try catch")
3733         << QString::fromLatin1("try {"
3734                                "    while (1) { }"
3735                                "} catch(e) {"
3736                                "    throw new Error('Caught');"
3737                                "}");
3738 }
3739
3740 void tst_QJSEngine::abortEvaluation()
3741 {
3742     QFETCH(QString, script);
3743
3744     QScriptEngine eng;
3745     EventReceiver3 receiver(&eng);
3746
3747     eng.setProcessEventsInterval(100);
3748     for (int x = 0; x < 4; ++x) {
3749         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3750         receiver.resultType = EventReceiver3::AbortionResult(x);
3751         QScriptValue ret = eng.evaluate(script);
3752         switch (receiver.resultType) {
3753         case EventReceiver3::None:
3754             QVERIFY(!eng.hasUncaughtException());
3755             QVERIFY(!ret.isValid());
3756             break;
3757         case EventReceiver3::Number:
3758             QVERIFY(!eng.hasUncaughtException());
3759             QVERIFY(ret.isNumber());
3760             QCOMPARE(ret.toInt32(), 1234);
3761             break;
3762         case EventReceiver3::String:
3763             QVERIFY(!eng.hasUncaughtException());
3764             QVERIFY(ret.isString());
3765             QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
3766             break;
3767         case EventReceiver3::Error:
3768             QVERIFY(eng.hasUncaughtException());
3769             QVERIFY(ret.isError());
3770             QCOMPARE(ret.toString(), QString::fromLatin1("Error: AbortedWithError"));
3771             break;
3772         }
3773     }
3774
3775 }
3776
3777 void tst_QJSEngine::abortEvaluation_tryCatch()
3778 {
3779     QSKIP("It crashes");
3780     QScriptEngine eng;
3781     EventReceiver3 receiver(&eng);
3782     eng.setProcessEventsInterval(100);
3783     // scripts cannot intercept the abortion with try/catch
3784     for (int y = 0; y < 4; ++y) {
3785         QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3786         receiver.resultType = EventReceiver3::AbortionResult(y);
3787         QScriptValue ret = eng.evaluate(QString::fromLatin1(
3788                                             "while (1) {\n"
3789                                             "  try {\n"
3790                                             "    (function() { while (1) { } })();\n"
3791                                             "  } catch (e) {\n"
3792                                             "    ;\n"
3793                                             "  }\n"
3794                                             "}"));
3795         switch (receiver.resultType) {
3796         case EventReceiver3::None:
3797             QVERIFY(!eng.hasUncaughtException());
3798             QVERIFY(!ret.isValid());
3799             break;
3800         case EventReceiver3::Number:
3801             QVERIFY(!eng.hasUncaughtException());
3802             QVERIFY(ret.isNumber());
3803             QCOMPARE(ret.toInt32(), 1234);
3804             break;
3805         case EventReceiver3::String:
3806             QVERIFY(!eng.hasUncaughtException());
3807             QVERIFY(ret.isString());
3808             QCOMPARE(ret.toString(), QString::fromLatin1("Aborted"));
3809             break;
3810         case EventReceiver3::Error:
3811             QVERIFY(eng.hasUncaughtException());
3812             QVERIFY(ret.isError());
3813             break;
3814         }
3815     }
3816 }
3817
3818 void tst_QJSEngine::abortEvaluation_fromNative()
3819 {
3820     QScriptEngine eng;
3821     QScriptValue fun = eng.newFunction(myFunctionAbortingEvaluation);
3822     eng.globalObject().setProperty("myFunctionAbortingEvaluation", fun);
3823     QScriptValue ret = eng.evaluate("myFunctionAbortingEvaluation()");
3824     QVERIFY(!ret.isValid());
3825 }
3826
3827 class ThreadedEngine : public QThread {
3828     Q_OBJECT;
3829
3830 private:
3831     QScriptEngine* m_engine;
3832 protected:
3833     void run() {
3834         m_engine = new QScriptEngine();
3835         m_engine->setGlobalObject(m_engine->newQObject(this));
3836         m_engine->evaluate("while (1) { sleep(); }");
3837         delete m_engine;
3838     }
3839
3840 public slots:
3841     void sleep()
3842     {
3843         QTest::qSleep(25);
3844         m_engine->abortEvaluation();
3845     }
3846 };
3847
3848 void tst_QJSEngine::abortEvaluation_QTBUG9433()
3849 {
3850     ThreadedEngine engine;
3851     engine.start();
3852     QVERIFY(engine.isRunning());
3853     QTest::qSleep(50);
3854     for (uint i = 0; i < 50; ++i) { // up to ~2500 ms
3855         if (engine.isFinished())
3856             return;
3857         QTest::qSleep(50);
3858     }
3859     if (!engine.isFinished()) {
3860         engine.terminate();
3861         engine.wait(7000);
3862         QFAIL("abortEvaluation doesn't work");
3863     }
3864
3865 }
3866 #endif
3867
3868 #if 0 // ###FIXME: no QScriptEngine::isEvaluating
3869 static QScriptValue myFunctionReturningIsEvaluating(QScriptContext *, QScriptEngine *eng)
3870 {
3871     return QScriptValue(eng, eng->isEvaluating());
3872 }
3873
3874 class EventReceiver4 : public QObject
3875 {
3876 public:
3877     EventReceiver4(QScriptEngine *eng) {
3878         engine = eng;
3879         wasEvaluating = false;
3880     }
3881
3882     bool event(QEvent *e) {
3883         if (e->type() == QEvent::User + 1) {
3884             wasEvaluating = engine->isEvaluating();
3885         }
3886         return QObject::event(e);
3887     }
3888
3889     QScriptEngine *engine;
3890     bool wasEvaluating;
3891 };
3892
3893 void tst_QJSEngine::isEvaluating_notEvaluating()
3894 {
3895     QScriptEngine eng;
3896
3897     QVERIFY(!eng.isEvaluating());
3898
3899     eng.evaluate("");
3900     QVERIFY(!eng.isEvaluating());
3901     eng.evaluate("123");
3902     QVERIFY(!eng.isEvaluating());
3903     eng.evaluate("0 = 0");
3904     QVERIFY(!eng.isEvaluating());
3905 }
3906
3907 void tst_QJSEngine::isEvaluating_fromNative()
3908 {
3909     QScriptEngine eng;
3910     QScriptValue fun = eng.newFunction(myFunctionReturningIsEvaluating);
3911     eng.globalObject().setProperty("myFunctionReturningIsEvaluating", fun);
3912     QScriptValue ret = eng.evaluate("myFunctionReturningIsEvaluating()");
3913     QVERIFY(ret.isBoolean());
3914     QVERIFY(ret.toBoolean());
3915     ret = fun.call();
3916     QVERIFY(ret.isBoolean());
3917     QVERIFY(ret.toBoolean());
3918     ret = myFunctionReturningIsEvaluating(eng.currentContext(), &eng);
3919     QVERIFY(ret.isBoolean());
3920     QVERIFY(!ret.toBoolean());
3921 }
3922
3923 void tst_QJSEngine::isEvaluating_fromEvent()
3924 {
3925     QScriptEngine eng;
3926     EventReceiver4 receiver(&eng);
3927     QCoreApplication::postEvent(&receiver, new QEvent(QEvent::Type(QEvent::User+1)));
3928
3929     QString script = QString::fromLatin1(
3930         "var end = Number(new Date()) + 1000;"
3931         "var x = 0;"
3932         "while (Number(new Date()) < end) {"
3933         "    ++x;"
3934         "}");
3935
3936     eng.setProcessEventsInterval(100);
3937     eng.evaluate(script);
3938     QVERIFY(receiver.wasEvaluating);
3939 }
3940 #endif
3941
3942 static QtMsgType theMessageType;
3943 static QString theMessage;
3944
3945 static void myMsgHandler(QtMsgType type, const char *msg)
3946 {
3947     theMessageType = type;
3948     theMessage = QString::fromLatin1(msg);
3949 }
3950
3951 #if 0
3952 void tst_QJSEngine::printFunctionWithCustomHandler()
3953 {
3954     // The built-in print() function passes the output to Qt's message
3955     // handler. By installing a custom message handler, the output can be
3956     // redirected without changing the print() function itself.
3957     // This behavior is not documented.
3958     QJSEngine eng;
3959     QtMsgHandler oldHandler = qInstallMsgHandler(myMsgHandler);
3960     QVERIFY(eng.globalObject().property("print").isFunction());
3961
3962     theMessageType = QtSystemMsg;
3963     QVERIFY(theMessage.isEmpty());
3964     QVERIFY(eng.evaluate("print('test')").isUndefined());
3965     QCOMPARE(theMessageType, QtDebugMsg);
3966     QCOMPARE(theMessage, QString::fromLatin1("test"));
3967
3968     theMessageType = QtSystemMsg;
3969     theMessage.clear();
3970     QVERIFY(eng.evaluate("print(3, true, 'little pigs')").isUndefined());
3971     QCOMPARE(theMessageType, QtDebugMsg);
3972     QCOMPARE(theMessage, QString::fromLatin1("3 true little pigs"));
3973
3974     qInstallMsgHandler(oldHandler);
3975 }
3976
3977 void tst_QJSEngine::printThrowsException()
3978 {
3979     // If an argument to print() causes an exception to be thrown when
3980     // it's converted to a string, print() should propagate the exception.
3981     QJSEngine eng;
3982     QJSValue ret = eng.evaluate("print({ toString: function() { throw 'foo'; } });");
3983     QVERIFY(eng.hasUncaughtException());
3984     QVERIFY(ret.strictlyEquals(QJSValue(&eng, QLatin1String("foo"))));
3985 }
3986 #endif
3987
3988 void tst_QJSEngine::errorConstructors()
3989 {
3990     QJSEngine eng;
3991     QStringList prefixes;
3992     prefixes << "" << "Eval" << "Range" << "Reference" << "Syntax" << "Type" << "URI";
3993     for (int x = 0; x < 3; ++x) {
3994         for (int i = 0; i < prefixes.size(); ++i) {
3995             QString name = prefixes.at(i) + QLatin1String("Error");
3996             QString code = QString(i+1, QLatin1Char('\n'));
3997             if (x == 0)
3998                 code += QLatin1String("throw ");
3999             else if (x == 1)
4000                 code += QLatin1String("new ");
4001             code += name + QLatin1String("()");
4002             QJSValue ret = eng.evaluate(code);
4003             QVERIFY(ret.isError());
4004             QCOMPARE(eng.hasUncaughtException(), x == 0);
4005             eng.clearExceptions();
4006             QVERIFY(ret.toString().startsWith(name));
4007             //QTBUG-6138: JSC doesn't assign lineNumber when errors are not thrown
4008             QEXPECT_FAIL("", "we have no more lineNumber property ", Continue);
4009             QCOMPARE(ret.property("lineNumber").toInt32(), i+2);
4010         }
4011     }
4012 }
4013
4014 void tst_QJSEngine::argumentsProperty_globalContext()
4015 {
4016     QJSEngine eng;
4017     {
4018         // Unlike function calls, the global context doesn't have an
4019         // arguments property.
4020         QJSValue ret = eng.evaluate("arguments");
4021         QVERIFY(ret.isError());
4022         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
4023     }
4024     eng.evaluate("arguments = 10");
4025     {
4026         QJSValue ret = eng.evaluate("arguments");
4027         QVERIFY(ret.isNumber());
4028         QCOMPARE(ret.toInt32(), 10);
4029     }
4030     QVERIFY(eng.evaluate("delete arguments").toBoolean());
4031     QVERIFY(!eng.globalObject().property("arguments").isValid());
4032 }
4033
4034 void tst_QJSEngine::argumentsProperty_JS()
4035 {
4036     {
4037         QJSEngine eng;
4038         eng.evaluate("o = { arguments: 123 }");
4039         QJSValue ret = eng.evaluate("with (o) { arguments; }");
4040         QVERIFY(ret.isNumber());
4041         QCOMPARE(ret.toInt32(), 123);
4042     }
4043     {
4044         QJSEngine eng;
4045         QVERIFY(!eng.globalObject().property("arguments").isValid());
4046         // This is testing ECMA-262 compliance. In function calls, "arguments"
4047         // appears like a local variable, and it can be replaced.
4048         QJSValue ret = eng.evaluate("(function() { arguments = 456; return arguments; })()");
4049         QVERIFY(ret.isNumber());
4050         QCOMPARE(ret.toInt32(), 456);
4051         QVERIFY(!eng.globalObject().property("arguments").isValid());
4052     }
4053 }
4054
4055 #if 0 // ###FIXME: no QScriptContext API
4056 static QScriptValue argumentsProperty_fun(QScriptContext *, QScriptEngine *eng)
4057 {
4058     // Since evaluate() is done in the current context, "arguments" should
4059     // refer to currentContext()->argumentsObject().
4060     // This is for consistency with the built-in JS eval() function.
4061     eng->evaluate("var a = arguments[0];");
4062     eng->evaluate("arguments[0] = 200;");
4063     return eng->evaluate("a + arguments[0]");
4064 }
4065
4066 void tst_QJSEngine::argumentsProperty_evaluateInNativeFunction()
4067 {
4068     QScriptEngine eng;
4069     QScriptValue fun = eng.newFunction(argumentsProperty_fun);
4070     eng.globalObject().setProperty("fun", eng.newFunction(argumentsProperty_fun));
4071     QScriptValue result = eng.evaluate("fun(18)");
4072     QVERIFY(result.isNumber());
4073     QCOMPARE(result.toInt32(), 200+18);
4074 }
4075 #endif
4076
4077 void tst_QJSEngine::jsNumberClass()
4078 {
4079     // See ECMA-262 Section 15.7, "Number Objects".
4080
4081     QJSEngine eng;
4082
4083     QJSValue ctor = eng.globalObject().property("Number");
4084     QVERIFY(ctor.property("length").isNumber());
4085     QCOMPARE(ctor.property("length").toNumber(), qreal(1));
4086     QJSValue proto = ctor.property("prototype");
4087     QVERIFY(proto.isObject());
4088     {
4089         QJSValue::PropertyFlags flags = QJSValue::SkipInEnumeration
4090                                             | QJSValue::Undeletable
4091                                             | QJSValue::ReadOnly;
4092         QCOMPARE(ctor.propertyFlags("prototype"), flags);
4093         QVERIFY(ctor.property("MAX_VALUE").isNumber());
4094         QCOMPARE(ctor.propertyFlags("MAX_VALUE"), flags);
4095         QVERIFY(ctor.property("MIN_VALUE").isNumber());
4096         QCOMPARE(ctor.propertyFlags("MIN_VALUE"), flags);
4097         QVERIFY(ctor.property("NaN").isNumber());
4098         QCOMPARE(ctor.propertyFlags("NaN"), flags);
4099         QVERIFY(ctor.property("NEGATIVE_INFINITY").isNumber());
4100         QCOMPARE(ctor.propertyFlags("NEGATIVE_INFINITY"), flags);
4101         QVERIFY(ctor.property("POSITIVE_INFINITY").isNumber());
4102         QCOMPARE(ctor.propertyFlags("POSITIVE_INFINITY"), flags);
4103     }
4104     QVERIFY(proto.instanceOf(eng.globalObject().property("Object")));
4105     QCOMPARE(proto.toNumber(), qreal(0));
4106     QVERIFY(proto.property("constructor").strictlyEquals(ctor));
4107
4108     {
4109         QJSValue ret = eng.evaluate("Number()");
4110         QVERIFY(ret.isNumber());
4111         QCOMPARE(ret.toNumber(), qreal(0));
4112     }
4113     {
4114         QJSValue ret = eng.evaluate("Number(123)");
4115         QVERIFY(ret.isNumber());
4116         QCOMPARE(ret.toNumber(), qreal(123));
4117     }
4118     {
4119         QJSValue ret = eng.evaluate("Number('456')");
4120         QVERIFY(ret.isNumber());
4121         QCOMPARE(ret.toNumber(), qreal(456));
4122     }
4123     {
4124         QJSValue ret = eng.evaluate("new Number()");
4125         QVERIFY(!ret.isNumber());
4126         QVERIFY(ret.isObject());
4127         QCOMPARE(ret.toNumber(), qreal(0));
4128     }
4129     {
4130         QJSValue ret = eng.evaluate("new Number(123)");
4131         QVERIFY(!ret.isNumber());
4132         QVERIFY(ret.isObject());
4133         QCOMPARE(ret.toNumber(), qreal(123));
4134     }
4135     {
4136         QJSValue ret = eng.evaluate("new Number('456')");
4137         QVERIFY(!ret.isNumber());
4138         QVERIFY(ret.isObject());
4139         QCOMPARE(ret.toNumber(), qreal(456));
4140     }
4141
4142     QVERIFY(proto.property("toString").isFunction());
4143     {
4144         QJSValue ret = eng.evaluate("new Number(123).toString()");
4145         QVERIFY(ret.isString());
4146         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
4147     }
4148     {
4149         QJSValue ret = eng.evaluate("new Number(123).toString(8)");
4150         QVERIFY(ret.isString());
4151         QCOMPARE(ret.toString(), QString::fromLatin1("173"));
4152     }
4153     {
4154         QJSValue ret = eng.evaluate("new Number(123).toString(16)");
4155         QVERIFY(ret.isString());
4156         QCOMPARE(ret.toString(), QString::fromLatin1("7b"));
4157     }
4158     QVERIFY(proto.property("toLocaleString").isFunction());
4159     {
4160         QJSValue ret = eng.evaluate("new Number(123).toLocaleString()");
4161         QVERIFY(ret.isString());
4162         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
4163     }
4164     QVERIFY(proto.property("valueOf").isFunction());
4165     {
4166         QJSValue ret = eng.evaluate("new Number(123).valueOf()");
4167         QVERIFY(ret.isNumber());
4168         QCOMPARE(ret.toNumber(), qreal(123));
4169     }
4170     QVERIFY(proto.property("toExponential").isFunction());
4171     {
4172         QJSValue ret = eng.evaluate("new Number(123).toExponential()");
4173         QVERIFY(ret.isString());
4174         QCOMPARE(ret.toString(), QString::fromLatin1("1.23e+2"));
4175     }
4176     QVERIFY(proto.property("toFixed").isFunction());
4177     {
4178         QJSValue ret = eng.evaluate("new Number(123).toFixed()");
4179         QVERIFY(ret.isString());
4180         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
4181     }
4182     QVERIFY(proto.property("toPrecision").isFunction());
4183     {
4184         QJSValue ret = eng.evaluate("new Number(123).toPrecision()");
4185         QVERIFY(ret.isString());
4186         QCOMPARE(ret.toString(), QString::fromLatin1("123"));
4187     }
4188 }
4189
4190 void tst_QJSEngine::jsForInStatement_simple()
4191 {
4192     QJSEngine eng;
4193     {
4194         QJSValue ret = eng.evaluate("o = { }; r = []; for (var p in o) r[r.length] = p; r");
4195         QStringList lst = qjsvalue_cast<QStringList>(ret);
4196         QVERIFY(lst.isEmpty());
4197     }
4198     {
4199         QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];"
4200                                         "for (var p in o) r[r.length] = p; r");
4201         QStringList lst = qjsvalue_cast<QStringList>(ret);
4202         QCOMPARE(lst.size(), 1);
4203         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4204     }
4205     {
4206         QJSValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
4207                                         "for (var p in o) r[r.length] = p; r");
4208         QStringList lst = qjsvalue_cast<QStringList>(ret);
4209         QCOMPARE(lst.size(), 2);
4210         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4211         QCOMPARE(lst.at(1), QString::fromLatin1("q"));
4212     }
4213 }
4214
4215 void tst_QJSEngine::jsForInStatement_prototypeProperties()
4216 {
4217     QJSEngine eng;
4218     {
4219         QJSValue ret = eng.evaluate("o = { }; o.__proto__ = { p: 123 }; r = [];"
4220                                         "for (var p in o) r[r.length] = p; r");
4221         QStringList lst = qjsvalue_cast<QStringList>(ret);
4222         QCOMPARE(lst.size(), 1);
4223         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4224     }
4225     {
4226         QJSValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { q: 456 }; r = [];"
4227                                         "for (var p in o) r[r.length] = p; r");
4228         QStringList lst = qjsvalue_cast<QStringList>(ret);
4229         QCOMPARE(lst.size(), 2);
4230         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4231         QCOMPARE(lst.at(1), QString::fromLatin1("q"));
4232     }
4233     {
4234         // shadowed property
4235         QJSValue ret = eng.evaluate("o = { p: 123 }; o.__proto__ = { p: 456 }; r = [];"
4236                                         "for (var p in o) r[r.length] = p; r");
4237         QStringList lst = qjsvalue_cast<QStringList>(ret);
4238         QCOMPARE(lst.size(), 1);
4239         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4240     }
4241
4242 }
4243
4244 void tst_QJSEngine::jsForInStatement_mutateWhileIterating()
4245 {
4246     QJSEngine eng;
4247     // deleting property during enumeration
4248     {
4249         QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];"
4250                                         "for (var p in o) { r[r.length] = p; delete r[p]; } r");
4251         QStringList lst = qjsvalue_cast<QStringList>(ret);
4252         QCOMPARE(lst.size(), 1);
4253         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4254     }
4255     {
4256         QJSValue ret = eng.evaluate("o = { p: 123, q: 456 }; r = [];"
4257                                         "for (var p in o) { r[r.length] = p; delete o.q; } r");
4258         QStringList lst = qjsvalue_cast<QStringList>(ret);
4259         QCOMPARE(lst.size(), 1);
4260         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4261     }
4262
4263     // adding property during enumeration
4264     {
4265         QJSValue ret = eng.evaluate("o = { p: 123 }; r = [];"
4266                                         "for (var p in o) { r[r.length] = p; o.q = 456; } r");
4267         QStringList lst = qjsvalue_cast<QStringList>(ret);
4268         QCOMPARE(lst.size(), 1);
4269         QCOMPARE(lst.at(0), QString::fromLatin1("p"));
4270     }
4271
4272 }
4273
4274 void tst_QJSEngine::jsForInStatement_arrays()
4275 {
4276     QJSEngine eng;
4277     {
4278         QJSValue ret = eng.evaluate("a = [123, 456]; r = [];"
4279                                         "for (var p in a) r[r.length] = p; r");
4280         QStringList lst = qjsvalue_cast<QStringList>(ret);
4281         QCOMPARE(lst.size(), 2);
4282         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
4283         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
4284     }
4285     {
4286         QJSValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar'; r = [];"
4287                                         "for (var p in a) r[r.length] = p; r");
4288         QStringList lst = qjsvalue_cast<QStringList>(ret);
4289         QCOMPARE(lst.size(), 3);
4290         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
4291         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
4292         QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
4293     }
4294     {
4295         QJSValue ret = eng.evaluate("a = [123, 456]; a.foo = 'bar';"
4296                                         "b = [111, 222, 333]; b.bar = 'baz';"
4297                                         "a.__proto__ = b; r = [];"
4298                                         "for (var p in a) r[r.length] = p; r");
4299         QStringList lst = qjsvalue_cast<QStringList>(ret);
4300         QCOMPARE(lst.size(), 5);
4301         QCOMPARE(lst.at(0), QString::fromLatin1("0"));
4302         QCOMPARE(lst.at(1), QString::fromLatin1("1"));
4303         QCOMPARE(lst.at(2), QString::fromLatin1("foo"));
4304         QCOMPARE(lst.at(3), QString::fromLatin1("2"));
4305         QCOMPARE(lst.at(4), QString::fromLatin1("bar"));
4306     }
4307 }
4308
4309 void tst_QJSEngine::jsForInStatement_nullAndUndefined()
4310 {
4311     QJSEngine eng;
4312     {
4313         QJSValue ret = eng.evaluate("r = true; for (var p in undefined) r = false; r");
4314         QVERIFY(ret.isBool());
4315         QVERIFY(ret.toBool());
4316     }
4317     {
4318         QJSValue ret = eng.evaluate("r = true; for (var p in null) r = false; r");
4319         QVERIFY(ret.isBool());
4320         QVERIFY(ret.toBool());
4321     }
4322 }
4323
4324 void tst_QJSEngine::jsFunctionDeclarationAsStatement()
4325 {
4326     // ECMA-262 does not allow function declarations to be used as statements,
4327     // but several popular implementations (including JSC) do. See the NOTE
4328     // at the beginning of chapter 12 in ECMA-262 5th edition, where it's
4329     // recommended that implementations either disallow this usage or issue
4330     // a warning.
4331     // Since we had a bug report long ago about QtScript not supporting this
4332     // "feature" (and thus deviating from other implementations), we still
4333     // check this behavior.
4334
4335     QJSEngine eng;
4336     QVERIFY(!eng.globalObject().property("bar").isValid());
4337     eng.evaluate("function foo(arg) {\n"
4338                  "  if (arg == 'bar')\n"
4339                  "    function bar() { return 'bar'; }\n"
4340                  "  else\n"
4341                  "    function baz() { return 'baz'; }\n"
4342                  "  return (arg == 'bar') ? bar : baz;\n"
4343                  "}");
4344     QVERIFY(!eng.globalObject().property("bar").isValid());
4345     QVERIFY(!eng.globalObject().property("baz").isValid());
4346     QVERIFY(eng.evaluate("foo").isFunction());
4347     {
4348         QJSValue ret = eng.evaluate("foo('bar')");
4349         QVERIFY(ret.isFunction());
4350         QJSValue ret2 = ret.call(QJSValue());
4351         QCOMPARE(ret2.toString(), QString::fromLatin1("bar"));
4352         QVERIFY(!eng.globalObject().property("bar").isValid());
4353         QVERIFY(!eng.globalObject().property("baz").isValid());
4354     }
4355     {
4356         QJSValue ret = eng.evaluate("foo('baz')");
4357         QVERIFY(ret.isFunction());
4358         QJSValue ret2 = ret.call(QJSValue());
4359         QCOMPARE(ret2.toString(), QString::fromLatin1("baz"));
4360         QVERIFY(!eng.globalObject().property("bar").isValid());
4361         QVERIFY(!eng.globalObject().property("baz").isValid());
4362     }
4363 }
4364
4365 void tst_QJSEngine::stringObjects()
4366 {
4367     // See ECMA-262 Section 15.5, "String Objects".
4368
4369     QJSEngine eng;
4370     QString str("ciao");
4371     // in C++
4372     {
4373         QJSValue obj = QJSValue(&eng, str).toObject();
4374         QCOMPARE(obj.property("length").toInt32(), str.length());
4375         QCOMPARE(obj.propertyFlags("length"), QJSValue::PropertyFlags(QJSValue::Undeletable | QJSValue::SkipInEnumeration | QJSValue::ReadOnly));
4376         for (int i = 0; i < str.length(); ++i) {
4377             QString pname = QString::number(i);
4378             QVERIFY(obj.property(pname).isString());
4379             QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
4380             QEXPECT_FAIL("", "FIXME: This is V8 issue 862. ECMA script standard 15.5.5.2 compliance.", Continue);
4381             QCOMPARE(obj.propertyFlags(pname), QJSValue::PropertyFlags(QJSValue::Undeletable | QJSValue::ReadOnly));
4382             obj.setProperty(pname, QJSValue());
4383             obj.setProperty(pname, QJSValue(&eng, 123));
4384             QVERIFY(obj.property(pname).isString());
4385             QCOMPARE(obj.property(pname).toString(), QString(str.at(i)));
4386         }
4387         QVERIFY(!obj.property("-1").isValid());
4388         QVERIFY(!obj.property(QString::number(str.length())).isValid());
4389
4390         QJSValue val(&eng, 123);
4391         obj.setProperty("-1", val);
4392         QVERIFY(obj.property("-1").strictlyEquals(val));
4393         obj.setProperty("100", val);
4394         QVERIFY(obj.property("100").strictlyEquals(val));
4395     }
4396
4397     // in script
4398     {
4399         QJSValue ret = eng.evaluate("s = new String('ciao'); r = []; for (var p in s) r.push(p); r");
4400         QVERIFY(ret.isArray());
4401         QStringList lst = qjsvalue_cast<QStringList>(ret);
4402         QCOMPARE(lst.size(), str.length());
4403         for (int i = 0; i < str.length(); ++i)
4404             QCOMPARE(lst.at(i), QString::number(i));
4405
4406         QJSValue ret2 = eng.evaluate("s[0] = 123; s[0]");
4407         QVERIFY(ret2.isString());
4408         QCOMPARE(ret2.toString().length(), 1);
4409         QCOMPARE(ret2.toString().at(0), str.at(0));
4410
4411         QJSValue ret3 = eng.evaluate("s[-1] = 123; s[-1]");
4412         QVERIFY(ret3.isNumber());
4413         QCOMPARE(ret3.toInt32(), 123);
4414
4415         QJSValue ret4 = eng.evaluate("s[s.length] = 456; s[s.length]");
4416         QVERIFY(ret4.isNumber());
4417         QCOMPARE(ret4.toInt32(), 456);
4418
4419         QJSValue ret5 = eng.evaluate("delete s[0]");
4420         QVERIFY(ret5.isBoolean());
4421         QEXPECT_FAIL("", "FIXME: This is V8 bug, please report it! ECMA script standard 15.5.5.2", Abort);
4422         QVERIFY(!ret5.toBoolean());
4423
4424         QJSValue ret6 = eng.evaluate("delete s[-1]");
4425         QVERIFY(ret6.isBoolean());
4426         QVERIFY(ret6.toBoolean());
4427
4428         QJSValue ret7 = eng.evaluate("delete s[s.length]");
4429         QVERIFY(ret7.isBoolean());
4430         QVERIFY(ret7.toBoolean());
4431     }
4432 }
4433
4434 void tst_QJSEngine::jsStringPrototypeReplaceBugs()
4435 {
4436     QJSEngine eng;
4437     // task 212440
4438     {
4439         QJSValue ret = eng.evaluate("replace_args = []; \"a a a\".replace(/(a)/g, function() { replace_args.push(arguments); }); replace_args");
4440         QVERIFY(ret.isArray());
4441         int len = ret.property("length").toInt32();
4442         QCOMPARE(len, 3);
4443         for (int i = 0; i < len; ++i) {
4444             QJSValue args = ret.property(i);
4445             QCOMPARE(args.property("length").toInt32(), 4);
4446             QCOMPARE(args.property(0).toString(), QString::fromLatin1("a")); // matched string
4447             QCOMPARE(args.property(1).toString(), QString::fromLatin1("a")); // capture
4448             QVERIFY(args.property(2).isNumber());
4449             QCOMPARE(args.property(2).toInt32(), i*2); // index of match
4450             QCOMPARE(args.property(3).toString(), QString::fromLatin1("a a a"));
4451         }
4452     }
4453     // task 212501
4454     {
4455         QJSValue ret = eng.evaluate("\"foo\".replace(/a/g, function() {})");
4456         QVERIFY(ret.isString());
4457         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4458     }
4459 }
4460
4461 void tst_QJSEngine::getterSetterThisObject_global()
4462 {
4463     {
4464         QJSEngine eng;
4465         // read
4466         eng.evaluate("__defineGetter__('x', function() { return this; });");
4467         {
4468             QJSValue ret = eng.evaluate("x");
4469             QVERIFY(ret.equals(eng.globalObject()));
4470         }
4471         {
4472             QJSValue ret = eng.evaluate("(function() { return x; })()");
4473             QVERIFY(ret.equals(eng.globalObject()));
4474         }
4475         {
4476             QJSValue ret = eng.evaluate("with (this) x");
4477             QVERIFY(ret.equals(eng.globalObject()));
4478         }
4479         {
4480             QJSValue ret = eng.evaluate("with ({}) x");
4481             QVERIFY(ret.equals(eng.globalObject()));
4482         }
4483         {
4484             QJSValue ret = eng.evaluate("(function() { with ({}) return x; })()");
4485             QVERIFY(ret.equals(eng.globalObject()));
4486         }
4487         // write
4488         eng.evaluate("__defineSetter__('x', function() { return this; });");
4489         {
4490             QJSValue ret = eng.evaluate("x = 'foo'");
4491             // SpiderMonkey says setter return value, JSC says RHS.
4492             QVERIFY(ret.isString());
4493             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4494         }
4495         {
4496             QJSValue ret = eng.evaluate("(function() { return x = 'foo'; })()");
4497             // SpiderMonkey says setter return value, JSC says RHS.
4498             QVERIFY(ret.isString());
4499             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4500         }
4501         {
4502             QJSValue ret = eng.evaluate("with (this) x = 'foo'");
4503             // SpiderMonkey says setter return value, JSC says RHS.
4504             QVERIFY(ret.isString());
4505             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4506         }
4507         {
4508             QJSValue ret = eng.evaluate("with ({}) x = 'foo'");
4509             // SpiderMonkey says setter return value, JSC says RHS.
4510             QVERIFY(ret.isString());
4511             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4512         }
4513         {
4514             QJSValue ret = eng.evaluate("(function() { with ({}) return x = 'foo'; })()");
4515             // SpiderMonkey says setter return value, JSC says RHS.
4516             QVERIFY(ret.isString());
4517             QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
4518         }
4519     }
4520 }
4521
4522 void tst_QJSEngine::getterSetterThisObject_plain()
4523 {
4524     {
4525         QJSEngine eng;
4526         eng.evaluate("o = {}");
4527         // read
4528         eng.evaluate("o.__defineGetter__('x', function() { return this; })");
4529         QVERIFY(eng.evaluate("o.x === o").toBoolean());
4530         QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
4531         QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
4532         eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
4533         // write
4534         eng.evaluate("o.__defineSetter__('x', function() { return this; });");
4535         // SpiderMonkey says setter return value, JSC says RHS.
4536         QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
4537         QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
4538         QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
4539     }
4540 }
4541
4542 void tst_QJSEngine::getterSetterThisObject_prototypeChain()
4543 {
4544     {
4545         QJSEngine eng;
4546         eng.evaluate("o = {}; p = {}; o.__proto__ = p");
4547         // read
4548         eng.evaluate("p.__defineGetter__('x', function() { return this; })");
4549         QVERIFY(eng.evaluate("o.x === o").toBoolean());
4550         QVERIFY(eng.evaluate("with (o) x").equals(eng.evaluate("o")));
4551         QVERIFY(eng.evaluate("(function() { with (o) return x; })() === o").toBoolean());
4552         eng.evaluate("q = {}; with (o) with (q) x").equals(eng.evaluate("o"));
4553         eng.evaluate("with (q) with (o) x").equals(eng.evaluate("o"));
4554         // write
4555         eng.evaluate("o.__defineSetter__('x', function() { return this; });");
4556         // SpiderMonkey says setter return value, JSC says RHS.
4557         QVERIFY(eng.evaluate("(o.x = 'foo') === 'foo'").toBoolean());
4558         QVERIFY(eng.evaluate("with (o) x = 'foo'").equals("foo"));
4559         QVERIFY(eng.evaluate("with (o) with (q) x = 'foo'").equals("foo"));
4560     }
4561 }
4562
4563 #if 0 // ###FIXME: no QScriptContext API
4564 void tst_QJSEngine::getterSetterThisObject_activation()
4565 {
4566     {
4567         QScriptEngine eng;
4568         QScriptContext *ctx = eng.pushContext();
4569         QVERIFY(ctx != 0);
4570         QScriptValue act = ctx->activationObject();
4571         act.setProperty("act", act);
4572         // read
4573         eng.evaluate("act.__defineGetter__('x', function() { return this; })");
4574         QVERIFY(eng.evaluate("x === act").toBoolean());
4575         QEXPECT_FAIL("", "QTBUG-17605: Not possible to implement local variables as getter/setter properties", Abort);
4576         QVERIFY(!eng.hasUncaughtException());
4577         QVERIFY(eng.evaluate("with (act) x").equals("foo"));
4578         QVERIFY(eng.evaluate("(function() { with (act) return x; })() === act").toBoolean());
4579         eng.evaluate("q = {}; with (act) with (q) x").equals(eng.evaluate("act"));
4580         eng.evaluate("with (q) with (act) x").equals(eng.evaluate("act"));
4581         // write
4582         eng.evaluate("act.__defineSetter__('x', function() { return this; });");
4583         QVERIFY(eng.evaluate("(x = 'foo') === 'foo'").toBoolean());
4584         QVERIFY(eng.evaluate("with (act) x = 'foo'").equals("foo"));
4585         QVERIFY(eng.evaluate("with (act) with (q) x = 'foo'").equals("foo"));
4586         eng.popContext();
4587     }
4588 }
4589 #endif
4590
4591 void tst_QJSEngine::jsContinueInSwitch()
4592 {
4593     // This is testing ECMA-262 compliance, not C++ API.
4594
4595     QJSEngine eng;
4596     // switch - continue
4597     {
4598         QJSValue ret = eng.evaluate("switch (1) { default: continue; }");
4599         QVERIFY(ret.isError());
4600     }
4601     // for - switch - case - continue
4602     {
4603         QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4604                                         "  switch (i) {\n"
4605                                         "    case 1: ++j; continue;\n"
4606                                         "    case 100: ++j; continue;\n"
4607                                         "    case 1000: ++j; continue;\n"
4608                                         "  }\n"
4609                                         "}; j");
4610         QVERIFY(ret.isNumber());
4611         QCOMPARE(ret.toInt32(), 3);
4612     }
4613     // for - switch - case - default - continue
4614     {
4615         QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4616                                         "  switch (i) {\n"
4617                                         "    case 1: ++j; continue;\n"
4618                                         "    case 100: ++j; continue;\n"
4619                                         "    case 1000: ++j; continue;\n"
4620                                         "    default: if (i < 50000) break; else continue;\n"
4621                                         "  }\n"
4622                                         "}; j");
4623         QVERIFY(ret.isNumber());
4624         QCOMPARE(ret.toInt32(), 3);
4625     }
4626     // switch - for - continue
4627     {
4628         QJSValue ret = eng.evaluate("j = 123; switch (j) {\n"
4629                                         "  case 123:\n"
4630                                         "  for (i = 0; i < 100000; ++i) {\n"
4631                                         "    continue;\n"
4632                                         "  }\n"
4633                                         "}; i\n");
4634         QVERIFY(ret.isNumber());
4635         QCOMPARE(ret.toInt32(), 100000);
4636     }
4637     // switch - switch - continue
4638     {
4639         QJSValue ret = eng.evaluate("i = 1; switch (i) { default: switch (i) { case 1: continue; } }");
4640         QVERIFY(ret.isError());
4641     }
4642     // for - switch - switch - continue
4643     {
4644         QJSValue ret = eng.evaluate("j = 0; for (i = 0; i < 100000; ++i) {\n"
4645                                         "  switch (i) {\n"
4646                                         "    case 1:\n"
4647                                         "    switch (i) {\n"
4648                                         "      case 1: ++j; continue;\n"
4649                                         "    }\n"
4650                                         "  }\n"
4651                                         "}; j");
4652         QVERIFY(ret.isNumber());
4653         QCOMPARE(ret.toInt32(), 1);
4654     }
4655     // switch - for - switch - continue
4656     {
4657         QJSValue ret = eng.evaluate("j = 123; switch (j) {\n"
4658                                         "  case 123:\n"
4659                                         "  for (i = 0; i < 100000; ++i) {\n"
4660                                         "    switch (i) {\n"
4661                                         "      case 1:\n"
4662                                         "      ++j; continue;\n"
4663                                         "    }\n"
4664                                         "  }\n"
4665                                         "}; i\n");
4666         QVERIFY(ret.isNumber());
4667         QCOMPARE(ret.toInt32(), 100000);
4668     }
4669 }
4670
4671 void tst_QJSEngine::jsShadowReadOnlyPrototypeProperty()
4672 {
4673     // SpiderMonkey has different behavior than JSC and V8; it disallows
4674     // creating a property on the instance if there's a property with the
4675     // same name in the prototype, and that property is read-only. We
4676     // adopted that behavior in the old (4.5) QtScript back-end, but it
4677     // just seems weird -- and non-compliant. Adopt the JSC behavior instead.
4678     QJSEngine eng;
4679     QVERIFY(eng.evaluate("o = {}; o.__proto__ = parseInt; o.length").isNumber());
4680     QCOMPARE(eng.evaluate("o.length = 123; o.length").toInt32(), 123);
4681     QVERIFY(eng.evaluate("o.hasOwnProperty('length')").toBoolean());
4682 }
4683
4684 void tst_QJSEngine::toObject()
4685 {
4686     QJSEngine eng;
4687
4688     QVERIFY(!eng.toObject(eng.undefinedValue()).isValid());
4689
4690     QVERIFY(!eng.toObject(eng.nullValue()).isValid());
4691
4692     QJSValue falskt(false);
4693     {
4694         QJSValue tmp = eng.toObject(falskt);
4695         QVERIFY(tmp.isObject());
4696         QCOMPARE(tmp.toNumber(), falskt.toNumber());
4697     }
4698     QVERIFY(falskt.isBool());
4699
4700     QJSValue sant(true);
4701     {
4702         QJSValue tmp = eng.toObject(sant);
4703         QVERIFY(tmp.isObject());
4704         QCOMPARE(tmp.toNumber(), sant.toNumber());
4705     }
4706     QVERIFY(sant.isBool());
4707
4708     QJSValue number(123.0);
4709     {
4710         QJSValue tmp = eng.toObject(number);
4711         QVERIFY(tmp.isObject());
4712         QCOMPARE(tmp.toNumber(), number.toNumber());
4713     }
4714     QVERIFY(number.isNumber());
4715
4716     QJSValue str = QJSValue(&eng, QString("ciao"));
4717     {
4718         QJSValue tmp = eng.toObject(str);
4719         QVERIFY(tmp.isObject());
4720         QCOMPARE(tmp.toString(), str.toString());
4721     }
4722     QVERIFY(str.isString());
4723
4724     QJSValue object = eng.newObject();
4725     {
4726         QJSValue tmp = eng.toObject(object);
4727         QVERIFY(tmp.isObject());
4728         QVERIFY(tmp.strictlyEquals(object));
4729     }
4730
4731     QJSValue qobject = eng.newQObject(this);
4732     QVERIFY(eng.toObject(qobject).strictlyEquals(qobject));
4733
4734     QVERIFY(!eng.toObject(QJSValue()).isValid());
4735
4736     // v1 constructors
4737
4738     QJSValue boolValue(&eng, true);
4739     {
4740         QJSValue ret = eng.toObject(boolValue);
4741         QVERIFY(ret.isObject());
4742         QCOMPARE(ret.toBool(), boolValue.toBool());
4743     }
4744     QVERIFY(boolValue.isBool());
4745
4746     QJSValue numberValue(&eng, 123.0);
4747     {
4748         QJSValue ret = eng.toObject(numberValue);
4749         QVERIFY(ret.isObject());
4750         QCOMPARE(ret.toNumber(), numberValue.toNumber());
4751     }
4752     QVERIFY(numberValue.isNumber());
4753
4754     QJSValue stringValue(&eng, QString::fromLatin1("foo"));
4755     {
4756         QJSValue ret = eng.toObject(stringValue);
4757         QVERIFY(ret.isObject());
4758         QCOMPARE(ret.toString(), stringValue.toString());
4759     }
4760     QVERIFY(stringValue.isString());
4761 }
4762
4763 void tst_QJSEngine::jsReservedWords_data()
4764 {
4765     QTest::addColumn<QString>("word");
4766     QTest::newRow("break") << QString("break");
4767     QTest::newRow("case") << QString("case");
4768     QTest::newRow("catch") << QString("catch");
4769     QTest::newRow("continue") << QString("continue");
4770     QTest::newRow("default") << QString("default");
4771     QTest::newRow("delete") << QString("delete");
4772     QTest::newRow("do") << QString("do");
4773     QTest::newRow("else") << QString("else");
4774     QTest::newRow("false") << QString("false");
4775     QTest::newRow("finally") << QString("finally");
4776     QTest::newRow("for") << QString("for");
4777     QTest::newRow("function") << QString("function");
4778     QTest::newRow("if") << QString("if");
4779     QTest::newRow("in") << QString("in");
4780     QTest::newRow("instanceof") << QString("instanceof");
4781     QTest::newRow("new") << QString("new");
4782     QTest::newRow("null") << QString("null");
4783     QTest::newRow("return") << QString("return");
4784     QTest::newRow("switch") << QString("switch");
4785     QTest::newRow("this") << QString("this");
4786     QTest::newRow("throw") << QString("throw");
4787     QTest::newRow("true") << QString("true");
4788     QTest::newRow("try") << QString("try");
4789     QTest::newRow("typeof") << QString("typeof");
4790     QTest::newRow("var") << QString("var");
4791     QTest::newRow("void") << QString("void");
4792     QTest::newRow("while") << QString("while");
4793     QTest::newRow("with") << QString("with");
4794 }
4795
4796 void tst_QJSEngine::jsReservedWords()
4797 {
4798     // See ECMA-262 Section 7.6.1, "Reserved Words".
4799     // We prefer that the implementation is less strict than the spec; e.g.
4800     // it's good to allow reserved words as identifiers in object literals,
4801     // and when accessing properties using dot notation.
4802
4803     QFETCH(QString, word);
4804     {
4805         QJSEngine eng;
4806         QJSValue ret = eng.evaluate(word + " = 123");
4807         QVERIFY(ret.isError());
4808         QString str = ret.toString();
4809         QVERIFY(str.startsWith("SyntaxError") || str.startsWith("ReferenceError"));
4810     }
4811     {
4812         QJSEngine eng;
4813         QJSValue ret = eng.evaluate("var " + word + " = 123");
4814         QVERIFY(ret.isError());
4815         QVERIFY(ret.toString().startsWith("SyntaxError"));
4816     }
4817     {
4818         QJSEngine eng;
4819         QJSValue ret = eng.evaluate("o = {}; o." + word + " = 123");
4820         // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC
4821         QVERIFY(!ret.isError());
4822         QVERIFY(ret.strictlyEquals(eng.evaluate("o." + word)));
4823     }
4824     {
4825         QJSEngine eng;
4826         QJSValue ret = eng.evaluate("o = { " + word + ": 123 }");
4827         // in the old back-end, in SpiderMonkey and in v8, this is allowed, but not in JSC
4828         QVERIFY(!ret.isError());
4829         QVERIFY(ret.property(word).isNumber());
4830     }
4831     {
4832         // SpiderMonkey allows this, but we don't
4833         QJSEngine eng;
4834         QJSValue ret = eng.evaluate("function " + word + "() {}");
4835         QVERIFY(ret.isError());
4836         QVERIFY(ret.toString().startsWith("SyntaxError"));
4837     }
4838 }
4839
4840 void tst_QJSEngine::jsFutureReservedWords_data()
4841 {
4842     QTest::addColumn<QString>("word");
4843     QTest::addColumn<bool>("allowed");
4844     QTest::newRow("abstract") << QString("abstract") << true;
4845     QTest::newRow("boolean") << QString("boolean") << true;
4846     QTest::newRow("byte") << QString("byte") << true;
4847     QTest::newRow("char") << QString("char") << true;
4848     QTest::newRow("class") << QString("class") << false;
4849     QTest::newRow("const") << QString("const") << false;
4850     QTest::newRow("debugger") << QString("debugger") << false;
4851     QTest::newRow("double") << QString("double") << true;
4852     QTest::newRow("enum") << QString("enum") << false;
4853     QTest::newRow("export") << QString("export") << false;
4854     QTest::newRow("extends") << QString("extends") << false;
4855     QTest::newRow("final") << QString("final") << true;
4856     QTest::newRow("float") << QString("float") << true;
4857     QTest::newRow("goto") << QString("goto") << true;
4858     QTest::newRow("implements") << QString("implements") << true;
4859     QTest::newRow("import") << QString("import") << false;
4860     QTest::newRow("int") << QString("int") << true;
4861     QTest::newRow("interface") << QString("interface") << true;
4862     QTest::newRow("long") << QString("long") << true;
4863     QTest::newRow("native") << QString("native") << true;
4864     QTest::newRow("package") << QString("package") << true;
4865     QTest::newRow("private") << QString("private") << true;
4866     QTest::newRow("protected") << QString("protected") << true;
4867     QTest::newRow("public") << QString("public") << true;
4868     QTest::newRow("short") << QString("short") << true;
4869     QTest::newRow("static") << QString("static") << true;
4870     QTest::newRow("super") << QString("super") << false;
4871     QTest::newRow("synchronized") << QString("synchronized") << true;
4872     QTest::newRow("throws") << QString("throws") << true;
4873     QTest::newRow("transient") << QString("transient") << true;
4874     QTest::newRow("volatile") << QString("volatile") << true;
4875 }
4876
4877 void tst_QJSEngine::jsFutureReservedWords()
4878 {
4879     QSKIP("Fails");
4880     // See ECMA-262 Section 7.6.1.2, "Future Reserved Words".
4881     // In real-world implementations, most of these words are
4882     // actually allowed as normal identifiers.
4883
4884     QFETCH(QString, word);
4885     QFETCH(bool, allowed);
4886     {
4887         QJSEngine eng;
4888         QJSValue ret = eng.evaluate(word + " = 123");
4889         QCOMPARE(!ret.isError(), allowed);
4890     }
4891     {
4892         QJSEngine eng;
4893         QJSValue ret = eng.evaluate("var " + word + " = 123");
4894         QCOMPARE(!ret.isError(), allowed);
4895     }
4896     {
4897         // this should probably be allowed (see task 162567)
4898         QJSEngine eng;
4899         QJSValue ret = eng.evaluate("o = {}; o." + word + " = 123");
4900         QCOMPARE(ret.isNumber(), allowed);
4901         QCOMPARE(!ret.isError(), allowed);
4902     }
4903     {
4904         // this should probably be allowed (see task 162567)
4905         QJSEngine eng;
4906         QJSValue ret = eng.evaluate("o = { " + word + ": 123 }");
4907         QCOMPARE(!ret.isError(), allowed);
4908     }
4909 }
4910
4911 void tst_QJSEngine::jsThrowInsideWithStatement()
4912 {
4913     // This is testing ECMA-262 compliance, not C++ API.
4914
4915     // task 209988
4916     QJSEngine eng;
4917     {
4918         QJSValue ret = eng.evaluate(
4919             "try {"
4920             "  o = { bad : \"bug\" };"
4921             "  with (o) {"
4922             "    throw 123;"
4923             "  }"
4924             "} catch (e) {"
4925             "  bad;"
4926             "}");
4927         QVERIFY(ret.isError());
4928         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
4929     }
4930     {
4931         QJSValue ret = eng.evaluate(
4932             "try {"
4933             "  o = { bad : \"bug\" };"
4934             "  with (o) {"
4935             "    throw 123;"
4936             "  }"
4937             "} finally {"
4938             "  bad;"
4939             "}");
4940         QVERIFY(ret.isError());
4941         QVERIFY(ret.toString().contains(QString::fromLatin1("ReferenceError")));
4942     }
4943     {
4944         eng.clearExceptions();
4945         QJSValue ret = eng.evaluate(
4946             "o = { bug : \"no bug\" };"
4947             "with (o) {"
4948             "  try {"
4949             "    throw 123;"
4950             "  } finally {"
4951             "    bug;"
4952             "  }"
4953             "}");
4954         QVERIFY(ret.isNumber());
4955         QCOMPARE(ret.toInt32(), 123);
4956         QVERIFY(eng.hasUncaughtException());
4957     }
4958     {
4959         eng.clearExceptions();
4960         QJSValue ret = eng.evaluate(
4961             "o = { bug : \"no bug\" };"
4962             "with (o) {"
4963             "    throw 123;"
4964             "}");
4965         QVERIFY(ret.isNumber());
4966         QJSValue ret2 = eng.evaluate("bug");
4967         QVERIFY(ret2.isError());
4968         QVERIFY(ret2.toString().contains(QString::fromLatin1("ReferenceError")));
4969     }
4970 }
4971
4972 #if 0 // ###FIXME: No QScriptEngineAgent API
4973 class TestAgent : public QScriptEngineAgent
4974 {
4975 public:
4976     TestAgent(QScriptEngine *engine) : QScriptEngineAgent(engine) {}
4977 };
4978
4979 void tst_QJSEngine::getSetAgent_ownership()
4980 {
4981     // engine deleted before agent --> agent deleted too
4982     QScriptEngine *eng = new QScriptEngine;
4983     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
4984     TestAgent *agent = new TestAgent(eng);
4985     eng->setAgent(agent);
4986     QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
4987     eng->setAgent(0); // the engine maintains ownership of the old agent
4988     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
4989     delete eng;
4990 }
4991
4992 void tst_QJSEngine::getSetAgent_deleteAgent()
4993 {
4994     // agent deleted before engine --> engine's agent should become 0
4995     QScriptEngine *eng = new QScriptEngine;
4996     TestAgent *agent = new TestAgent(eng);
4997     eng->setAgent(agent);
4998     QCOMPARE(eng->agent(), (QScriptEngineAgent*)agent);
4999     delete agent;
5000     QCOMPARE(eng->agent(), (QScriptEngineAgent*)0);
5001     eng->evaluate("(function(){ return 123; })()");
5002     delete eng;
5003 }
5004
5005 void tst_QJSEngine::getSetAgent_differentEngine()
5006 {
5007     QScriptEngine eng;
5008     QScriptEngine eng2;
5009     TestAgent *agent = new TestAgent(&eng);
5010     QTest::ignoreMessage(QtWarningMsg, "QScriptEngine::setAgent(): cannot set agent belonging to different engine");
5011     eng2.setAgent(agent);
5012     QCOMPARE(eng2.agent(), (QScriptEngineAgent*)0);
5013 }
5014 #endif
5015
5016 #if 0 // ###FIXME: No QScriptString API
5017 void tst_QJSEngine::reentrancy_stringHandles()
5018 {
5019     QScriptEngine eng1;
5020     QScriptEngine eng2;
5021     QScriptString s1 = eng1.toStringHandle("foo");
5022     QScriptString s2 = eng2.toStringHandle("foo");
5023     QVERIFY(s1 != s2);
5024 }
5025 #endif
5026
5027 #if 0 // ###FIXME: No processEventsInterval API
5028 void tst_QJSEngine::reentrancy_processEventsInterval()
5029 {
5030     QScriptEngine eng1;
5031     QScriptEngine eng2;
5032     eng1.setProcessEventsInterval(123);
5033     QCOMPARE(eng2.processEventsInterval(), -1);
5034     eng2.setProcessEventsInterval(456);
5035     QCOMPARE(eng1.processEventsInterval(), 123);
5036 }
5037 #endif
5038
5039 #if 0 // FIXME: No support for custom types
5040 void tst_QJSEngine::reentrancy_typeConversion()
5041 {
5042     QScriptEngine eng1;
5043     QScriptEngine eng2;
5044     qScriptRegisterMetaType<Foo>(&eng1, fooToScriptValue, fooFromScriptValue);
5045     Foo foo;
5046     foo.x = 12;
5047     foo.y = 34;
5048     {
5049         QScriptValue fooVal = qScriptValueFromValue(&eng1, foo);
5050         QVERIFY(fooVal.isObject());
5051         QVERIFY(!fooVal.isVariant());
5052         QCOMPARE(fooVal.property("x").toInt32(), 12);
5053         QCOMPARE(fooVal.property("y").toInt32(), 34);
5054         fooVal.setProperty("x", 56);
5055         fooVal.setProperty("y", 78);
5056
5057         Foo foo2 = eng.fromScriptValue<Foo>(fooVal);
5058         QCOMPARE(foo2.x, 56);
5059         QCOMPARE(foo2.y, 78);
5060     }
5061     {
5062         QScriptValue fooVal = qScriptValueFromValue(&eng2, foo);
5063         QVERIFY(fooVal.isVariant());
5064
5065         Foo foo2 = eng.fromScriptValue<Foo>(fooVal);
5066         QCOMPARE(foo2.x, 12);
5067         QCOMPARE(foo2.y, 34);
5068     }
5069     QVERIFY(!eng1.defaultPrototype(qMetaTypeId<Foo>()).isValid());
5070     QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
5071     QScriptValue proto1 = eng1.newObject();
5072     eng1.setDefaultPrototype(qMetaTypeId<Foo>(), proto1);
5073     QVERIFY(!eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
5074     QScriptValue proto2 = eng2.newObject();
5075     eng2.setDefaultPrototype(qMetaTypeId<Foo>(), proto2);
5076     QVERIFY(eng2.defaultPrototype(qMetaTypeId<Foo>()).isValid());
5077     QVERIFY(eng1.defaultPrototype(qMetaTypeId<Foo>()).strictlyEquals(proto1));
5078 }
5079 #endif
5080
5081 void tst_QJSEngine::reentrancy_globalObjectProperties()
5082 {
5083     QJSEngine eng1;
5084     QJSEngine eng2;
5085     QVERIFY(!eng2.globalObject().property("a").isValid());
5086     eng1.evaluate("a = 10");
5087     QVERIFY(eng1.globalObject().property("a").isNumber());
5088     QVERIFY(!eng2.globalObject().property("a").isValid());
5089     eng2.evaluate("a = 20");
5090     QVERIFY(eng2.globalObject().property("a").isNumber());
5091     QCOMPARE(eng1.globalObject().property("a").toInt32(), 10);
5092 }
5093
5094 void tst_QJSEngine::reentrancy_Array()
5095 {
5096     // weird bug with JSC backend
5097     {
5098         QJSEngine eng;
5099         QCOMPARE(eng.evaluate("Array()").toString(), QString());
5100         eng.evaluate("Array.prototype.toString");
5101         QCOMPARE(eng.evaluate("Array()").toString(), QString());
5102     }
5103     {
5104         QJSEngine eng;
5105         QCOMPARE(eng.evaluate("Array()").toString(), QString());
5106     }
5107 }
5108
5109 void tst_QJSEngine::reentrancy_objectCreation()
5110 {
5111     QJSEngine eng1;
5112     QJSEngine eng2;
5113     {
5114         QJSValue d1 = eng1.newDate(0);
5115         QJSValue d2 = eng2.newDate(0);
5116         QCOMPARE(d1.toDateTime(), d2.toDateTime());
5117         QCOMPARE(d2.toDateTime(), d1.toDateTime());
5118     }
5119     {
5120         QJSValue r1 = eng1.newRegExp("foo", "gim");
5121         QJSValue r2 = eng2.newRegExp("foo", "gim");
5122         QCOMPARE(r1.toRegExp(), r2.toRegExp());
5123         QCOMPARE(r2.toRegExp(), r1.toRegExp());
5124     }
5125     {
5126         QJSValue o1 = eng1.newQObject(this);
5127         QJSValue o2 = eng2.newQObject(this);
5128         QCOMPARE(o1.toQObject(), o2.toQObject());
5129         QCOMPARE(o2.toQObject(), o1.toQObject());
5130     }
5131 #if 0 // ###FIXME: No QScriptEngine::newQMetaObject API
5132     {
5133         QScriptValue mo1 = eng1.newQMetaObject(&staticMetaObject);
5134         QScriptValue mo2 = eng2.newQMetaObject(&staticMetaObject);
5135         QCOMPARE(mo1.toQMetaObject(), mo2.toQMetaObject());
5136         QCOMPARE(mo2.toQMetaObject(), mo1.toQMetaObject());
5137     }
5138 #endif
5139 }
5140
5141 void tst_QJSEngine::jsIncDecNonObjectProperty()
5142 {
5143     // This is testing ECMA-262 compliance, not C++ API.
5144
5145     QJSEngine eng;
5146     {
5147         QJSValue ret = eng.evaluate("var a; a.n++");
5148         QVERIFY(ret.isError());
5149         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5150     }
5151     {
5152         QJSValue ret = eng.evaluate("var a; a.n--");
5153         QVERIFY(ret.isError());
5154         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5155     }
5156     {
5157         QJSValue ret = eng.evaluate("var a = null; a.n++");
5158         QVERIFY(ret.isError());
5159         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5160     }
5161     {
5162         QJSValue ret = eng.evaluate("var a = null; a.n--");
5163         QVERIFY(ret.isError());
5164         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5165     }
5166     {
5167         QJSValue ret = eng.evaluate("var a; ++a.n");
5168         QVERIFY(ret.isError());
5169         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5170     }
5171     {
5172         QJSValue ret = eng.evaluate("var a; --a.n");
5173         QVERIFY(ret.isError());
5174         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5175     }
5176     {
5177         QJSValue ret = eng.evaluate("var a; a.n += 1");
5178         QVERIFY(ret.isError());
5179         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5180     }
5181     {
5182         QJSValue ret = eng.evaluate("var a; a.n -= 1");
5183         QVERIFY(ret.isError());
5184         QVERIFY(ret.toString().contains(QString::fromLatin1("TypeError")));
5185     }
5186     {
5187         QJSValue ret = eng.evaluate("var a = 'ciao'; a.length++");
5188         QVERIFY(ret.isNumber());
5189         QCOMPARE(ret.toInt32(), 4);
5190     }
5191     {
5192         QJSValue ret = eng.evaluate("var a = 'ciao'; a.length--");
5193         QVERIFY(ret.isNumber());
5194         QCOMPARE(ret.toInt32(), 4);
5195     }
5196     {
5197         QJSValue ret = eng.evaluate("var a = 'ciao'; ++a.length");
5198         QVERIFY(ret.isNumber());
5199         QCOMPARE(ret.toInt32(), 5);
5200     }
5201     {
5202         QJSValue ret = eng.evaluate("var a = 'ciao'; --a.length");
5203         QVERIFY(ret.isNumber());
5204         QCOMPARE(ret.toInt32(), 3);
5205     }
5206 }
5207
5208 #if 0 // ###FIXME: no installTranslatorFunctions API
5209 void tst_QJSEngine::installTranslatorFunctions()
5210 {
5211     QScriptEngine eng;
5212     QScriptValue global = eng.globalObject();
5213     QVERIFY(!global.property("qsTranslate").isValid());
5214     QVERIFY(!global.property("QT_TRANSLATE_NOOP").isValid());
5215     QVERIFY(!global.property("qsTr").isValid());
5216     QVERIFY(!global.property("QT_TR_NOOP").isValid());
5217     QVERIFY(!global.property("qsTrId").isValid());
5218     QVERIFY(!global.property("QT_TRID_NOOP").isValid());
5219     QVERIFY(!global.property("String").property("prototype").property("arg").isValid());
5220
5221     eng.installTranslatorFunctions();
5222     QVERIFY(global.property("qsTranslate").isFunction());
5223     QVERIFY(global.property("QT_TRANSLATE_NOOP").isFunction());
5224     QVERIFY(global.property("qsTr").isFunction());
5225     QVERIFY(global.property("QT_TR_NOOP").isFunction());
5226     QVERIFY(global.property("qsTrId").isFunction());
5227     QVERIFY(global.property("QT_TRID_NOOP").isFunction());
5228     QVERIFY(global.property("String").property("prototype").property("arg").isFunction());
5229
5230     {
5231         QScriptValue ret = eng.evaluate("qsTr('foo')");
5232         QVERIFY(ret.isString());
5233         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
5234     }
5235     {
5236         QScriptValue ret = eng.evaluate("qsTranslate('foo', 'bar')");
5237         QVERIFY(ret.isString());
5238         QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
5239     }
5240     {
5241         QScriptValue ret = eng.evaluate("QT_TR_NOOP('foo')");
5242         QVERIFY(ret.isString());
5243         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
5244     }
5245     {
5246         QScriptValue ret = eng.evaluate("QT_TRANSLATE_NOOP('foo', 'bar')");
5247         QVERIFY(ret.isString());
5248         QCOMPARE(ret.toString(), QString::fromLatin1("bar"));
5249     }
5250     {
5251         QScriptValue ret = eng.evaluate("'foo%0'.arg('bar')");
5252         QEXPECT_FAIL("Custom global object", "FIXME: why we expect that String prototype exists?", Abort);
5253         QVERIFY(ret.isString());
5254         QCOMPARE(ret.toString(), QString::fromLatin1("foobar"));
5255     }
5256     {
5257         QScriptValue ret = eng.evaluate("'foo%0'.arg(123)");
5258         QVERIFY(ret.isString());
5259         QCOMPARE(ret.toString(), QString::fromLatin1("foo123"));
5260     }
5261     {
5262         // Maybe this should throw an error?
5263         QScriptValue ret = eng.evaluate("'foo%0'.arg()");
5264         QVERIFY(ret.isString());
5265         QCOMPARE(ret.toString(), QString());
5266     }
5267
5268     {
5269         QScriptValue ret = eng.evaluate("qsTrId('foo')");
5270         QVERIFY(ret.isString());
5271         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
5272     }
5273     {
5274         QScriptValue ret = eng.evaluate("QT_TRID_NOOP('foo')");
5275         QVERIFY(ret.isString());
5276         QCOMPARE(ret.toString(), QString::fromLatin1("foo"));
5277     }
5278     QVERIFY(eng.evaluate("QT_TRID_NOOP()").isUndefined());
5279 }
5280
5281 class TranslationScope
5282 {
5283 public:
5284     TranslationScope(const QString &fileName)
5285     {
5286         translator.load(fileName);
5287         QCoreApplication::instance()->installTranslator(&translator);
5288     }
5289     ~TranslationScope()
5290     {
5291         QCoreApplication::instance()->removeTranslator(&translator);
5292     }
5293
5294 private:
5295     QTranslator translator;
5296 };
5297
5298 void tst_QJSEngine::translateScript_data()
5299 {
5300     QTest::addColumn<QString>("expression");
5301     QTest::addColumn<QString>("fileName");
5302     QTest::addColumn<QString>("expectedTranslation");
5303
5304     QString fileName = QString::fromLatin1("translatable.js");
5305     // Top-level
5306     QTest::newRow("qsTr('One')@translatable.js")
5307             << QString::fromLatin1("qsTr('One')") << fileName << QString::fromLatin1("En");
5308     QTest::newRow("qsTr('Hello')@translatable.js")
5309             << QString::fromLatin1("qsTr('Hello')") << fileName << QString::fromLatin1("Hallo");
5310     // From function
5311     QTest::newRow("(function() { return qsTr('One'); })()@translatable.js")
5312             << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName << QString::fromLatin1("En");
5313     QTest::newRow("(function() { return qsTr('Hello'); })()@translatable.js")
5314             << QString::fromLatin1("(function() { return qsTr('Hello'); })()") << fileName << QString::fromLatin1("Hallo");
5315     // From eval
5316     QTest::newRow("eval('qsTr(\\'One\\')')@translatable.js")
5317             << QString::fromLatin1("eval('qsTr(\\'One\\')')") << fileName << QString::fromLatin1("En");
5318     QTest::newRow("eval('qsTr(\\'Hello\\')')@translatable.js")
5319             << QString::fromLatin1("eval('qsTr(\\'Hello\\')')") << fileName << QString::fromLatin1("Hallo");
5320     // Plural
5321     QTest::newRow("qsTr('%n message(s) saved', '', 1)@translatable.js")
5322             << QString::fromLatin1("qsTr('%n message(s) saved', '', 1)") << fileName << QString::fromLatin1("1 melding lagret");
5323     QTest::newRow("qsTr('%n message(s) saved', '', 3).arg@translatable.js")
5324             << QString::fromLatin1("qsTr('%n message(s) saved', '', 3)") << fileName << QString::fromLatin1("3 meldinger lagret");
5325
5326     // Top-level
5327     QTest::newRow("qsTranslate('FooContext', 'Two')@translatable.js")
5328             << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName << QString::fromLatin1("To");
5329     QTest::newRow("qsTranslate('FooContext', 'Goodbye')@translatable.js")
5330             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye')") << fileName << QString::fromLatin1("Farvel");
5331     // From eval
5332     QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')@translatable.js")
5333             << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Two\\')')") << fileName << QString::fromLatin1("To");
5334     QTest::newRow("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')@translatable.js")
5335             << QString::fromLatin1("eval('qsTranslate(\\'FooContext\\', \\'Goodbye\\')')") << fileName << QString::fromLatin1("Farvel");
5336
5337     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8')@translatable.js")
5338             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8')") << fileName << QString::fromLatin1("Farvel");
5339     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'CodecForTr')@translatable.js")
5340             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'CodecForTr')") << fileName << QString::fromLatin1("Farvel");
5341
5342     QTest::newRow("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8', 42)@translatable.js")
5343             << QString::fromLatin1("qsTranslate('FooContext', 'Goodbye', '', 'UnicodeUTF8', 42)") << fileName << QString::fromLatin1("Goodbye");
5344
5345     QTest::newRow("qsTr('One', 'not the same one')@translatable.js")
5346             << QString::fromLatin1("qsTr('One', 'not the same one')") << fileName << QString::fromLatin1("Enda en");
5347
5348     QTest::newRow("qsTr('One', 'not the same one', 42)@translatable.js")
5349             << QString::fromLatin1("qsTr('One', 'not the same one', 42)") << fileName << QString::fromLatin1("One");
5350
5351     // Plural
5352     QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 1)@translatable.js")
5353             << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 1)") << fileName << QString::fromLatin1("1 fooaktig bar funnet");
5354     QTest::newRow("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 2)@translatable.js")
5355             << QString::fromLatin1("qsTranslate('FooContext', '%n fooish bar(s) found', '', 'UnicodeUTF8', 2)") << fileName << QString::fromLatin1("2 fooaktige barer funnet");
5356
5357     // Don't exist in translation
5358     QTest::newRow("qsTr('Three')@translatable.js")
5359             << QString::fromLatin1("qsTr('Three')") << fileName << QString::fromLatin1("Three");
5360     QTest::newRow("qsTranslate('FooContext', 'So long')@translatable.js")
5361             << QString::fromLatin1("qsTranslate('FooContext', 'So long')") << fileName << QString::fromLatin1("So long");
5362     QTest::newRow("qsTranslate('BarContext', 'Goodbye')@translatable.js")
5363             << QString::fromLatin1("qsTranslate('BarContext', 'Goodbye')") << fileName << QString::fromLatin1("Goodbye");
5364
5365     // Translate strings from the second script (translatable2.js)
5366
5367     QString fileName2 = QString::fromLatin1("translatable2.js");
5368     QTest::newRow("qsTr('Three')@translatable2.js")
5369             << QString::fromLatin1("qsTr('Three')") << fileName2 << QString::fromLatin1("Tre");
5370     QTest::newRow("qsTr('Happy birthday!')@translatable2.js")
5371             << QString::fromLatin1("qsTr('Happy birthday!')") << fileName2 << QString::fromLatin1("Gratulerer med dagen!");
5372
5373     // Not translated because translation is only in translatable.js
5374     QTest::newRow("qsTr('One')@translatable2.js")
5375             << QString::fromLatin1("qsTr('One')") << fileName2 << QString::fromLatin1("One");
5376     QTest::newRow("(function() { return qsTr('One'); })()@translatable2.js")
5377             << QString::fromLatin1("(function() { return qsTr('One'); })()") << fileName2 << QString::fromLatin1("One");
5378
5379     // For qsTranslate() the filename shouldn't matter
5380     QTest::newRow("qsTranslate('FooContext', 'Two')@translatable2.js")
5381             << QString::fromLatin1("qsTranslate('FooContext', 'Two')") << fileName2 << QString::fromLatin1("To");
5382     QTest::newRow("qsTranslate('BarContext', 'Congratulations!')@translatable.js")
5383             << QString::fromLatin1("qsTranslate('BarContext', 'Congratulations!')") << fileName << QString::fromLatin1("Gratulerer!");
5384 }
5385
5386 void tst_QJSEngine::translateScript()
5387 {
5388     QFETCH(QString, expression);
5389     QFETCH(QString, fileName);
5390     QFETCH(QString, expectedTranslation);
5391
5392     QScriptEngine engine;
5393
5394     TranslationScope tranScope(":/translations/translatable_la");
5395     engine.installTranslatorFunctions();
5396
5397     QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
5398     QVERIFY(!engine.hasUncaughtException());
5399 }
5400
5401 void tst_QJSEngine::translateScript_crossScript()
5402 {
5403     QScriptEngine engine;
5404     TranslationScope tranScope(":/translations/translatable_la");
5405     engine.installTranslatorFunctions();
5406
5407     QString fileName = QString::fromLatin1("translatable.js");
5408     QString fileName2 = QString::fromLatin1("translatable2.js");
5409     // qsTr() should use the innermost filename as context
5410     engine.evaluate("function foo(s) { return bar(s); }", fileName);
5411     engine.evaluate("function bar(s) { return qsTr(s); }", fileName2);
5412     QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
5413     QCOMPARE(engine.evaluate("bar('Three')", fileName).toString(), QString::fromLatin1("Tre"));
5414     QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("One"));
5415
5416     engine.evaluate("function foo(s) { return bar(s); }", fileName2);
5417     engine.evaluate("function bar(s) { return qsTr(s); }", fileName);
5418     QCOMPARE(engine.evaluate("bar('Three')", fileName2).toString(), QString::fromLatin1("Three"));
5419     QCOMPARE(engine.evaluate("bar('One')", fileName).toString(), QString::fromLatin1("En"));
5420     QCOMPARE(engine.evaluate("bar('One')", fileName2).toString(), QString::fromLatin1("En"));
5421 }
5422
5423 static QScriptValue callQsTr(QScriptContext *ctx, QScriptEngine *eng)
5424 {
5425     return eng->globalObject().property("qsTr").call(ctx->thisObject(), ctx->argumentsObject());
5426 }
5427
5428 void tst_QJSEngine::translateScript_callQsTrFromNative()
5429 {
5430     QScriptEngine engine;
5431     TranslationScope tranScope(":/translations/translatable_la");
5432     engine.installTranslatorFunctions();
5433
5434     QString fileName = QString::fromLatin1("translatable.js");
5435     QString fileName2 = QString::fromLatin1("translatable2.js");
5436     // Calling qsTr() from a native function
5437     engine.globalObject().setProperty("qsTrProxy", engine.newFunction(callQsTr));
5438     QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName).toString(), QString::fromLatin1("En"));
5439     QCOMPARE(engine.evaluate("qsTrProxy('One')", fileName2).toString(), QString::fromLatin1("One"));
5440     QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName).toString(), QString::fromLatin1("Three"));
5441     QCOMPARE(engine.evaluate("qsTrProxy('Three')", fileName2).toString(), QString::fromLatin1("Tre"));
5442 }
5443
5444 void tst_QJSEngine::translateScript_trNoOp()
5445 {
5446     QScriptEngine engine;
5447     TranslationScope tranScope(":/translations/translatable_la");
5448     engine.installTranslatorFunctions();
5449
5450     QVERIFY(engine.evaluate("QT_TR_NOOP()").isUndefined());
5451     QCOMPARE(engine.evaluate("QT_TR_NOOP('One')").toString(), QString::fromLatin1("One"));
5452
5453     QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP()").isUndefined());
5454     QVERIFY(engine.evaluate("QT_TRANSLATE_NOOP('FooContext')").isUndefined());
5455     QCOMPARE(engine.evaluate("QT_TRANSLATE_NOOP('FooContext', 'Two')").toString(), QString::fromLatin1("Two"));
5456 }
5457
5458 void tst_QJSEngine::translateScript_callQsTrFromCpp()
5459 {
5460     QScriptEngine engine;
5461     TranslationScope tranScope(":/translations/translatable_la");
5462     engine.installTranslatorFunctions();
5463
5464     // There is no context, but it shouldn't crash
5465     QCOMPARE(engine.globalObject().property("qsTr").call(
5466              QScriptValue(), QScriptValueList() << "One").toString(), QString::fromLatin1("One"));
5467 }
5468
5469 void tst_QJSEngine::translateWithInvalidArgs_data()
5470 {
5471     QTest::addColumn<QString>("expression");
5472     QTest::addColumn<QString>("expectedError");
5473
5474     QTest::newRow("qsTr()")  << "qsTr()" << "Error: qsTr() requires at least one argument";
5475     QTest::newRow("qsTr(123)")  << "qsTr(123)" << "Error: qsTr(): first argument (text) must be a string";
5476     QTest::newRow("qsTr('foo', 123)")  << "qsTr('foo', 123)" << "Error: qsTr(): second argument (comment) must be a string";
5477     QTest::newRow("qsTr('foo', 'bar', 'baz')")  << "qsTr('foo', 'bar', 'baz')" << "Error: qsTr(): third argument (n) must be a number";
5478     QTest::newRow("qsTr('foo', 'bar', true)")  << "qsTr('foo', 'bar', true)" << "Error: qsTr(): third argument (n) must be a number";
5479
5480     QTest::newRow("qsTranslate()")  << "qsTranslate()" << "Error: qsTranslate() requires at least two arguments";
5481     QTest::newRow("qsTranslate('foo')")  << "qsTranslate('foo')" << "Error: qsTranslate() requires at least two arguments";
5482     QTest::newRow("qsTranslate(123, 'foo')")  << "qsTranslate(123, 'foo')" << "Error: qsTranslate(): first argument (context) must be a string";
5483     QTest::newRow("qsTranslate('foo', 123)")  << "qsTranslate('foo', 123)" << "Error: qsTranslate(): second argument (text) must be a string";
5484     QTest::newRow("qsTranslate('foo', 'bar', 123)")  << "qsTranslate('foo', 'bar', 123)" << "Error: qsTranslate(): third argument (comment) must be a string";
5485     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 123)")  << "qsTranslate('foo', 'bar', 'baz', 123)" << "Error: qsTranslate(): fourth argument (encoding) must be a string";
5486     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')")  << "qsTranslate('foo', 'bar', 'baz', 'zab', 'rab')" << "Error: qsTranslate(): fifth argument (n) must be a number";
5487     QTest::newRow("qsTranslate('foo', 'bar', 'baz', 'zab', 123)")  << "qsTranslate('foo', 'bar', 'baz', 'zab', 123)" << "Error: qsTranslate(): invalid encoding 'zab'";
5488
5489     QTest::newRow("qsTrId()")  << "qsTrId()" << "Error: qsTrId() requires at least one argument";
5490     QTest::newRow("qsTrId(123)")  << "qsTrId(123)" << "TypeError: qsTrId(): first argument (id) must be a string";
5491     QTest::newRow("qsTrId('foo', 'bar')")  << "qsTrId('foo', 'bar')" << "TypeError: qsTrId(): second argument (n) must be a number";
5492 }
5493
5494 void tst_QJSEngine::translateWithInvalidArgs()
5495 {
5496     QFETCH(QString, expression);
5497     QFETCH(QString, expectedError);
5498     QScriptEngine engine;
5499     engine.installTranslatorFunctions();
5500     QScriptValue result = engine.evaluate(expression);
5501     QVERIFY(result.isError());
5502     QCOMPARE(result.toString(), expectedError);
5503 }
5504
5505 void tst_QJSEngine::translationContext_data()
5506 {
5507     QTest::addColumn<QString>("path");
5508     QTest::addColumn<QString>("text");
5509     QTest::addColumn<QString>("expectedTranslation");
5510
5511     QTest::newRow("translatable.js")  << "translatable.js" << "One" << "En";
5512     QTest::newRow("/translatable.js")  << "/translatable.js" << "One" << "En";
5513     QTest::newRow("/foo/translatable.js")  << "/foo/translatable.js" << "One" << "En";
5514     QTest::newRow("/foo/bar/translatable.js")  << "/foo/bar/translatable.js" << "One" << "En";
5515     QTest::newRow("./translatable.js")  << "./translatable.js" << "One" << "En";
5516     QTest::newRow("../translatable.js")  << "../translatable.js" << "One" << "En";
5517     QTest::newRow("foo/translatable.js")  << "foo/translatable.js" << "One" << "En";
5518     QTest::newRow("file:///home/qt/translatable.js")  << "file:///home/qt/translatable.js" << "One" << "En";
5519     QTest::newRow(":/resources/translatable.js")  << ":/resources/translatable.js" << "One" << "En";
5520     QTest::newRow("/translatable.js.foo")  << "/translatable.js.foo" << "One" << "En";
5521     QTest::newRow("/translatable.txt")  << "/translatable.txt" << "One" << "En";
5522     QTest::newRow("translatable")  << "translatable" << "One" << "En";
5523     QTest::newRow("foo/translatable")  << "foo/translatable" << "One" << "En";
5524
5525     QTest::newRow("native separators")
5526         << (QDir::toNativeSeparators(QDir::currentPath()) + QDir::separator() + "translatable.js")
5527         << "One" << "En";
5528
5529     QTest::newRow("translatable.js/")  << "translatable.js/" << "One" << "One";
5530     QTest::newRow("nosuchscript.js")  << "" << "One" << "One";
5531     QTest::newRow("(empty)")  << "" << "One" << "One";
5532 }
5533
5534 void tst_QJSEngine::translationContext()
5535 {
5536     TranslationScope tranScope(":/translations/translatable_la");
5537
5538     QScriptEngine engine;
5539     engine.installTranslatorFunctions();
5540
5541     QFETCH(QString, path);
5542     QFETCH(QString, text);
5543     QFETCH(QString, expectedTranslation);
5544     QScriptValue ret = engine.evaluate(QString::fromLatin1("qsTr('%0')").arg(text), path);
5545     QVERIFY(ret.isString());
5546     QCOMPARE(ret.toString(), expectedTranslation);
5547 }
5548
5549 void tst_QJSEngine::translateScriptIdBased()
5550 {
5551     QScriptEngine engine;
5552
5553     TranslationScope tranScope(":/translations/idtranslatable_la");
5554     engine.installTranslatorFunctions();
5555
5556     QString fileName = QString::fromLatin1("idtranslatable.js");
5557
5558     QHash<QString, QString> expectedTranslations;
5559     expectedTranslations["qtn_foo_bar"] = "First string";
5560     expectedTranslations["qtn_needle"] = "Second string";
5561     expectedTranslations["qtn_haystack"] = "Third string";
5562     expectedTranslations["qtn_bar_baz"] = "Fourth string";
5563
5564     QHash<QString, QString>::const_iterator it;
5565     for (it = expectedTranslations.constBegin(); it != expectedTranslations.constEnd(); ++it) {
5566         for (int x = 0; x < 2; ++x) {
5567             QString fn;
5568             if (x)
5569                 fn = fileName;
5570             // Top-level
5571             QCOMPARE(engine.evaluate(QString::fromLatin1("qsTrId('%0')")
5572                                      .arg(it.key()), fn).toString(),
5573                      it.value());
5574             QCOMPARE(engine.evaluate(QString::fromLatin1("QT_TRID_NOOP('%0')")
5575                                      .arg(it.key()), fn).toString(),
5576                      it.key());
5577             // From function
5578             QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return qsTrId('%0'); })()")
5579                                      .arg(it.key()), fn).toString(),
5580                      it.value());
5581             QCOMPARE(engine.evaluate(QString::fromLatin1("(function() { return QT_TRID_NOOP('%0'); })()")
5582                                      .arg(it.key()), fn).toString(),
5583                      it.key());
5584         }
5585     }
5586
5587     // Plural form
5588     QCOMPARE(engine.evaluate("qsTrId('qtn_bar_baz', 10)").toString(),
5589              QString::fromLatin1("10 fooish bar(s) found"));
5590     QCOMPARE(engine.evaluate("qsTrId('qtn_foo_bar', 10)").toString(),
5591              QString::fromLatin1("qtn_foo_bar")); // Doesn't have plural
5592 }
5593
5594 // How to add a new test row:
5595 // - Find a nice list of Unicode characters to choose from
5596 // - Write source string/context/comment in .js using Unicode escape sequences (\uABCD)
5597 // - Update corresponding .ts file (e.g. lupdate foo.js -ts foo.ts -codecfortr UTF-8)
5598 // - Enter translation in Linguist
5599 // - Update corresponding .qm file (e.g. lrelease foo.ts)
5600 // - Evaluate script that performs translation; make sure the correct result is returned
5601 //   (e.g. by setting the resulting string as the text of a QLabel and visually verifying
5602 //   that it looks the same as what you entered in Linguist :-) )
5603 // - Generate the expectedTranslation column data using toUtf8().toHex()
5604 void tst_QJSEngine::translateScriptUnicode_data()
5605 {
5606     QTest::addColumn<QString>("expression");
5607     QTest::addColumn<QString>("fileName");
5608     QTest::addColumn<QString>("expectedTranslation");
5609
5610     QString fileName = QString::fromLatin1("translatable-unicode.js");
5611     QTest::newRow("qsTr('H\\u2082O')@translatable-unicode.js")
5612             << QString::fromLatin1("qsTr('H\\u2082O')") << fileName << QString::fromUtf8("\xcd\xbb\xcd\xbc\xcd\xbd");
5613     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')@translatable-unicode.js")
5614             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << fileName << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
5615     QTest::newRow("qsTr('\\u0391\\u0392\\u0393')@translatable-unicode.js")
5616             << QString::fromLatin1("qsTr('\\u0391\\u0392\\u0393')") << fileName << QString::fromUtf8("\xd3\x9c\xd2\xb4\xd1\xbc");
5617     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')@translatable-unicode.js")
5618             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', '\\u0414\\u0415\\u0416')") << fileName << QString::fromUtf8("\xd8\xae\xd8\xb3\xd8\xb3");
5619     QTest::newRow("qsTr('H\\u2082O', 'not the same H\\u2082O')@translatable-unicode.js")
5620             << QString::fromLatin1("qsTr('H\\u2082O', 'not the same H\\u2082O')") << fileName << QString::fromUtf8("\xd4\xb6\xd5\x8a\xd5\x92");
5621     QTest::newRow("qsTr('H\\u2082O')")
5622             << QString::fromLatin1("qsTr('H\\u2082O')") << QString() << QString::fromUtf8("\x48\xe2\x82\x82\x4f");
5623     QTest::newRow("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')")
5624             << QString::fromLatin1("qsTranslate('\\u010C\\u0101\\u011F\\u0115', 'CO\\u2082')") << QString() << QString::fromUtf8("\xd7\x91\xd7\x9a\xd7\xa2");
5625 }
5626
5627 void tst_QJSEngine::translateScriptUnicode()
5628 {
5629     QFETCH(QString, expression);
5630     QFETCH(QString, fileName);
5631     QFETCH(QString, expectedTranslation);
5632
5633     QScriptEngine engine;
5634
5635     TranslationScope tranScope(":/translations/translatable-unicode");
5636     engine.installTranslatorFunctions();
5637
5638     QCOMPARE(engine.evaluate(expression, fileName).toString(), expectedTranslation);
5639     QVERIFY(!engine.hasUncaughtException());
5640 }
5641
5642 void tst_QJSEngine::translateScriptUnicodeIdBased_data()
5643 {
5644     QTest::addColumn<QString>("expression");
5645     QTest::addColumn<QString>("expectedTranslation");
5646
5647     QTest::newRow("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1'')")
5648             << QString::fromLatin1("qsTrId('\\u01F8\\u01D2\\u0199\\u01D0\\u01E1')") << QString::fromUtf8("\xc6\xa7\xc6\xb0\xc6\x88\xc8\xbc\xc8\x9d\xc8\xbf\xc8\x99");
5649     QTest::newRow("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')")
5650             << QString::fromLatin1("qsTrId('\\u0191\\u01CE\\u0211\\u0229\\u019C\\u018E\\u019A\\u01D0')") << QString::fromUtf8("\xc7\xa0\xc8\xa1\xc8\x8b\xc8\x85\xc8\x95");
5651     QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)")
5652             << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C', 10)") << QString::fromUtf8("\x31\x30\x20\xc6\x92\xc6\xa1\xc7\x92\x28\xc8\x99\x29");
5653     QTest::newRow("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')")
5654             << QString::fromLatin1("qsTrId('\\u0181\\u01A1\\u0213\\u018F\\u018C')") << QString::fromUtf8("\xc6\x91\xc6\xb0\xc7\xb9");
5655     QTest::newRow("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')")
5656             << QString::fromLatin1("qsTrId('\\u01CD\\u0180\\u01A8\\u0190\\u019E\\u01AB')") << QString::fromUtf8("\xc7\x8d\xc6\x80\xc6\xa8\xc6\x90\xc6\x9e\xc6\xab");
5657 }
5658
5659 void tst_QJSEngine::translateScriptUnicodeIdBased()
5660 {
5661     QFETCH(QString, expression);
5662     QFETCH(QString, expectedTranslation);
5663
5664     QScriptEngine engine;
5665
5666     TranslationScope tranScope(":/translations/idtranslatable-unicode");
5667     engine.installTranslatorFunctions();
5668
5669     QCOMPARE(engine.evaluate(expression).toString(), expectedTranslation);
5670     QVERIFY(!engine.hasUncaughtException());
5671 }
5672
5673 void tst_QJSEngine::translateFromBuiltinCallback()
5674 {
5675     QScriptEngine eng;
5676     eng.installTranslatorFunctions();
5677
5678     // Callback has no translation context.
5679     eng.evaluate("function foo() { qsTr('foo'); }");
5680
5681     // Stack at translation time will be:
5682     // qsTr, foo, forEach, global
5683     // qsTr() needs to walk to the outer-most (global) frame before it finds
5684     // a translation context, and this should not crash.
5685     eng.evaluate("[10,20].forEach(foo)", "script.js");
5686 }
5687 #endif
5688
5689 #if 0 // ###FIXME: No QScriptValue::scope API
5690 void tst_QJSEngine::functionScopes()
5691 {
5692     QScriptEngine eng;
5693     {
5694         // top-level functions have only the global object in their scope
5695         QScriptValue fun = eng.evaluate("(function() {})");
5696         QVERIFY(fun.isFunction());
5697         QEXPECT_FAIL("", "QScriptValue::scope() is internal, not implemented", Abort);
5698         QVERIFY(fun.scope().isObject());
5699         QVERIFY(fun.scope().strictlyEquals(eng.globalObject()));
5700         QVERIFY(!eng.globalObject().scope().isValid());
5701     }
5702     {
5703         QScriptValue fun = eng.globalObject().property("Object");
5704         QVERIFY(fun.isFunction());
5705         // native built-in functions don't have scope
5706         QVERIFY(!fun.scope().isValid());
5707     }
5708     {
5709         // closure
5710         QScriptValue fun = eng.evaluate("(function(arg) { var foo = arg; return function() { return foo; }; })(123)");
5711         QVERIFY(fun.isFunction());
5712         {
5713             QScriptValue ret = fun.call();
5714             QVERIFY(ret.isNumber());
5715             QCOMPARE(ret.toInt32(), 123);
5716         }
5717         QScriptValue scope = fun.scope();
5718         QVERIFY(scope.isObject());
5719         {
5720             QScriptValue ret = scope.property("foo");
5721             QVERIFY(ret.isNumber());
5722             QCOMPARE(ret.toInt32(), 123);
5723             QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
5724         }
5725         {
5726             QScriptValue ret = scope.property("arg");
5727             QVERIFY(ret.isNumber());
5728             QCOMPARE(ret.toInt32(), 123);
5729             QCOMPARE(scope.propertyFlags("arg"), QScriptValue::Undeletable | QScriptValue::SkipInEnumeration);
5730         }
5731
5732         scope.setProperty("foo", 456);
5733         {
5734             QScriptValue ret = fun.call();
5735             QVERIFY(ret.isNumber());
5736             QCOMPARE(ret.toInt32(), 456);
5737         }
5738
5739         scope = scope.scope();
5740         QVERIFY(scope.isObject());
5741         QVERIFY(scope.strictlyEquals(eng.globalObject()));
5742     }
5743 }
5744 #endif
5745
5746 #if 0 // ###FIXME: No QScriptContext API
5747 static QScriptValue counter_inner(QScriptContext *ctx, QScriptEngine *)
5748 {
5749      QScriptValue outerAct = ctx->callee().scope();
5750      double count = outerAct.property("count").toNumber();
5751      outerAct.setProperty("count", count+1);
5752      return count;
5753 }
5754
5755 static QScriptValue counter(QScriptContext *ctx, QScriptEngine *eng)
5756 {
5757      QScriptValue act = ctx->activationObject();
5758      act.setProperty("count", ctx->argument(0).toInt32());
5759      QScriptValue result = eng->newFunction(counter_inner);
5760      result.setScope(act);
5761      return result;
5762 }
5763
5764 static QScriptValue counter_hybrid(QScriptContext *ctx, QScriptEngine *eng)
5765 {
5766      QScriptValue act = ctx->activationObject();
5767      act.setProperty("count", ctx->argument(0).toInt32());
5768      return eng->evaluate("(function() { return count++; })");
5769 }
5770
5771 void tst_QJSEngine::nativeFunctionScopes()
5772 {
5773     QScriptEngine eng;
5774     {
5775         QScriptValue fun = eng.newFunction(counter);
5776         QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
5777         QVERIFY(cnt.isFunction());
5778         {
5779             QScriptValue ret = cnt.call();
5780             QVERIFY(ret.isNumber());
5781             QEXPECT_FAIL("", "QScriptValue::setScope not implemented", Continue);
5782             QCOMPARE(ret.toInt32(), 123);
5783         }
5784     }
5785     {
5786         QScriptValue fun = eng.newFunction(counter_hybrid);
5787         QScriptValue cnt = fun.call(QScriptValue(), QScriptValueList() << 123);
5788         QVERIFY(cnt.isFunction());
5789         {
5790             QScriptValue ret = cnt.call();
5791             QVERIFY(ret.isNumber());
5792             QCOMPARE(ret.toInt32(), 123);
5793         }
5794     }
5795
5796     //from http://doc.trolltech.com/latest/qtscript.html#nested-functions-and-the-scope-chain
5797     {
5798         QScriptEngine eng;
5799         eng.evaluate("function counter() { var count = 0; return function() { return count++; } }\n"
5800                      "var c1 = counter();  var c2 = counter(); ");
5801         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5802         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5803         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5804         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5805         QVERIFY(!eng.hasUncaughtException());
5806     }
5807     {
5808         QScriptEngine eng;
5809         eng.globalObject().setProperty("counter", eng.newFunction(counter));
5810         eng.evaluate("var c1 = counter();  var c2 = counter(); ");
5811         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5812         QEXPECT_FAIL("", "QScriptValue::setScope not implemented", Continue);
5813         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5814         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5815         QEXPECT_FAIL("", "QScriptValue::setScope not implemented", Continue);
5816         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5817         QVERIFY(!eng.hasUncaughtException());
5818     }
5819     {
5820         QScriptEngine eng;
5821         eng.globalObject().setProperty("counter", eng.newFunction(counter_hybrid));
5822         eng.evaluate("var c1 = counter();  var c2 = counter(); ");
5823         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("0"));
5824         QCOMPARE(eng.evaluate("c1()").toString(), QString::fromLatin1("1"));
5825         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("0"));
5826         QCOMPARE(eng.evaluate("c2()").toString(), QString::fromLatin1("1"));
5827         QVERIFY(!eng.hasUncaughtException());
5828     }
5829 }
5830 #endif
5831
5832 #if 0 // ###FIXME: No QScriptProgram API
5833 static QScriptValue createProgram(QScriptContext *ctx, QScriptEngine *eng)
5834 {
5835     QString code = ctx->argument(0).toString();
5836     QScriptProgram result(code);
5837     return qScriptValueFromValue(eng, result);
5838 }
5839
5840 void tst_QJSEngine::evaluateProgram()
5841 {
5842     QScriptEngine eng;
5843
5844     {
5845         QString code("1 + 2");
5846         QString fileName("hello.js");
5847         int lineNumber(123);
5848         QScriptProgram program(code, fileName, lineNumber);
5849         QVERIFY(!program.isNull());
5850         QCOMPARE(program.sourceCode(), code);
5851         QCOMPARE(program.fileName(), fileName);
5852         QCOMPARE(program.firstLineNumber(), lineNumber);
5853
5854         QScriptValue expected = eng.evaluate(code);
5855         for (int x = 0; x < 10; ++x) {
5856             QScriptValue ret = eng.evaluate(program);
5857             QVERIFY(ret.equals(expected));
5858         }
5859
5860         // operator=
5861         QScriptProgram sameProgram = program;
5862         QVERIFY(sameProgram == program);
5863         QVERIFY(eng.evaluate(sameProgram).equals(expected));
5864
5865         // copy constructor
5866         QScriptProgram sameProgram2(program);
5867         QVERIFY(sameProgram2 == program);
5868         QVERIFY(eng.evaluate(sameProgram2).equals(expected));
5869
5870         QScriptProgram differentProgram("2 + 3");
5871         QVERIFY(differentProgram != program);
5872         QVERIFY(!eng.evaluate(differentProgram).equals(expected));
5873     }
5874 }
5875
5876 void tst_QJSEngine::evaluateProgram_customScope()
5877 {
5878     QScriptEngine eng;
5879     {
5880         QScriptProgram program("a");
5881         QVERIFY(!program.isNull());
5882         {
5883             QScriptValue ret = eng.evaluate(program);
5884             QVERIFY(ret.isError());
5885             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: a is not defined"));
5886         }
5887
5888         QScriptValue obj = eng.newObject();
5889         obj.setProperty("a", 123);
5890         QScriptContext *ctx = eng.currentContext();
5891         ctx->pushScope(obj);
5892         {
5893             QScriptValue ret = eng.evaluate(program);
5894             QVERIFY(!ret.isError());
5895             QVERIFY(ret.equals(obj.property("a")));
5896         }
5897
5898         obj.setProperty("a", QScriptValue());
5899         {
5900             QScriptValue ret = eng.evaluate(program);
5901             QVERIFY(ret.isError());
5902         }
5903
5904         QScriptValue obj2 = eng.newObject();
5905         obj2.setProperty("a", 456);
5906         ctx->pushScope(obj2);
5907         {
5908             QScriptValue ret = eng.evaluate(program);
5909             QVERIFY(!ret.isError());
5910             QVERIFY(ret.equals(obj2.property("a")));
5911         }
5912
5913         ctx->popScope();
5914     }
5915 }
5916
5917 void tst_QJSEngine::evaluateProgram_closure()
5918 {
5919     QScriptEngine eng;
5920     {
5921         QScriptProgram program("(function() { var count = 0; return function() { return count++; }; })");
5922         QVERIFY(!program.isNull());
5923         QScriptValue createCounter = eng.evaluate(program);
5924         QVERIFY(createCounter.isFunction());
5925         QScriptValue counter = createCounter.call();
5926         QVERIFY(counter.isFunction());
5927         {
5928             QScriptValue ret = counter.call();
5929             QVERIFY(ret.isNumber());
5930         }
5931         QScriptValue counter2 = createCounter.call();
5932         QVERIFY(counter2.isFunction());
5933         QVERIFY(!counter2.equals(counter));
5934         {
5935             QScriptValue ret = counter2.call();
5936             QVERIFY(ret.isNumber());
5937         }
5938     }
5939 }
5940
5941 void tst_QJSEngine::evaluateProgram_executeLater()
5942 {
5943     QScriptEngine eng;
5944     // Program created in a function call, then executed later
5945     {
5946         QScriptValue fun = eng.newFunction(createProgram);
5947         QScriptProgram program = qscriptvalue_cast<QScriptProgram>(
5948             fun.call(QScriptValue(), QScriptValueList() << "a + 1"));
5949         QVERIFY(!program.isNull());
5950         eng.globalObject().setProperty("a", QScriptValue());
5951         {
5952             QScriptValue ret = eng.evaluate(program);
5953             QVERIFY(ret.isError());
5954             QCOMPARE(ret.toString(), QString::fromLatin1("ReferenceError: a is not defined"));
5955         }
5956         eng.globalObject().setProperty("a", 122);
5957         {
5958             QScriptValue ret = eng.evaluate(program);
5959             QVERIFY(!ret.isError());
5960             QVERIFY(ret.isNumber());
5961             QCOMPARE(ret.toInt32(), 123);
5962         }
5963     }
5964 }
5965
5966 void tst_QJSEngine::evaluateProgram_multipleEngines()
5967 {
5968     QScriptEngine eng;
5969     {
5970         QString code("1 + 2");
5971         QScriptProgram program(code);
5972         QVERIFY(!program.isNull());
5973         double expected = eng.evaluate(program).toNumber();
5974         for (int x = 0; x < 2; ++x) {
5975             QScriptEngine eng2;
5976             for (int y = 0; y < 2; ++y) {
5977                 double ret = eng2.evaluate(program).toNumber();
5978                 QCOMPARE(ret, expected);
5979             }
5980         }
5981     }
5982 }
5983
5984 void tst_QJSEngine::evaluateProgram_empty()
5985 {
5986     QScriptEngine eng;
5987     {
5988         QScriptProgram program;
5989         QVERIFY(program.isNull());
5990         QScriptValue ret = eng.evaluate(program);
5991         QVERIFY(!ret.isValid());
5992     }
5993 }
5994 #endif
5995
5996 #if 0 // ###FIXME: No ScriptOwnership API
5997 void tst_QJSEngine::collectGarbageAfterConnect()
5998 {
5999     // QTBUG-6366
6000     QScriptEngine engine;
6001     QPointer<QWidget> widget = new QWidget;
6002     engine.globalObject().setProperty(
6003         "widget", engine.newQObject(widget, QScriptEngine::ScriptOwnership));
6004     QVERIFY(engine.evaluate("widget.customContextMenuRequested.connect(\n"
6005                             "  function() { print('hello'); }\n"
6006                             ");")
6007             .isUndefined());
6008     QVERIFY(widget != 0);
6009     engine.evaluate("widget = null;");
6010     // The connection should not keep the widget alive.
6011     collectGarbage_helper(engine);
6012     QVERIFY(widget == 0);
6013 }
6014 #endif
6015
6016 #if 0 // ###FIXME: No QScriptContext API
6017 void tst_QJSEngine::collectGarbageAfterNativeArguments()
6018 {
6019     // QTBUG-17788
6020     QScriptEngine eng;
6021     QScriptContext *ctx = eng.pushContext();
6022     QScriptValue arguments = ctx->argumentsObject();
6023     // Shouldn't crash when marking the arguments object.
6024     collectGarbage_helper(eng);
6025 }
6026
6027 static QScriptValue constructQObjectFromThisObject(QScriptContext *ctx, QScriptEngine *eng)
6028 {
6029     if (!ctx->isCalledAsConstructor()) {
6030         qWarning("%s: ctx->isCalledAsConstructor() returned false", Q_FUNC_INFO);
6031         return QScriptValue();
6032     }
6033     return eng->newQObject(ctx->thisObject(), new QObject, QScriptEngine::ScriptOwnership);
6034 }
6035
6036 void tst_QJSEngine::promoteThisObjectToQObjectInConstructor()
6037 {
6038     QScriptEngine engine;
6039     QScriptValue ctor = engine.newFunction(constructQObjectFromThisObject);
6040     engine.globalObject().setProperty("Ctor", ctor);
6041     QScriptValue object = engine.evaluate("new Ctor");
6042     QVERIFY(!object.isError());
6043     QVERIFY(object.isQObject());
6044     QVERIFY(object.toQObject() != 0);
6045     QVERIFY(object.property("objectName").isString());
6046     QVERIFY(object.property("deleteLater").isFunction());
6047 }
6048 #endif
6049
6050 static QRegExp minimal(QRegExp r) { r.setMinimal(true); return r; }
6051
6052 void tst_QJSEngine::qRegExpInport_data()
6053 {
6054     QTest::addColumn<QRegExp>("rx");
6055     QTest::addColumn<QString>("string");
6056     QTest::addColumn<QString>("matched");
6057
6058     QTest::newRow("normal")  << QRegExp("(test|foo)") << "test _ foo _ test _ Foo";
6059     QTest::newRow("normal2")  << QRegExp("(Test|Foo)") << "test _ foo _ test _ Foo";
6060     QTest::newRow("case insensitive)")  << QRegExp("(test|foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
6061     QTest::newRow("case insensitive2)")  << QRegExp("(Test|Foo)", Qt::CaseInsensitive) << "test _ foo _ test _ Foo";
6062     QTest::newRow("b(a*)(b*)")  << QRegExp("b(a*)(b*)", Qt::CaseInsensitive) << "aaabbBbaAabaAaababaaabbaaab";
6063     QTest::newRow("greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp2) << "aaaabaaba";
6064     // this one will fail because we do not support the QRegExp::RegExp in JSC
6065     //QTest::newRow("not_greedy")  << QRegExp("a*(a*)", Qt::CaseInsensitive, QRegExp::RegExp) << "aaaabaaba";
6066     QTest::newRow("willcard")  << QRegExp("*.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "file.txt";
6067     QTest::newRow("willcard 2")  << QRegExp("a?b.txt", Qt::CaseSensitive, QRegExp::Wildcard) << "ab.txt abb.rtc acb.txt";
6068     QTest::newRow("slash")  << QRegExp("g/.*/s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string/string/string";
6069     QTest::newRow("slash2")  << QRegExp("g / .* / s", Qt::CaseInsensitive, QRegExp::RegExp2) << "string / string / string";
6070     QTest::newRow("fixed")  << QRegExp("a*aa.a(ba)*a\\ba", Qt::CaseInsensitive, QRegExp::FixedString) << "aa*aa.a(ba)*a\\ba";
6071     QTest::newRow("fixed insensitive")  << QRegExp("A*A", Qt::CaseInsensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
6072     QTest::newRow("fixed sensitive")  << QRegExp("A*A", Qt::CaseSensitive, QRegExp::FixedString) << "a*A A*a A*A a*a";
6073     QTest::newRow("html")  << QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2) << "<b>bold</b><i>italic</i><b>bold</b>";
6074     QTest::newRow("html minimal")  << minimal(QRegExp("<b>(.*)</b>", Qt::CaseSensitive, QRegExp::RegExp2)) << "<b>bold</b><i>italic</i><b>bold</b>";
6075     QTest::newRow("aaa")  << QRegExp("a{2,5}") << "aAaAaaaaaAa";
6076     QTest::newRow("aaa minimal")  << minimal(QRegExp("a{2,5}")) << "aAaAaaaaaAa";
6077     QTest::newRow("minimal")  << minimal(QRegExp(".*\\} [*8]")) << "}?} ?} *";
6078     QTest::newRow(".? minimal")  << minimal(QRegExp(".?")) << ".?";
6079     QTest::newRow(".+ minimal")  << minimal(QRegExp(".+")) << ".+";
6080     QTest::newRow("[.?] minimal")  << minimal(QRegExp("[.?]")) << ".?";
6081     QTest::newRow("[.+] minimal")  << minimal(QRegExp("[.+]")) << ".+";
6082 }
6083
6084 void tst_QJSEngine::qRegExpInport()
6085 {
6086     QSKIP("Test failing - QTBUG-22238", SkipAll);
6087     QFETCH(QRegExp, rx);
6088     QFETCH(QString, string);
6089
6090     QJSEngine eng;
6091     QJSValue rexp;
6092     rexp = eng.newRegExp(rx);
6093
6094     QCOMPARE(rexp.isValid(), true);
6095     QCOMPARE(rexp.isRegExp(), true);
6096     QVERIFY(rexp.isFunction());
6097
6098     QJSValue func = eng.evaluate("(function(string, regexp) { return string.match(regexp); })");
6099     QJSValue result = func.call(QJSValue(),  QJSValueList() << string << rexp);
6100
6101     rx.indexIn(string);
6102     for (int i = 0; i <= rx.captureCount(); i++)  {
6103         QCOMPARE(result.property(i).toString(), rx.cap(i));
6104     }
6105 }
6106
6107 // QScriptValue::toDateTime() returns a local time, whereas JS dates
6108 // are always stored as UTC. QtScript must respect the current time
6109 // zone, and correctly adjust for daylight saving time that may be in
6110 // effect at a given date (QTBUG-9770).
6111 void tst_QJSEngine::dateRoundtripJSQtJS()
6112 {
6113     uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
6114     QJSEngine eng;
6115     for (int i = 0; i < 8000; ++i) {
6116         QJSValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0));
6117         QDateTime qtDate = jsDate.toDateTime();
6118         QJSValue jsDate2 = eng.newDate(qtDate);
6119         if (jsDate2.toNumber() != jsDate.toNumber())
6120             QFAIL(qPrintable(jsDate.toString()));
6121         secs += 2*60*60;
6122     }
6123 }
6124
6125 void tst_QJSEngine::dateRoundtripQtJSQt()
6126 {
6127     QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
6128     QJSEngine eng;
6129     for (int i = 0; i < 8000; ++i) {
6130         QJSValue jsDate = eng.newDate(qtDate);
6131         QDateTime qtDate2 = jsDate.toDateTime();
6132         if (qtDate2 != qtDate)
6133             QFAIL(qPrintable(qtDate.toString()));
6134         qtDate = qtDate.addSecs(2*60*60);
6135     }
6136 }
6137
6138 void tst_QJSEngine::dateConversionJSQt()
6139 {
6140     uint secs = QDateTime(QDate(2009, 1, 1)).toUTC().toTime_t();
6141     QJSEngine eng;
6142     for (int i = 0; i < 8000; ++i) {
6143         QJSValue jsDate = eng.evaluate(QString::fromLatin1("new Date(%0)").arg(secs * 1000.0));
6144         QDateTime qtDate = jsDate.toDateTime();
6145         QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
6146         QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
6147         jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000")
6148         if (qtUTCDateStr != jsUTCDateStr)
6149             QFAIL(qPrintable(jsDate.toString()));
6150         secs += 2*60*60;
6151     }
6152 }
6153
6154 void tst_QJSEngine::dateConversionQtJS()
6155 {
6156     QDateTime qtDate = QDateTime(QDate(2009, 1, 1));
6157     QJSEngine eng;
6158     for (int i = 0; i < 8000; ++i) {
6159         QJSValue jsDate = eng.newDate(qtDate);
6160         QString jsUTCDateStr = jsDate.property("toISOString").call(jsDate).toString();
6161         jsUTCDateStr.remove(jsUTCDateStr.length() - 5, 4); // get rid of milliseconds (".000")
6162         QString qtUTCDateStr = qtDate.toUTC().toString(Qt::ISODate);
6163         if (jsUTCDateStr != qtUTCDateStr)
6164             QFAIL(qPrintable(qtDate.toString()));
6165         qtDate = qtDate.addSecs(2*60*60);
6166     }
6167 }
6168
6169 #if 0 // ###FIXME: No QScriptContext API
6170 static QScriptValue createAnotherEngine(QScriptContext *, QScriptEngine *)
6171 {
6172     QScriptEngine eng;
6173     eng.evaluate("function foo(x, y) { return x + y; }" );
6174     eng.evaluate("hello = 5; world = 6" );
6175     return eng.evaluate("foo(hello,world)").toInt32();
6176 }
6177
6178
6179 void tst_QJSEngine::reentrency()
6180 {
6181     QScriptEngine eng;
6182     eng.globalObject().setProperty("foo", eng.newFunction(createAnotherEngine));
6183     eng.evaluate("function bar() { return foo(); }  hello = 9; function getHello() { return hello; }");
6184     QCOMPARE(eng.evaluate("foo() + getHello() + foo()").toInt32(), 5+6 + 9 + 5+6);
6185     QCOMPARE(eng.evaluate("foo").call().toInt32(), 5+6);
6186     QCOMPARE(eng.evaluate("hello").toInt32(), 9);
6187     QCOMPARE(eng.evaluate("foo() + hello").toInt32(), 5+6+9);
6188 }
6189 #endif
6190
6191 #if 0 // ###FIXME: No QSCriptDeclarativeClass API
6192 void tst_QJSEngine::newFixedStaticScopeObject()
6193 {
6194     // "Static scope objects" is an optimization we do for QML.
6195     // It enables the creation of JS objects that can guarantee to the
6196     // compiler that no properties will be added or removed. This enables
6197     // the compiler to generate a very simple (fast) property access, as
6198     // opposed to a full virtual lookup. Due to the inherent use of scope
6199     // chains in QML, this can make a huge difference (10x improvement for
6200     // benchmark in QTBUG-8576).
6201     // Ideally we would not need a special object type for this, and the
6202     // VM would dynamically optimize it to be fast...
6203     // See also QScriptEngine benchmark.
6204
6205     QScriptEngine eng;
6206     static const int propertyCount = 4;
6207     QString names[] = { "foo", "bar", "baz", "Math" };
6208     QScriptValue values[] = { 123, "ciao", true, false };
6209     QScriptValue::PropertyFlags flags[] = { QScriptValue::Undeletable,
6210                                             QScriptValue::ReadOnly | QScriptValue::Undeletable,
6211                                             QScriptValue::SkipInEnumeration | QScriptValue::Undeletable,
6212                                             QScriptValue::Undeletable };
6213     QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount, names, values, flags);
6214
6215     // Query property.
6216     for (int i = 0; i < propertyCount; ++i) {
6217         for (int x = 0; x < 2; ++x) {
6218             if (x) {
6219                 // Properties can't be deleted.
6220                 scope.setProperty(names[i], QScriptValue());
6221             }
6222             QVERIFY(scope.property(names[i]).equals(values[i]));
6223             QCOMPARE(scope.propertyFlags(names[i]), flags[i]);
6224         }
6225     }
6226
6227     // Property that doesn't exist.
6228     QVERIFY(!scope.property("noSuchProperty").isValid());
6229     QCOMPARE(scope.propertyFlags("noSuchProperty"), QScriptValue::PropertyFlags());
6230
6231     // Write to writable property.
6232     {
6233         QScriptValue oldValue = scope.property("foo");
6234         QVERIFY(oldValue.isNumber());
6235         QScriptValue newValue = oldValue.toNumber() * 2;
6236         scope.setProperty("foo", newValue);
6237         QVERIFY(scope.property("foo").equals(newValue));
6238         scope.setProperty("foo", oldValue);
6239         QVERIFY(scope.property("foo").equals(oldValue));
6240     }
6241
6242     // Write to read-only property.
6243     scope.setProperty("bar", 456);
6244     QVERIFY(scope.property("bar").equals("ciao"));
6245
6246     // Iterate.
6247     {
6248         QScriptValueIterator it(scope);
6249         QSet<QString> iteratedNames;
6250         while (it.hasNext()) {
6251             it.next();
6252             iteratedNames.insert(it.name());
6253         }
6254         for (int i = 0; i < propertyCount; ++i)
6255             QVERIFY(iteratedNames.contains(names[i]));
6256     }
6257
6258     // Push it on the scope chain of a new context.
6259     QScriptContext *ctx = eng.pushContext();
6260     ctx->pushScope(scope);
6261     QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
6262     QEXPECT_FAIL("", "activationObject has not been implemented yet", Continue);
6263     QVERIFY(ctx->activationObject().equals(scope));
6264
6265     // Read property from JS.
6266     for (int i = 0; i < propertyCount; ++i) {
6267         for (int x = 0; x < 2; ++x) {
6268             if (x) {
6269                 // Property can't be deleted from JS.
6270                 QScriptValue ret = eng.evaluate(QString::fromLatin1("delete %0").arg(names[i]));
6271                 QVERIFY(ret.equals(false));
6272             }
6273             QVERIFY(eng.evaluate(names[i]).equals(values[i]));
6274         }
6275     }
6276
6277     // Property that doesn't exist.
6278     QVERIFY(eng.evaluate("noSuchProperty").equals("ReferenceError: noSuchProperty is not defined"));
6279
6280     // Write property from JS.
6281     {
6282         QScriptValue oldValue = eng.evaluate("foo");
6283         QVERIFY(oldValue.isNumber());
6284         QScriptValue newValue = oldValue.toNumber() * 2;
6285         QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
6286         scope.setProperty("foo", oldValue);
6287         QVERIFY(eng.evaluate("foo").equals(oldValue));
6288     }
6289
6290     // Write to read-only property.
6291     QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
6292
6293     // Create a closure and return properties from there.
6294     {
6295         QScriptValue props = eng.evaluate("(function() { var baz = 'shadow'; return [foo, bar, baz, Math, Array]; })()");
6296         QVERIFY(props.isArray());
6297         // "foo" and "bar" come from scope object.
6298         QVERIFY(props.property(0).equals(scope.property("foo")));
6299         QVERIFY(props.property(1).equals(scope.property("bar")));
6300         // "baz" shadows property in scope object.
6301         QVERIFY(props.property(2).equals("shadow"));
6302         // "Math" comes from scope object, and shadows Global Object's "Math".
6303         QVERIFY(props.property(3).equals(scope.property("Math")));
6304         QVERIFY(!props.property(3).equals(eng.globalObject().property("Math")));
6305         // "Array" comes from Global Object.
6306         QVERIFY(props.property(4).equals(eng.globalObject().property("Array")));
6307     }
6308
6309     // As with normal JS, assigning to an undefined variable will create
6310     // the property on the Global Object, not the inner scope.
6311     QVERIFY(!eng.globalObject().property("newProperty").isValid());
6312     QVERIFY(eng.evaluate("(function() { newProperty = 789; })()").isUndefined());
6313     QVERIFY(!scope.property("newProperty").isValid());
6314     QVERIFY(eng.globalObject().property("newProperty").isNumber());
6315
6316     // Nested static scope.
6317     {
6318         static const int propertyCount2 = 2;
6319         QString names2[] = { "foo", "hum" };
6320         QScriptValue values2[] = { 321, "hello" };
6321         QScriptValue::PropertyFlags flags2[] = { QScriptValue::Undeletable,
6322                                                  QScriptValue::ReadOnly | QScriptValue::Undeletable };
6323         QScriptValue scope2 = QScriptDeclarativeClass::newStaticScopeObject(&eng, propertyCount2, names2, values2, flags2);
6324         ctx->pushScope(scope2);
6325
6326         // "foo" shadows scope.foo.
6327         QVERIFY(eng.evaluate("foo").equals(scope2.property("foo")));
6328         QVERIFY(!eng.evaluate("foo").equals(scope.property("foo")));
6329         // "hum" comes from scope2.
6330         QVERIFY(eng.evaluate("hum").equals(scope2.property("hum")));
6331         // "Array" comes from Global Object.
6332         QVERIFY(eng.evaluate("Array").equals(eng.globalObject().property("Array")));
6333
6334         ctx->popScope();
6335     }
6336
6337     QScriptValue fun = eng.evaluate("(function() { return foo; })");
6338     QVERIFY(fun.isFunction());
6339     eng.popContext();
6340     // Function's scope chain persists after popContext().
6341     QVERIFY(fun.call().equals(scope.property("foo")));
6342 }
6343
6344 void tst_QJSEngine::newGrowingStaticScopeObject()
6345 {
6346     // The main use case for a growing static scope object is to set it as
6347     // the activation object of a QScriptContext, so that all JS variable
6348     // declarations end up in that object. It needs to be "growable" since
6349     // we don't know in advance how many variables a script will declare.
6350
6351     QScriptEngine eng;
6352     QScriptValue scope = QScriptDeclarativeClass::newStaticScopeObject(&eng);
6353
6354     // Initially empty.
6355     QVERIFY(!QScriptValueIterator(scope).hasNext());
6356     QVERIFY(!scope.property("foo").isValid());
6357
6358     // Add a static property.
6359     scope.setProperty("foo", 123);
6360     QVERIFY(scope.property("foo").equals(123));
6361     QEXPECT_FAIL("", "FIXME: newStaticScopeObject not properly implemented", Abort);
6362     QCOMPARE(scope.propertyFlags("foo"), QScriptValue::Undeletable);
6363
6364     // Modify existing property.
6365     scope.setProperty("foo", 456);
6366     QVERIFY(scope.property("foo").equals(456));
6367
6368     // Add a read-only property.
6369     scope.setProperty("bar", "ciao", QScriptValue::ReadOnly);
6370     QVERIFY(scope.property("bar").equals("ciao"));
6371     QCOMPARE(scope.propertyFlags("bar"), QScriptValue::ReadOnly | QScriptValue::Undeletable);
6372
6373     // Attempt to modify read-only property.
6374     scope.setProperty("bar", "hello");
6375     QVERIFY(scope.property("bar").equals("ciao"));
6376
6377     // Properties can't be deleted.
6378     scope.setProperty("foo", QScriptValue());
6379     QVERIFY(scope.property("foo").equals(456));
6380     scope.setProperty("bar", QScriptValue());
6381     QVERIFY(scope.property("bar").equals("ciao"));
6382
6383     // Iterate.
6384     {
6385         QScriptValueIterator it(scope);
6386         QSet<QString> iteratedNames;
6387         while (it.hasNext()) {
6388             it.next();
6389             iteratedNames.insert(it.name());
6390         }
6391         QCOMPARE(iteratedNames.size(), 2);
6392         QVERIFY(iteratedNames.contains("foo"));
6393         QVERIFY(iteratedNames.contains("bar"));
6394     }
6395
6396     // Push it on the scope chain of a new context.
6397     QScriptContext *ctx = eng.pushContext();
6398     ctx->pushScope(scope);
6399     QCOMPARE(ctx->scopeChain().size(), 3); // Global Object, native activation, custom scope
6400     QVERIFY(ctx->activationObject().equals(scope));
6401
6402     // Read property from JS.
6403     QVERIFY(eng.evaluate("foo").equals(scope.property("foo")));
6404     QVERIFY(eng.evaluate("bar").equals(scope.property("bar")));
6405
6406     // Write property from JS.
6407     {
6408         QScriptValue oldValue = eng.evaluate("foo");
6409         QVERIFY(oldValue.isNumber());
6410         QScriptValue newValue = oldValue.toNumber() * 2;
6411         QVERIFY(eng.evaluate("foo = foo * 2; foo").equals(newValue));
6412         scope.setProperty("foo", oldValue);
6413         QVERIFY(eng.evaluate("foo").equals(oldValue));
6414     }
6415
6416     // Write to read-only property.
6417     QVERIFY(eng.evaluate("bar = 456; bar").equals("ciao"));
6418
6419     // Shadow property.
6420     QVERIFY(eng.evaluate("Math").equals(eng.globalObject().property("Math")));
6421     scope.setProperty("Math", "fake Math");
6422     QVERIFY(eng.evaluate("Math").equals(scope.property("Math")));
6423
6424     // Variable declarations will create properties on the scope.
6425     eng.evaluate("var baz = 456");
6426     QVERIFY(scope.property("baz").equals(456));
6427
6428     // Function declarations will create properties on the scope.
6429     eng.evaluate("function fun() { return baz; }");
6430     QVERIFY(scope.property("fun").isFunction());
6431     QVERIFY(scope.property("fun").call().equals(scope.property("baz")));
6432
6433     // Demonstrate the limitation of a growable static scope: Once a function that
6434     // uses the scope has been compiled, it won't pick up properties that are added
6435     // to the scope later.
6436     {
6437         QScriptValue fun = eng.evaluate("(function() { return futureProperty; })");
6438         QVERIFY(fun.isFunction());
6439         QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
6440         scope.setProperty("futureProperty", "added after the function was compiled");
6441         // If scope were dynamic, this would return the new property.
6442         QVERIFY(fun.call().toString().contains(QString::fromLatin1("ReferenceError")));
6443     }
6444
6445     eng.popContext();
6446 }
6447 #endif
6448
6449 #if 0 // ###FIXME: No QScript MetaObject API
6450 QT_BEGIN_NAMESPACE
6451 Q_SCRIPT_DECLARE_QMETAOBJECT(QStandardItemModel, QObject*)
6452 QT_END_NAMESPACE
6453
6454 void tst_QJSEngine::scriptValueFromQMetaObject()
6455 {
6456     QScriptEngine eng;
6457     {
6458         QScriptValue meta = eng.scriptValueFromQMetaObject<QScriptEngine>();
6459         QVERIFY(meta.isQMetaObject());
6460         QCOMPARE(meta.toQMetaObject(), &QScriptEngine::staticMetaObject);
6461         // Because of missing Q_SCRIPT_DECLARE_QMETAOBJECT() for QScriptEngine.
6462         QEXPECT_FAIL("", "FIXME: because construct never returns invalid values", Continue);
6463         QVERIFY(!meta.construct().isValid());
6464     }
6465     {
6466         QScriptValue meta = eng.scriptValueFromQMetaObject<QStandardItemModel>();
6467         QVERIFY(meta.isQMetaObject());
6468         QCOMPARE(meta.toQMetaObject(), &QStandardItemModel::staticMetaObject);
6469         QScriptValue obj = meta.construct(QScriptValueList() << eng.newQObject(&eng));
6470         QVERIFY(obj.isQObject());
6471         QStandardItemModel *model = qobject_cast<QStandardItemModel*>(obj.toQObject());
6472         QVERIFY(model != 0);
6473         QCOMPARE(model->parent(), (QObject*)&eng);
6474     }
6475 }
6476 #endif
6477
6478 void tst_QJSEngine::functionPrototypeExtensions()
6479 {
6480     // QJS adds connect and disconnect properties to Function.prototype.
6481     QJSEngine eng;
6482     QJSValue funProto = eng.globalObject().property("Function").property("prototype");
6483     QVERIFY(funProto.isFunction());
6484     QVERIFY(funProto.property("connect").isFunction());
6485     QCOMPARE(funProto.propertyFlags("connect"), QJSValue::SkipInEnumeration);
6486     QVERIFY(funProto.property("disconnect").isFunction());
6487     QCOMPARE(funProto.propertyFlags("disconnect"), QJSValue::SkipInEnumeration);
6488
6489     // No properties should appear in for-in statements.
6490     QJSValue props = eng.evaluate("props = []; for (var p in Function.prototype) props.push(p); props");
6491     QVERIFY(!eng.hasUncaughtException());
6492     QVERIFY(props.isArray());
6493     QCOMPARE(props.property("length").toInt32(), 0);
6494 }
6495
6496 QTEST_MAIN(tst_QJSEngine)
6497
6498 #include "tst_qjsengine.moc"
6499