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