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