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