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