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