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