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