1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Crimson AS <info@crimson.no> |
4 | ** Copyright (C) 2016 The Qt Company Ltd. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | #include <QtTest/QtTest> |
30 | #include <QtQml/qqmlcomponent.h> |
31 | #include <QtQml/qqmlengine.h> |
32 | #include <QtQml/qqmlexpression.h> |
33 | #include <QtQml/qqmlcontext.h> |
34 | #include <QtCore/qfileinfo.h> |
35 | #include <QtCore/qdebug.h> |
36 | #include <QtCore/qdir.h> |
37 | #include <QtCore/qnumeric.h> |
38 | #include <private/qqmlengine_p.h> |
39 | #include <private/qqmlvmemetaobject_p.h> |
40 | #include <private/qv4qmlcontext_p.h> |
41 | #include "testtypes.h" |
42 | #include "testhttpserver.h" |
43 | #include "../../shared/util.h" |
44 | #include <private/qv4functionobject_p.h> |
45 | #include <private/qv4scopedvalue_p.h> |
46 | #include <private/qv4jscall_p.h> |
47 | #include <private/qv4alloca_p.h> |
48 | #include <private/qv4runtime_p.h> |
49 | #include <private/qv4object_p.h> |
50 | #include <private/qv4script_p.h> |
51 | #include <private/qqmlcomponentattached_p.h> |
52 | #include <private/qv4objectiterator_p.h> |
53 | #include <private/qqmlabstractbinding_p.h> |
54 | #include <private/qqmlvaluetypeproxybinding_p.h> |
55 | |
56 | #ifdef Q_CC_MSVC |
57 | #define NO_INLINE __declspec(noinline) |
58 | #else |
59 | #define NO_INLINE __attribute__((noinline)) |
60 | #endif |
61 | |
62 | /* |
63 | This test covers evaluation of ECMAScript expressions and bindings from within |
64 | QML. This does not include static QML language issues. |
65 | |
66 | Static QML language issues are covered in qmllanguage |
67 | */ |
68 | |
69 | class tst_qqmlecmascript : public QQmlDataTest |
70 | { |
71 | Q_OBJECT |
72 | public: |
73 | tst_qqmlecmascript() {} |
74 | |
75 | private slots: |
76 | void initTestCase(); |
77 | void arrayIncludesValueType(); |
78 | void assignBasicTypes(); |
79 | void assignDate_data(); |
80 | void assignDate(); |
81 | void exportDate_data(); |
82 | void exportDate(); |
83 | void checkDate_data(); |
84 | void checkDate(); |
85 | void checkDateTime_data(); |
86 | void checkDateTime(); |
87 | void idShortcutInvalidates(); |
88 | void boolPropertiesEvaluateAsBool(); |
89 | void methods(); |
90 | void signalAssignment(); |
91 | void signalArguments(); |
92 | void bindingLoop(); |
93 | void basicExpressions(); |
94 | void basicExpressions_data(); |
95 | void arrayExpressions(); |
96 | void contextPropertiesTriggerReeval(); |
97 | void objectPropertiesTriggerReeval(); |
98 | void dependenciesWithFunctions(); |
99 | void deferredProperties(); |
100 | void deferredPropertiesErrors(); |
101 | void deferredPropertiesInComponents(); |
102 | void deferredPropertiesInDestruction(); |
103 | void extensionObjects(); |
104 | void overrideExtensionProperties(); |
105 | void attachedProperties(); |
106 | void enums(); |
107 | void valueTypeFunctions(); |
108 | void constantsOverrideBindings(); |
109 | void outerBindingOverridesInnerBinding(); |
110 | void aliasPropertyAndBinding(); |
111 | void aliasPropertyReset(); |
112 | void nonExistentAttachedObject(); |
113 | void scope(); |
114 | void importScope(); |
115 | void signalParameterTypes(); |
116 | void objectsCompareAsEqual(); |
117 | void componentCreation_data(); |
118 | void componentCreation(); |
119 | void dynamicCreation_data(); |
120 | void dynamicCreation(); |
121 | void dynamicDestruction(); |
122 | void objectToString(); |
123 | void objectHasOwnProperty(); |
124 | void selfDeletingBinding(); |
125 | void extendedObjectPropertyLookup(); |
126 | void extendedObjectPropertyLookup2(); |
127 | void uncreatableExtendedObjectFailureCheck(); |
128 | void extendedObjectPropertyLookup3(); |
129 | void scriptErrors(); |
130 | void functionErrors(); |
131 | void propertyAssignmentErrors(); |
132 | void signalTriggeredBindings(); |
133 | void listProperties(); |
134 | void exceptionClearsOnReeval(); |
135 | void exceptionSlotProducesWarning(); |
136 | void exceptionBindingProducesWarning(); |
137 | void compileInvalidBinding(); |
138 | void transientErrors(); |
139 | void shutdownErrors(); |
140 | void compositePropertyType(); |
141 | void jsObject(); |
142 | void undefinedResetsProperty(); |
143 | void listToVariant(); |
144 | void listAssignment(); |
145 | void multiEngineObject(); |
146 | void deletedObject(); |
147 | void attachedPropertyScope(); |
148 | void scriptConnect(); |
149 | void scriptDisconnect(); |
150 | void ownership(); |
151 | void cppOwnershipReturnValue(); |
152 | void ownershipCustomReturnValue(); |
153 | void ownershipRootObject(); |
154 | void ownershipConsistency(); |
155 | void ownershipQmlIncubated(); |
156 | void qlistqobjectMethods(); |
157 | void strictlyEquals(); |
158 | void compiled(); |
159 | void numberAssignment(); |
160 | void propertySplicing(); |
161 | void signalWithUnknownTypes(); |
162 | void signalWithJSValueInVariant_data(); |
163 | void signalWithJSValueInVariant(); |
164 | void signalWithJSValueInVariant_twoEngines_data(); |
165 | void signalWithJSValueInVariant_twoEngines(); |
166 | void signalWithQJSValue_data(); |
167 | void signalWithQJSValue(); |
168 | void singletonType_data(); |
169 | void singletonType(); |
170 | void singletonTypeCaching_data(); |
171 | void singletonTypeCaching(); |
172 | void singletonTypeImportOrder(); |
173 | void singletonTypeResolution(); |
174 | void importScripts_data(); |
175 | void importScripts(); |
176 | void importCreationContext(); |
177 | void scarceResources(); |
178 | void scarceResources_data(); |
179 | void scarceResources_other(); |
180 | void propertyChangeSlots(); |
181 | void propertyVar_data(); |
182 | void propertyVar(); |
183 | void propertyQJSValue_data(); |
184 | void propertyQJSValue(); |
185 | void propertyVarCpp(); |
186 | void propertyVarOwnership(); |
187 | void propertyVarImplicitOwnership(); |
188 | void propertyVarReparent(); |
189 | void propertyVarReparentNullContext(); |
190 | void propertyVarCircular(); |
191 | void propertyVarCircular2(); |
192 | void propertyVarInheritance(); |
193 | void propertyVarInheritance2(); |
194 | void elementAssign(); |
195 | void objectPassThroughSignals(); |
196 | void objectConversion(); |
197 | void booleanConversion(); |
198 | void handleReferenceManagement(); |
199 | void stringArg(); |
200 | void readonlyDeclaration(); |
201 | void sequenceConversionRead(); |
202 | void sequenceConversionWrite(); |
203 | void sequenceConversionArray(); |
204 | void sequenceConversionIndexes(); |
205 | void sequenceConversionThreads(); |
206 | void sequenceConversionBindings(); |
207 | void sequenceConversionCopy(); |
208 | void assignSequenceTypes(); |
209 | void sequenceSort_data(); |
210 | void sequenceSort(); |
211 | void dateParse(); |
212 | void utcDate(); |
213 | void negativeYear(); |
214 | void qtbug_22464(); |
215 | void qtbug_21580(); |
216 | void singleV8BindingDestroyedDuringEvaluation(); |
217 | void bug1(); |
218 | #ifndef QT_NO_WIDGETS |
219 | void bug2(); |
220 | #endif |
221 | void dynamicCreationCrash(); |
222 | void dynamicCreationOwnership(); |
223 | void regExpBug(); |
224 | void nullObjectBinding(); |
225 | void nullObjectInitializer(); |
226 | void deletedEngine(); |
227 | void libraryScriptAssert(); |
228 | void variantsAssignedUndefined(); |
229 | void variants(); |
230 | void qtbug_9792(); |
231 | void qtcreatorbug_1289(); |
232 | void noSpuriousWarningsAtShutdown(); |
233 | void canAssignNullToQObject(); |
234 | void functionAssignment_fromBinding(); |
235 | void functionAssignment_fromJS(); |
236 | void functionAssignment_fromJS_data(); |
237 | void functionAssignmentfromJS_invalid(); |
238 | void functionAssignment_afterBinding(); |
239 | void eval(); |
240 | void function(); |
241 | void topLevelGeneratorFunction(); |
242 | void generatorCrashNewProperty(); |
243 | void generatorCallsGC(); |
244 | void qtbug_10696(); |
245 | void qtbug_11606(); |
246 | void qtbug_11600(); |
247 | void qtbug_21864(); |
248 | void qobjectConnectionListExceptionHandling(); |
249 | void nonscriptable(); |
250 | void deleteLater(); |
251 | void objectNameChangedSignal(); |
252 | void destroyedSignal(); |
253 | void in(); |
254 | void typeOf(); |
255 | void qtbug_24448(); |
256 | void sharedAttachedObject(); |
257 | void objectName(); |
258 | void writeRemovesBinding(); |
259 | void aliasBindingsAssignCorrectly(); |
260 | void aliasBindingsOverrideTarget(); |
261 | void aliasWritesOverrideBindings(); |
262 | void aliasToCompositeElement(); |
263 | void realToInt(); |
264 | void urlProperty(); |
265 | void urlPropertyWithEncoding(); |
266 | void urlListPropertyWithEncoding(); |
267 | void dynamicString(); |
268 | void include(); |
269 | void includeRemoteSuccess(); |
270 | void signalHandlers(); |
271 | void qtbug_37351(); |
272 | void doubleEvaluate(); |
273 | void forInLoop(); |
274 | void nonNotifyable(); |
275 | void nonNotifyableConstant(); |
276 | void deleteWhileBindingRunning(); |
277 | void callQtInvokables(); |
278 | void resolveClashingProperties(); |
279 | void invokableObjectArg(); |
280 | void invokableObjectRet(); |
281 | void invokableEnumRet(); |
282 | void qtbug_20344(); |
283 | void qtbug_22679(); |
284 | void qtbug_22843_data(); |
285 | void qtbug_22843(); |
286 | void rewriteMultiLineStrings(); |
287 | void revisionErrors(); |
288 | void revision(); |
289 | void invokableWithQObjectDerived(); |
290 | void realTypePrecision(); |
291 | void registeredFlagMethod(); |
292 | void deleteLaterObjectMethodCall(); |
293 | void automaticSemicolon(); |
294 | void compatibilitySemicolon(); |
295 | void incrDecrSemicolon1(); |
296 | void incrDecrSemicolon2(); |
297 | void incrDecrSemicolon_error1(); |
298 | void unaryExpression(); |
299 | void switchStatement(); |
300 | void withStatement(); |
301 | void tryStatement(); |
302 | void replaceBinding(); |
303 | void bindingBoundFunctions(); |
304 | void deleteRootObjectInCreation(); |
305 | void onDestruction(); |
306 | void onDestructionViaGC(); |
307 | void bindingSuppression(); |
308 | void signalEmitted(); |
309 | void threadSignal(); |
310 | void qqmldataDestroyed(); |
311 | void secondAlias(); |
312 | void varAlias(); |
313 | void overrideDataAssert(); |
314 | void fallbackBindings_data(); |
315 | void fallbackBindings(); |
316 | void propertyOverride(); |
317 | void concatenatedStringPropertyAccess(); |
318 | void jsOwnedObjectsDeletedOnEngineDestroy(); |
319 | void updateCall(); |
320 | void numberParsing(); |
321 | void stringParsing(); |
322 | void push_and_shift(); |
323 | void qtbug_32801(); |
324 | void thisObject(); |
325 | void qtbug_33754(); |
326 | void qtbug_34493(); |
327 | void singletonFromQMLToCpp(); |
328 | void singletonFromQMLAndBackAndCompare(); |
329 | void setPropertyOnInvalid(); |
330 | void miscTypeTest(); |
331 | void stackLimits(); |
332 | void idsAsLValues(); |
333 | void qtbug_34792(); |
334 | void noCaptureWhenWritingProperty(); |
335 | void singletonWithEnum(); |
336 | void lazyBindingEvaluation(); |
337 | void varPropertyAccessOnObjectWithInvalidContext(); |
338 | void importedScriptsAccessOnObjectWithInvalidContext(); |
339 | void importedScriptsWithoutQmlMode(); |
340 | void contextObjectOnLazyBindings(); |
341 | void garbageCollectionDuringCreation(); |
342 | void qtbug_39520(); |
343 | void readUnregisteredQObjectProperty(); |
344 | void writeUnregisteredQObjectProperty(); |
345 | void switchExpression(); |
346 | void qtbug_46022(); |
347 | void qtbug_52340(); |
348 | void qtbug_54589(); |
349 | void qtbug_54687(); |
350 | void stringify_qtbug_50592(); |
351 | void instanceof_data(); |
352 | void instanceof(); |
353 | void constkw_data(); |
354 | void constkw(); |
355 | void redefineGlobalProp(); |
356 | void freeze_empty_object(); |
357 | void singleBlockLoops(); |
358 | void qtbug_60547(); |
359 | void delayLoadingArgs(); |
360 | void manyArguments(); |
361 | void forInIterator(); |
362 | void localForInIterator(); |
363 | void shadowedFunctionName(); |
364 | void anotherNaN(); |
365 | void callPropertyOnUndefined(); |
366 | void jumpStrictNotEqualUndefined(); |
367 | void removeBindingsWithNoDependencies(); |
368 | void preserveBindingWithUnresolvedNames(); |
369 | void temporaryDeadZone(); |
370 | void importLexicalVariables_data(); |
371 | void importLexicalVariables(); |
372 | void hugeObject(); |
373 | void templateStringTerminator(); |
374 | void arrayAndException(); |
375 | void numberToStringWithRadix(); |
376 | void tailCallWithArguments(); |
377 | void deleteSparseInIteration(); |
378 | void saveAccumulatorBeforeToInt32(); |
379 | void intMinDividedByMinusOne(); |
380 | void undefinedPropertiesInObjectWrapper(); |
381 | void hugeRegexpQuantifiers(); |
382 | void singletonTypeWrapperLookup(); |
383 | void getThisObject(); |
384 | void semicolonAfterProperty(); |
385 | void hugeStack(); |
386 | void variantConversionMethod(); |
387 | void proxyIteration(); |
388 | void proxyHandlerTraps(); |
389 | void gcCrashRegressionTest(); |
390 | void functionAsDefaultArgument(); |
391 | |
392 | private: |
393 | // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); |
394 | static void verifyContextLifetime(QQmlContextData *ctxt); |
395 | |
396 | // When calling into JavaScript, the specific type of the return value can differ if that return |
397 | // value is a number. This is not only the case for non-integral numbers, or numbers that do not |
398 | // fit into the (signed) integer range, but it also depends on which optimizations are run. So, |
399 | // to check if the return value is of a number type, use this method instead of checking against |
400 | // a specific userType. |
401 | static bool isJSNumberType(int userType) |
402 | { |
403 | return userType == (int) QVariant::Int |
404 | || userType == (int) QVariant::UInt |
405 | || userType == (int) QVariant::Double; |
406 | } |
407 | }; |
408 | |
409 | static void gc(QQmlEngine &engine) |
410 | { |
411 | engine.collectGarbage(); |
412 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
413 | QCoreApplication::processEvents(); |
414 | } |
415 | |
416 | |
417 | void tst_qqmlecmascript::initTestCase() |
418 | { |
419 | QQmlDataTest::initTestCase(); |
420 | registerTypes(); |
421 | } |
422 | |
423 | void tst_qqmlecmascript::arrayIncludesValueType() |
424 | { |
425 | QQmlEngine engine; |
426 | QQmlComponent component(&engine); |
427 | // It is vital that QtQuick is imported below else we get a warning about |
428 | // QQml_colorProvider and tst_qqmlecmascript::signalParameterTypes fails due |
429 | // to some static variable being initialized with the wrong value |
430 | component.setData(R"( |
431 | import QtQuick 2.15 |
432 | import QtQml 2.15 |
433 | QtObject { |
434 | id: root |
435 | property color r: Qt.rgba(1, 0, 0) |
436 | property color g: Qt.rgba(0, 1, 0) |
437 | property color b: Qt.rgba(0, 0, 1) |
438 | property var colors: [r, g, b] |
439 | property bool success: false |
440 | |
441 | Component.onCompleted: { |
442 | root.success = root.colors.includes(root.g) |
443 | } |
444 | } |
445 | )" , baseUrl: QUrl("testData" )); |
446 | QScopedPointer<QObject> o(component.create()); |
447 | QVERIFY(o); |
448 | auto success = o->property(name: "success" ); |
449 | QVERIFY(success.isValid()); |
450 | QVERIFY(success.toBool()); |
451 | } |
452 | |
453 | void tst_qqmlecmascript::assignBasicTypes() |
454 | { |
455 | QQmlEngine engine; |
456 | { |
457 | QQmlComponent component(&engine, testFileUrl(fileName: "assignBasicTypes.qml" )); |
458 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: component.create()); |
459 | QVERIFY(object != nullptr); |
460 | QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3); |
461 | QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2); |
462 | QCOMPARE(object->relatedEnumProperty(), MyEnumContainer::RelatedValue); |
463 | QCOMPARE(object->stringProperty(), QString("Hello World!" )); |
464 | QCOMPARE(object->uintProperty(), uint(10)); |
465 | QCOMPARE(object->intProperty(), -19); |
466 | QCOMPARE((float)object->realProperty(), float(23.2)); |
467 | QCOMPARE((float)object->doubleProperty(), float(-19.75)); |
468 | QCOMPARE((float)object->floatProperty(), float(8.5)); |
469 | QCOMPARE(object->colorProperty(), QColor("red" )); |
470 | QCOMPARE(object->dateProperty(), QDate(1982, 11, 25)); |
471 | QCOMPARE(object->timeProperty(), QTime(11, 11, 32)); |
472 | QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1), Qt::UTC)); |
473 | QCOMPARE(object->pointProperty(), QPoint(99,13)); |
474 | QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3)); |
475 | QCOMPARE(object->sizeProperty(), QSize(99, 13)); |
476 | QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2)); |
477 | QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200)); |
478 | QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99)); |
479 | QCOMPARE(object->boolProperty(), true); |
480 | QCOMPARE(object->variantProperty(), QVariant("Hello World!" )); |
481 | QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2f)); |
482 | QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml" ))); |
483 | delete object; |
484 | } |
485 | { |
486 | QQmlComponent component(&engine, testFileUrl(fileName: "assignBasicTypes.2.qml" )); |
487 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: component.create()); |
488 | QVERIFY(object != nullptr); |
489 | QCOMPARE(object->flagProperty(), MyTypeObject::FlagVal1 | MyTypeObject::FlagVal3); |
490 | QCOMPARE(object->enumProperty(), MyTypeObject::EnumVal2); |
491 | QCOMPARE(object->relatedEnumProperty(), MyEnumContainer::RelatedValue); |
492 | QCOMPARE(object->stringProperty(), QString("Hello World!" )); |
493 | QCOMPARE(object->uintProperty(), uint(10)); |
494 | QCOMPARE(object->intProperty(), -19); |
495 | QCOMPARE((float)object->realProperty(), float(23.2)); |
496 | QCOMPARE((float)object->doubleProperty(), float(-19.75)); |
497 | QCOMPARE((float)object->floatProperty(), float(8.5)); |
498 | QCOMPARE(object->colorProperty(), QColor("red" )); |
499 | QCOMPARE(object->dateProperty(), QDate(1982, 11, 25)); |
500 | QCOMPARE(object->timeProperty(), QTime(11, 11, 32)); |
501 | QCOMPARE(object->dateTimeProperty(), QDateTime(QDate(2009, 5, 12), QTime(13, 22, 1), Qt::UTC)); |
502 | QCOMPARE(object->pointProperty(), QPoint(99,13)); |
503 | QCOMPARE(object->pointFProperty(), QPointF(-10.1, 12.3)); |
504 | QCOMPARE(object->sizeProperty(), QSize(99, 13)); |
505 | QCOMPARE(object->sizeFProperty(), QSizeF(0.1, 0.2)); |
506 | QCOMPARE(object->rectProperty(), QRect(9, 7, 100, 200)); |
507 | QCOMPARE(object->rectFProperty(), QRectF(1000.1, -10.9, 400, 90.99)); |
508 | QCOMPARE(object->boolProperty(), true); |
509 | QCOMPARE(object->variantProperty(), QVariant("Hello World!" )); |
510 | QCOMPARE(object->vectorProperty(), QVector3D(10, 1, 2.2f)); |
511 | QCOMPARE(object->urlProperty(), component.url().resolved(QUrl("main.qml" ))); |
512 | delete object; |
513 | } |
514 | } |
515 | |
516 | void tst_qqmlecmascript::assignDate_data() |
517 | { |
518 | QTest::addColumn<QUrl>(name: "source" ); |
519 | QTest::addColumn<int>(name: "timeOffset" ); // -1 for local-time, else minutes from UTC |
520 | |
521 | QTest::newRow(dataTag: "Component.onComplete JS Parse" ) << testFileUrl(fileName: "assignDate.qml" ) << -1; |
522 | QTest::newRow(dataTag: "Component.onComplete JS" ) << testFileUrl(fileName: "assignDate.1.qml" ) << 0; |
523 | QTest::newRow(dataTag: "Binding JS" ) << testFileUrl(fileName: "assignDate.2.qml" ) << -1; |
524 | QTest::newRow(dataTag: "Binding UTC" ) << testFileUrl(fileName: "assignDate.3.qml" ) << 0; |
525 | QTest::newRow(dataTag: "Binding JS UTC" ) << testFileUrl(fileName: "assignDate.4.qml" ) << 0; |
526 | QTest::newRow(dataTag: "Binding UTC+2" ) << testFileUrl(fileName: "assignDate.5.qml" ) << 120; |
527 | QTest::newRow(dataTag: "Binding JS UTC+2 " ) << testFileUrl(fileName: "assignDate.6.qml" ) << 120; |
528 | } |
529 | |
530 | void tst_qqmlecmascript::assignDate() |
531 | { |
532 | QFETCH(QUrl, source); |
533 | QFETCH(int, timeOffset); |
534 | |
535 | QQmlEngine engine; |
536 | QQmlComponent component(&engine, source); |
537 | QScopedPointer<QObject> obj(component.create()); |
538 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: obj.data()); |
539 | QVERIFY(object != nullptr); |
540 | |
541 | QDate expectedDate(2009, 5, 12); |
542 | QDateTime expectedDateTime; |
543 | QDateTime expectedDateTime2; |
544 | if (timeOffset == -1) { |
545 | expectedDateTime = QDateTime(QDate(2009, 5, 12), QTime(0, 0, 1), Qt::LocalTime); |
546 | expectedDateTime2 = QDateTime(QDate(2009, 5, 12), QTime(23, 59, 59), Qt::LocalTime); |
547 | } else { |
548 | expectedDateTime = QDateTime(QDate(2009, 5, 12), QTime(0, 0, 1), Qt::OffsetFromUTC, timeOffset * 60); |
549 | expectedDateTime2 = QDateTime(QDate(2009, 5, 12), QTime(23, 59, 59), Qt::OffsetFromUTC, timeOffset * 60); |
550 | } |
551 | |
552 | QCOMPARE(object->dateProperty(), expectedDate); |
553 | QCOMPARE(object->dateTimeProperty(), expectedDateTime); |
554 | QCOMPARE(object->dateTimeProperty2(), expectedDateTime2); |
555 | QCOMPARE(object->boolProperty(), true); |
556 | } |
557 | |
558 | void tst_qqmlecmascript::exportDate_data() |
559 | { |
560 | QTest::addColumn<QUrl>(name: "source" ); |
561 | QTest::addColumn<QDateTime>(name: "datetime" ); |
562 | |
563 | // Verify that we can export datetime information to QML and that consumers can access |
564 | // the data correctly provided they know the TZ info associated with the value |
565 | |
566 | const QDate date(2009, 5, 12); |
567 | const QTime early(0, 0, 1); |
568 | const QTime late(23, 59, 59); |
569 | const int offset(((11 * 60) + 30) * 60); |
570 | |
571 | QTest::newRow(dataTag: "Localtime early" ) << testFileUrl(fileName: "exportDate.qml" ) << QDateTime(date, early, Qt::LocalTime); |
572 | QTest::newRow(dataTag: "Localtime late" ) << testFileUrl(fileName: "exportDate.2.qml" ) << QDateTime(date, late, Qt::LocalTime); |
573 | QTest::newRow(dataTag: "UTC early" ) << testFileUrl(fileName: "exportDate.3.qml" ) << QDateTime(date, early, Qt::UTC); |
574 | QTest::newRow(dataTag: "UTC late" ) << testFileUrl(fileName: "exportDate.4.qml" ) << QDateTime(date, late, Qt::UTC); |
575 | { |
576 | QDateTime dt(date, early, Qt::OffsetFromUTC); |
577 | dt.setOffsetFromUtc(offset); |
578 | QTest::newRow(dataTag: "+11:30 early" ) << testFileUrl(fileName: "exportDate.5.qml" ) << dt; |
579 | } |
580 | { |
581 | QDateTime dt(date, late, Qt::OffsetFromUTC); |
582 | dt.setOffsetFromUtc(offset); |
583 | QTest::newRow(dataTag: "+11:30 late" ) << testFileUrl(fileName: "exportDate.6.qml" ) << dt; |
584 | } |
585 | { |
586 | QDateTime dt(date, early, Qt::OffsetFromUTC); |
587 | dt.setOffsetFromUtc(-offset); |
588 | QTest::newRow(dataTag: "-11:30 early" ) << testFileUrl(fileName: "exportDate.7.qml" ) << dt; |
589 | } |
590 | { |
591 | QDateTime dt(date, late, Qt::OffsetFromUTC); |
592 | dt.setOffsetFromUtc(-offset); |
593 | QTest::newRow(dataTag: "-11:30 late" ) << testFileUrl(fileName: "exportDate.8.qml" ) << dt; |
594 | } |
595 | } |
596 | |
597 | void tst_qqmlecmascript::exportDate() |
598 | { |
599 | QFETCH(QUrl, source); |
600 | QFETCH(QDateTime, datetime); |
601 | |
602 | DateTimeExporter exporter(datetime); |
603 | |
604 | QQmlEngine e; |
605 | e.rootContext()->setContextProperty("datetimeExporter" , &exporter); |
606 | |
607 | QQmlComponent component(&e, source); |
608 | QScopedPointer<QObject> obj(component.create()); |
609 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: obj.data()); |
610 | QVERIFY(object != nullptr); |
611 | QCOMPARE(object->boolProperty(), true); |
612 | } |
613 | |
614 | void tst_qqmlecmascript::checkDate_data() |
615 | { |
616 | QTest::addColumn<QUrl>(name: "source" ); |
617 | QTest::addColumn<QDate>(name: "date" ); |
618 | // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12. |
619 | QTest::newRow(dataTag: "denormal-March" ) |
620 | << testFileUrl(fileName: "checkDate-denormal-March.qml" ) |
621 | << QDate(2019, 3, 1); |
622 | QTest::newRow(dataTag: "denormal-leap" ) |
623 | << testFileUrl(fileName: "checkDate-denormal-leap.qml" ) |
624 | << QDate(2020, 2, 29); |
625 | QTest::newRow(dataTag: "denormal-Feb" ) |
626 | << testFileUrl(fileName: "checkDate-denormal-Feb.qml" ) |
627 | << QDate(2019, 2, 28); |
628 | QTest::newRow(dataTag: "denormal-year" ) |
629 | << testFileUrl(fileName: "checkDate-denormal-year.qml" ) |
630 | << QDate(2019, 12, 31); |
631 | QTest::newRow(dataTag: "denormal-wrap" ) |
632 | << testFileUrl(fileName: "checkDate-denormal-wrap.qml" ) |
633 | << QDate(2020, 2, 29); |
634 | QTest::newRow(dataTag: "October" ) |
635 | << testFileUrl(fileName: "checkDate-October.qml" ) |
636 | << QDate(2019, 10, 3); |
637 | } |
638 | |
639 | void tst_qqmlecmascript::checkDate() |
640 | { |
641 | QFETCH(const QUrl, source); |
642 | QFETCH(const QDate, date); |
643 | QQmlEngine e; |
644 | QQmlComponent component(&e, source); |
645 | QScopedPointer<QObject> obj(component.create()); |
646 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: obj.data()); |
647 | QVERIFY(object != nullptr); |
648 | QCOMPARE(object->dateProperty(), date); |
649 | QVERIFY(object->boolProperty()); |
650 | } |
651 | |
652 | void tst_qqmlecmascript::checkDateTime_data() |
653 | { |
654 | QTest::addColumn<QUrl>(name: "source" ); |
655 | QTest::addColumn<QDateTime>(name: "when" ); |
656 | // NB: JavaScript month-indices are Jan = 0 to Dec = 11; QDate's are Jan = 1 to Dec = 12. |
657 | QTest::newRow(dataTag: "denormal-March" ) |
658 | << testFileUrl(fileName: "checkDateTime-denormal-March.qml" ) |
659 | << QDateTime(QDate(2019, 3, 1), QTime(0, 0, 0, 1), Qt::LocalTime); |
660 | QTest::newRow(dataTag: "denormal-leap" ) |
661 | << testFileUrl(fileName: "checkDateTime-denormal-leap.qml" ) |
662 | << QDateTime(QDate(2020, 2, 29), QTime(23, 59, 59, 999), Qt::LocalTime); |
663 | QTest::newRow(dataTag: "denormal-hours" ) |
664 | << testFileUrl(fileName: "checkDateTime-denormal-hours.qml" ) |
665 | << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); |
666 | QTest::newRow(dataTag: "denormal-minutes" ) |
667 | << testFileUrl(fileName: "checkDateTime-denormal-minutes.qml" ) |
668 | << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); |
669 | QTest::newRow(dataTag: "denormal-seconds" ) |
670 | << testFileUrl(fileName: "checkDateTime-denormal-seconds.qml" ) |
671 | << QDateTime(QDate(2020, 2, 29), QTime(0, 0), Qt::LocalTime); |
672 | QTest::newRow(dataTag: "October" ) |
673 | << testFileUrl(fileName: "checkDateTime-October.qml" ) |
674 | << QDateTime(QDate(2019, 10, 3), QTime(12, 0), Qt::LocalTime); |
675 | } |
676 | |
677 | void tst_qqmlecmascript::checkDateTime() |
678 | { |
679 | QFETCH(const QUrl, source); |
680 | QFETCH(const QDateTime, when); |
681 | QQmlEngine e; |
682 | QQmlComponent component(&e, source); |
683 | QScopedPointer<QObject> obj(component.create()); |
684 | MyTypeObject *object = qobject_cast<MyTypeObject *>(object: obj.data()); |
685 | QVERIFY(object != nullptr); |
686 | QCOMPARE(object->dateTimeProperty(), when); |
687 | QVERIFY(object->boolProperty()); |
688 | } |
689 | |
690 | void tst_qqmlecmascript::idShortcutInvalidates() |
691 | { |
692 | QQmlEngine engine; |
693 | { |
694 | QQmlComponent component(&engine, testFileUrl(fileName: "idShortcutInvalidates.qml" )); |
695 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
696 | QVERIFY(object != nullptr); |
697 | QVERIFY(object->objectProperty() != nullptr); |
698 | delete object->objectProperty(); |
699 | QVERIFY(!object->objectProperty()); |
700 | delete object; |
701 | } |
702 | |
703 | { |
704 | QQmlComponent component(&engine, testFileUrl(fileName: "idShortcutInvalidates.1.qml" )); |
705 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
706 | QVERIFY(object != nullptr); |
707 | QVERIFY(object->objectProperty() != nullptr); |
708 | delete object->objectProperty(); |
709 | QVERIFY(!object->objectProperty()); |
710 | delete object; |
711 | } |
712 | } |
713 | |
714 | void tst_qqmlecmascript::boolPropertiesEvaluateAsBool() |
715 | { |
716 | QQmlEngine engine; |
717 | { |
718 | QQmlComponent component(&engine, testFileUrl(fileName: "boolPropertiesEvaluateAsBool.1.qml" )); |
719 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
720 | QVERIFY(object != nullptr); |
721 | QCOMPARE(object->stringProperty(), QLatin1String("pass" )); |
722 | delete object; |
723 | } |
724 | { |
725 | QQmlComponent component(&engine, testFileUrl(fileName: "boolPropertiesEvaluateAsBool.2.qml" )); |
726 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
727 | QVERIFY(object != nullptr); |
728 | QCOMPARE(object->stringProperty(), QLatin1String("pass" )); |
729 | delete object; |
730 | } |
731 | } |
732 | |
733 | void tst_qqmlecmascript::signalAssignment() |
734 | { |
735 | QQmlEngine engine; |
736 | { |
737 | QQmlComponent component(&engine, testFileUrl(fileName: "signalAssignment.1.qml" )); |
738 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
739 | QVERIFY(object != nullptr); |
740 | QCOMPARE(object->string(), QString()); |
741 | emit object->basicSignal(); |
742 | QCOMPARE(object->string(), QString("pass" )); |
743 | delete object; |
744 | } |
745 | |
746 | { |
747 | QQmlComponent component(&engine, testFileUrl(fileName: "signalAssignment.2.qml" )); |
748 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
749 | QVERIFY(object != nullptr); |
750 | QCOMPARE(object->string(), QString()); |
751 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
752 | QCOMPARE(object->string(), QString("pass 19 Hello world! 10.25 3 2" )); |
753 | delete object; |
754 | } |
755 | |
756 | { |
757 | QQmlComponent component(&engine, testFileUrl(fileName: "signalAssignment.3.qml" )); |
758 | QVERIFY(component.isError()); |
759 | QString expectedErrorString = component.url().toString() + QLatin1String(":4 Signal uses unnamed parameter followed by named parameter.\n" ); |
760 | QCOMPARE(component.errorString(), expectedErrorString); |
761 | } |
762 | |
763 | { |
764 | QQmlComponent component(&engine, testFileUrl(fileName: "signalAssignment.4.qml" )); |
765 | QVERIFY(component.isError()); |
766 | QString expectedErrorString = component.url().toString() + QLatin1String(":5 Signal parameter \"parseInt\" hides global variable.\n" ); |
767 | QCOMPARE(component.errorString(), expectedErrorString); |
768 | } |
769 | } |
770 | |
771 | void tst_qqmlecmascript::signalArguments() |
772 | { |
773 | QQmlEngine engine; |
774 | { |
775 | QQmlComponent component(&engine, testFileUrl(fileName: "signalArguments.1.qml" )); |
776 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
777 | QVERIFY(object != nullptr); |
778 | QCOMPARE(object->string(), QString()); |
779 | emit object->basicSignal(); |
780 | QCOMPARE(object->string(), QString("pass" )); |
781 | QCOMPARE(object->property("argumentCount" ).toInt(), 0); |
782 | delete object; |
783 | } |
784 | |
785 | { |
786 | QQmlComponent component(&engine, testFileUrl(fileName: "signalArguments.2.qml" )); |
787 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
788 | QVERIFY(object != nullptr); |
789 | QCOMPARE(object->string(), QString()); |
790 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
791 | QCOMPARE(object->string(), QString("pass 19 Hello world! 10.25 3 2" )); |
792 | QCOMPARE(object->property("argumentCount" ).toInt(), 5); |
793 | delete object; |
794 | } |
795 | } |
796 | |
797 | void tst_qqmlecmascript::methods() |
798 | { |
799 | QQmlEngine engine; |
800 | { |
801 | QQmlComponent component(&engine, testFileUrl(fileName: "methods.1.qml" )); |
802 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
803 | QVERIFY(object != nullptr); |
804 | QCOMPARE(object->methodCalled(), false); |
805 | QCOMPARE(object->methodIntCalled(), false); |
806 | emit object->basicSignal(); |
807 | QCOMPARE(object->methodCalled(), true); |
808 | QCOMPARE(object->methodIntCalled(), false); |
809 | delete object; |
810 | } |
811 | |
812 | { |
813 | QQmlComponent component(&engine, testFileUrl(fileName: "methods.2.qml" )); |
814 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
815 | QVERIFY(object != nullptr); |
816 | QCOMPARE(object->methodCalled(), false); |
817 | QCOMPARE(object->methodIntCalled(), false); |
818 | emit object->basicSignal(); |
819 | QCOMPARE(object->methodCalled(), false); |
820 | QCOMPARE(object->methodIntCalled(), true); |
821 | delete object; |
822 | } |
823 | |
824 | { |
825 | QQmlComponent component(&engine, testFileUrl(fileName: "methods.3.qml" )); |
826 | QObject *object = component.create(); |
827 | QVERIFY(object != nullptr); |
828 | QCOMPARE(object->property("test" ).toInt(), 19); |
829 | delete object; |
830 | } |
831 | |
832 | { |
833 | QQmlComponent component(&engine, testFileUrl(fileName: "methods.4.qml" )); |
834 | QObject *object = component.create(); |
835 | QVERIFY(object != nullptr); |
836 | QCOMPARE(object->property("test" ).toInt(), 19); |
837 | QCOMPARE(object->property("test2" ).toInt(), 17); |
838 | QCOMPARE(object->property("test3" ).toInt(), 16); |
839 | delete object; |
840 | } |
841 | |
842 | { |
843 | QQmlComponent component(&engine, testFileUrl(fileName: "methods.5.qml" )); |
844 | QObject *object = component.create(); |
845 | QVERIFY(object != nullptr); |
846 | QCOMPARE(object->property("test" ).toInt(), 9); |
847 | delete object; |
848 | } |
849 | } |
850 | |
851 | void tst_qqmlecmascript::bindingLoop() |
852 | { |
853 | QQmlEngine engine; |
854 | QQmlComponent component(&engine, testFileUrl(fileName: "bindingLoop.qml" )); |
855 | QString warning = component.url().toString() + ":9:9: QML MyQmlObject: Binding loop detected for property \"stringProperty\"" ; |
856 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
857 | QObject *object = component.create(); |
858 | QVERIFY(object != nullptr); |
859 | delete object; |
860 | } |
861 | |
862 | void tst_qqmlecmascript::basicExpressions_data() |
863 | { |
864 | QTest::addColumn<QString>(name: "expression" ); |
865 | QTest::addColumn<QVariant>(name: "result" ); |
866 | QTest::addColumn<bool>(name: "nest" ); |
867 | |
868 | QTest::newRow(dataTag: "Syntax error (self test)" ) << "{console.log({'a':1'}.a)}" << QVariant() << false; |
869 | QTest::newRow(dataTag: "Context property" ) << "a" << QVariant(1944) << false; |
870 | QTest::newRow(dataTag: "Context property" ) << "a" << QVariant(1944) << true; |
871 | QTest::newRow(dataTag: "Context property expression" ) << "a * 2" << QVariant(3888) << false; |
872 | QTest::newRow(dataTag: "Context property expression" ) << "a * 2" << QVariant(3888) << true; |
873 | QTest::newRow(dataTag: "Overridden context property" ) << "b" << QVariant("Milk" ) << false; |
874 | QTest::newRow(dataTag: "Overridden context property" ) << "b" << QVariant("Cow" ) << true; |
875 | QTest::newRow(dataTag: "Object property" ) << "object.stringProperty" << QVariant("Object1" ) << false; |
876 | QTest::newRow(dataTag: "Object property" ) << "object.stringProperty" << QVariant("Object1" ) << true; |
877 | QTest::newRow(dataTag: "Overridden object property" ) << "objectOverride.stringProperty" << QVariant("Object2" ) << false; |
878 | QTest::newRow(dataTag: "Overridden object property" ) << "objectOverride.stringProperty" << QVariant("Object3" ) << true; |
879 | QTest::newRow(dataTag: "Default object property" ) << "horseLegs" << QVariant(4) << false; |
880 | QTest::newRow(dataTag: "Default object property" ) << "antLegs" << QVariant(6) << false; |
881 | QTest::newRow(dataTag: "Default object property" ) << "emuLegs" << QVariant(2) << false; |
882 | QTest::newRow(dataTag: "Nested default object property" ) << "horseLegs" << QVariant(4) << true; |
883 | QTest::newRow(dataTag: "Nested default object property" ) << "antLegs" << QVariant(7) << true; |
884 | QTest::newRow(dataTag: "Nested default object property" ) << "emuLegs" << QVariant(2) << true; |
885 | QTest::newRow(dataTag: "Nested default object property" ) << "humanLegs" << QVariant(2) << true; |
886 | QTest::newRow(dataTag: "Context property override default object property" ) << "millipedeLegs" << QVariant(100) << true; |
887 | } |
888 | |
889 | void tst_qqmlecmascript::basicExpressions() |
890 | { |
891 | QFETCH(QString, expression); |
892 | QFETCH(QVariant, result); |
893 | QFETCH(bool, nest); |
894 | |
895 | QQmlEngine engine; |
896 | |
897 | MyQmlObject object1; |
898 | MyQmlObject object2; |
899 | MyQmlObject object3; |
900 | MyDefaultObject1 default1; |
901 | MyDefaultObject3 default3; |
902 | object1.setStringProperty("Object1" ); |
903 | object2.setStringProperty("Object2" ); |
904 | object3.setStringProperty("Object3" ); |
905 | |
906 | QQmlContext context(engine.rootContext()); |
907 | QQmlContext nestedContext(&context); |
908 | |
909 | context.setContextObject(&default1); |
910 | context.setContextProperty("a" , QVariant(1944)); |
911 | context.setContextProperty("b" , QVariant("Milk" )); |
912 | context.setContextProperty("object" , &object1); |
913 | context.setContextProperty("objectOverride" , &object2); |
914 | nestedContext.setContextObject(&default3); |
915 | nestedContext.setContextProperty("b" , QVariant("Cow" )); |
916 | nestedContext.setContextProperty("objectOverride" , &object3); |
917 | nestedContext.setContextProperty("millipedeLegs" , QVariant(100)); |
918 | |
919 | MyExpression expr(nest?&nestedContext:&context, expression); |
920 | QCOMPARE(expr.evaluate(), result); |
921 | } |
922 | |
923 | void tst_qqmlecmascript::arrayExpressions() |
924 | { |
925 | QObject obj1; |
926 | QObject obj2; |
927 | QObject obj3; |
928 | |
929 | QQmlEngine engine; |
930 | QQmlContext context(engine.rootContext()); |
931 | context.setContextProperty("a" , &obj1); |
932 | context.setContextProperty("b" , &obj2); |
933 | context.setContextProperty("c" , &obj3); |
934 | |
935 | MyExpression expr(&context, "[a, b, c, 10]" ); |
936 | QVariant result = expr.evaluate(); |
937 | QCOMPARE(result.userType(), qMetaTypeId<QJSValue>()); |
938 | QJSValue list = qvariant_cast<QJSValue>(v: result); |
939 | QCOMPARE(list.property("length" ).toInt(), 4); |
940 | QCOMPARE(list.property(0).toQObject(), &obj1); |
941 | QCOMPARE(list.property(1).toQObject(), &obj2); |
942 | QCOMPARE(list.property(2).toQObject(), &obj3); |
943 | QCOMPARE(list.property(3).toInt(), 10); |
944 | } |
945 | |
946 | // Tests that modifying a context property will reevaluate expressions |
947 | void tst_qqmlecmascript::contextPropertiesTriggerReeval() |
948 | { |
949 | QQmlEngine engine; |
950 | QQmlContext context(engine.rootContext()); |
951 | MyQmlObject object1; |
952 | MyQmlObject object2; |
953 | MyQmlObject *object3 = new MyQmlObject; |
954 | |
955 | object1.setStringProperty("Hello" ); |
956 | object2.setStringProperty("World" ); |
957 | |
958 | context.setContextProperty("testProp" , QVariant(1)); |
959 | context.setContextProperty("testObj" , &object1); |
960 | context.setContextProperty("testObj2" , object3); |
961 | |
962 | { |
963 | MyExpression expr(&context, "testProp + 1" ); |
964 | QCOMPARE(expr.changed, false); |
965 | QCOMPARE(expr.evaluate(), QVariant(2)); |
966 | |
967 | context.setContextProperty("testProp" , QVariant(2)); |
968 | QCOMPARE(expr.changed, true); |
969 | QCOMPARE(expr.evaluate(), QVariant(3)); |
970 | } |
971 | |
972 | { |
973 | MyExpression expr(&context, "testProp + testProp + testProp" ); |
974 | QCOMPARE(expr.changed, false); |
975 | QCOMPARE(expr.evaluate(), QVariant(6)); |
976 | |
977 | context.setContextProperty("testProp" , QVariant(4)); |
978 | QCOMPARE(expr.changed, true); |
979 | QCOMPARE(expr.evaluate(), QVariant(12)); |
980 | } |
981 | |
982 | { |
983 | MyExpression expr(&context, "testObj.stringProperty" ); |
984 | QCOMPARE(expr.changed, false); |
985 | QCOMPARE(expr.evaluate(), QVariant("Hello" )); |
986 | |
987 | context.setContextProperty("testObj" , &object2); |
988 | QCOMPARE(expr.changed, true); |
989 | QCOMPARE(expr.evaluate(), QVariant("World" )); |
990 | } |
991 | |
992 | { |
993 | MyExpression expr(&context, "testObj.stringProperty /**/" ); |
994 | QCOMPARE(expr.changed, false); |
995 | QCOMPARE(expr.evaluate(), QVariant("World" )); |
996 | |
997 | context.setContextProperty("testObj" , &object1); |
998 | QCOMPARE(expr.changed, true); |
999 | QCOMPARE(expr.evaluate(), QVariant("Hello" )); |
1000 | } |
1001 | |
1002 | { |
1003 | MyExpression expr(&context, "testObj2" ); |
1004 | QCOMPARE(expr.changed, false); |
1005 | QCOMPARE(expr.evaluate(), QVariant::fromValue((QObject *)object3)); |
1006 | } |
1007 | |
1008 | delete object3; |
1009 | } |
1010 | |
1011 | void tst_qqmlecmascript::objectPropertiesTriggerReeval() |
1012 | { |
1013 | QQmlEngine engine; |
1014 | QQmlContext context(engine.rootContext()); |
1015 | MyQmlObject object1; |
1016 | MyQmlObject object2; |
1017 | MyQmlObject object3; |
1018 | context.setContextProperty("testObj" , &object1); |
1019 | |
1020 | object1.setStringProperty(QLatin1String("Hello" )); |
1021 | object2.setStringProperty(QLatin1String("Dog" )); |
1022 | object3.setStringProperty(QLatin1String("Cat" )); |
1023 | |
1024 | { |
1025 | MyExpression expr(&context, "testObj.stringProperty" ); |
1026 | QCOMPARE(expr.changed, false); |
1027 | QCOMPARE(expr.evaluate(), QVariant("Hello" )); |
1028 | |
1029 | object1.setStringProperty(QLatin1String("World" )); |
1030 | QCOMPARE(expr.changed, true); |
1031 | QCOMPARE(expr.evaluate(), QVariant("World" )); |
1032 | } |
1033 | |
1034 | { |
1035 | MyExpression expr(&context, "testObj.objectProperty.stringProperty" ); |
1036 | QCOMPARE(expr.changed, false); |
1037 | QCOMPARE(expr.evaluate(), QVariant()); |
1038 | |
1039 | object1.setObjectProperty(&object2); |
1040 | QCOMPARE(expr.changed, true); |
1041 | expr.changed = false; |
1042 | QCOMPARE(expr.evaluate(), QVariant("Dog" )); |
1043 | |
1044 | object1.setObjectProperty(&object3); |
1045 | QCOMPARE(expr.changed, true); |
1046 | expr.changed = false; |
1047 | QCOMPARE(expr.evaluate(), QVariant("Cat" )); |
1048 | |
1049 | object1.setObjectProperty(nullptr); |
1050 | QCOMPARE(expr.changed, true); |
1051 | expr.changed = false; |
1052 | QCOMPARE(expr.evaluate(), QVariant()); |
1053 | |
1054 | object1.setObjectProperty(&object3); |
1055 | QCOMPARE(expr.changed, true); |
1056 | expr.changed = false; |
1057 | QCOMPARE(expr.evaluate(), QVariant("Cat" )); |
1058 | |
1059 | object3.setStringProperty("Donkey" ); |
1060 | QCOMPARE(expr.changed, true); |
1061 | expr.changed = false; |
1062 | QCOMPARE(expr.evaluate(), QVariant("Donkey" )); |
1063 | } |
1064 | } |
1065 | |
1066 | void tst_qqmlecmascript::dependenciesWithFunctions() |
1067 | { |
1068 | QQmlEngine engine; |
1069 | QQmlComponent component(&engine, testFileUrl(fileName: "dependenciesWithFunctions.qml" )); |
1070 | |
1071 | QScopedPointer<QObject> object(component.create()); |
1072 | QVERIFY2(object, qPrintable(component.errorString())); |
1073 | QVERIFY(!object->property("success" ).toBool()); |
1074 | object->setProperty(name: "value" , value: 42); |
1075 | QVERIFY(object->property("success" ).toBool()); |
1076 | } |
1077 | |
1078 | void tst_qqmlecmascript::deferredProperties() |
1079 | { |
1080 | QQmlEngine engine; |
1081 | QQmlComponent component(&engine, testFileUrl(fileName: "deferredProperties.qml" )); |
1082 | MyDeferredObject *object = |
1083 | qobject_cast<MyDeferredObject *>(object: component.create()); |
1084 | QVERIFY(object != nullptr); |
1085 | QCOMPARE(object->value(), 0); |
1086 | QVERIFY(!object->objectProperty()); |
1087 | QVERIFY(object->objectProperty2() != nullptr); |
1088 | qmlExecuteDeferred(object); |
1089 | QCOMPARE(object->value(), 10); |
1090 | QVERIFY(object->objectProperty() != nullptr); |
1091 | MyQmlObject *qmlObject = |
1092 | qobject_cast<MyQmlObject *>(object: object->objectProperty()); |
1093 | QVERIFY(qmlObject != nullptr); |
1094 | QCOMPARE(qmlObject->value(), 10); |
1095 | object->setValue(19); |
1096 | QCOMPARE(qmlObject->value(), 19); |
1097 | |
1098 | delete object; |
1099 | } |
1100 | |
1101 | // Check errors on deferred properties are correctly emitted |
1102 | void tst_qqmlecmascript::deferredPropertiesErrors() |
1103 | { |
1104 | QQmlEngine engine; |
1105 | QQmlComponent component(&engine, testFileUrl(fileName: "deferredPropertiesErrors.qml" )); |
1106 | MyDeferredObject *object = |
1107 | qobject_cast<MyDeferredObject *>(object: component.create()); |
1108 | QVERIFY(object != nullptr); |
1109 | QCOMPARE(object->value(), 0); |
1110 | QVERIFY(!object->objectProperty()); |
1111 | QVERIFY(!object->objectProperty2()); |
1112 | |
1113 | QString warning = component.url().toString() + ":6:5: Unable to assign [undefined] to QObject*" ; |
1114 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
1115 | |
1116 | qmlExecuteDeferred(object); |
1117 | |
1118 | delete object; |
1119 | } |
1120 | |
1121 | void tst_qqmlecmascript::deferredPropertiesInComponents() |
1122 | { |
1123 | // Test that it works when the property is set inside and outside component |
1124 | QQmlEngine engine; |
1125 | QQmlComponent component(&engine, testFileUrl(fileName: "deferredPropertiesInComponents.qml" )); |
1126 | QObject *object = component.create(); |
1127 | if (!object) |
1128 | qDebug() << component.errorString(); |
1129 | QVERIFY(object != nullptr); |
1130 | QCOMPARE(object->property("value" ).value<int>(), 10); |
1131 | |
1132 | MyDeferredObject *defObjectA = |
1133 | qobject_cast<MyDeferredObject *>(object: object->property(name: "deferredInside" ).value<QObject*>()); |
1134 | QVERIFY(defObjectA != nullptr); |
1135 | QVERIFY(!defObjectA->objectProperty()); |
1136 | |
1137 | qmlExecuteDeferred(defObjectA); |
1138 | QVERIFY(defObjectA->objectProperty() != nullptr); |
1139 | QCOMPARE(defObjectA->objectProperty()->property("value" ).value<int>(), 10); |
1140 | |
1141 | MyDeferredObject *defObjectB = |
1142 | qobject_cast<MyDeferredObject *>(object: object->property(name: "deferredOutside" ).value<QObject*>()); |
1143 | QVERIFY(defObjectB != nullptr); |
1144 | QVERIFY(!defObjectB->objectProperty()); |
1145 | |
1146 | qmlExecuteDeferred(defObjectB); |
1147 | QVERIFY(defObjectB->objectProperty() != nullptr); |
1148 | QCOMPARE(defObjectB->objectProperty()->property("value" ).value<int>(), 10); |
1149 | |
1150 | delete object; |
1151 | } |
1152 | |
1153 | void tst_qqmlecmascript::deferredPropertiesInDestruction() |
1154 | { |
1155 | //Test that the component does not get created at all if creation is deferred until the containing context is destroyed |
1156 | //Very specific operation ordering is needed for this to occur, currently accessing object from object destructor. |
1157 | // |
1158 | QQmlEngine engine; |
1159 | QQmlComponent component(&engine, testFileUrl(fileName: "deferredPropertiesInDestruction.qml" )); |
1160 | QObject *object = component.create(); |
1161 | if (!object) |
1162 | qDebug() << component.errorString(); |
1163 | QVERIFY(object != nullptr); |
1164 | delete object; //QTBUG-33112 was that this used to cause a crash |
1165 | } |
1166 | |
1167 | void tst_qqmlecmascript::extensionObjects() |
1168 | { |
1169 | QQmlEngine engine; |
1170 | QQmlComponent component(&engine, testFileUrl(fileName: "extensionObjects.qml" )); |
1171 | MyExtendedObject *object = |
1172 | qobject_cast<MyExtendedObject *>(object: component.create()); |
1173 | QVERIFY(object != nullptr); |
1174 | QCOMPARE(object->baseProperty(), 13); |
1175 | QCOMPARE(object->coreProperty(), 9); |
1176 | object->setProperty(name: "extendedProperty" , value: QVariant(11)); |
1177 | object->setProperty(name: "baseExtendedProperty" , value: QVariant(92)); |
1178 | QCOMPARE(object->coreProperty(), 11); |
1179 | QCOMPARE(object->baseProperty(), 92); |
1180 | |
1181 | MyExtendedObject *nested = qobject_cast<MyExtendedObject*>(object: qvariant_cast<QObject *>(v: object->property(name: "nested" ))); |
1182 | QVERIFY(nested); |
1183 | QCOMPARE(nested->baseProperty(), 13); |
1184 | QCOMPARE(nested->coreProperty(), 9); |
1185 | nested->setProperty(name: "extendedProperty" , value: QVariant(11)); |
1186 | nested->setProperty(name: "baseExtendedProperty" , value: QVariant(92)); |
1187 | QCOMPARE(nested->coreProperty(), 11); |
1188 | QCOMPARE(nested->baseProperty(), 92); |
1189 | |
1190 | delete object; |
1191 | } |
1192 | |
1193 | void tst_qqmlecmascript::overrideExtensionProperties() |
1194 | { |
1195 | QQmlEngine engine; |
1196 | QQmlComponent component(&engine, testFileUrl(fileName: "extensionObjectsPropertyOverride.qml" )); |
1197 | OverrideDefaultPropertyObject *object = |
1198 | qobject_cast<OverrideDefaultPropertyObject *>(object: component.create()); |
1199 | QVERIFY(object != nullptr); |
1200 | QVERIFY(object->secondProperty() != nullptr); |
1201 | QVERIFY(!object->firstProperty()); |
1202 | |
1203 | delete object; |
1204 | } |
1205 | |
1206 | void tst_qqmlecmascript::attachedProperties() |
1207 | { |
1208 | QQmlEngine engine; |
1209 | |
1210 | { |
1211 | QQmlComponent component(&engine, testFileUrl(fileName: "attachedProperty.qml" )); |
1212 | QObject *object = component.create(); |
1213 | QVERIFY(object != nullptr); |
1214 | QCOMPARE(object->property("a" ).toInt(), 19); |
1215 | QCOMPARE(object->property("b" ).toInt(), 19); |
1216 | QCOMPARE(object->property("c" ).toInt(), 19); |
1217 | QCOMPARE(object->property("d" ).toInt(), 19); |
1218 | delete object; |
1219 | } |
1220 | |
1221 | { |
1222 | QQmlComponent component(&engine, testFileUrl(fileName: "attachedProperty.2.qml" )); |
1223 | QObject *object = component.create(); |
1224 | QVERIFY(object != nullptr); |
1225 | QCOMPARE(object->property("a" ).toInt(), 26); |
1226 | QCOMPARE(object->property("b" ).toInt(), 26); |
1227 | QCOMPARE(object->property("c" ).toInt(), 26); |
1228 | QCOMPARE(object->property("d" ).toInt(), 26); |
1229 | |
1230 | delete object; |
1231 | } |
1232 | |
1233 | { |
1234 | QQmlComponent component(&engine, testFileUrl(fileName: "writeAttachedProperty.qml" )); |
1235 | QObject *object = component.create(); |
1236 | QVERIFY(object != nullptr); |
1237 | |
1238 | QMetaObject::invokeMethod(obj: object, member: "writeValue2" ); |
1239 | |
1240 | MyQmlAttachedObject *attached = |
1241 | qobject_cast<MyQmlAttachedObject *>(object: qmlAttachedPropertiesObject<MyQmlObject>(obj: object)); |
1242 | QVERIFY(attached != nullptr); |
1243 | |
1244 | QCOMPARE(attached->value2(), 9); |
1245 | delete object; |
1246 | } |
1247 | } |
1248 | |
1249 | void tst_qqmlecmascript::enums() |
1250 | { |
1251 | QQmlEngine engine; |
1252 | |
1253 | // Existent enums |
1254 | { |
1255 | QQmlComponent component(&engine, testFileUrl(fileName: "enums.1.qml" )); |
1256 | QObject *object = component.create(); |
1257 | QVERIFY(object != nullptr); |
1258 | |
1259 | QCOMPARE(object->property("enumProperty" ).toInt(), (int)MyQmlObject::EnumValue2); |
1260 | QCOMPARE(object->property("relatedEnumProperty" ).toInt(), (int)MyEnumContainer::RelatedValue); |
1261 | QCOMPARE(object->property("unrelatedEnumProperty" ).toInt(), (int)MyEnumContainer::RelatedValue); |
1262 | QCOMPARE(object->property("qtEnumProperty" ).toInt(), (int)Qt::CaseInsensitive); |
1263 | QCOMPARE(object->property("a" ).toInt(), 0); |
1264 | QCOMPARE(object->property("b" ).toInt(), 1); |
1265 | QCOMPARE(object->property("c" ).toInt(), 2); |
1266 | QCOMPARE(object->property("d" ).toInt(), 3); |
1267 | QCOMPARE(object->property("e" ).toInt(), 0); |
1268 | QCOMPARE(object->property("f" ).toInt(), 1); |
1269 | QCOMPARE(object->property("g" ).toInt(), 2); |
1270 | QCOMPARE(object->property("h" ).toInt(), 3); |
1271 | QCOMPARE(object->property("i" ).toInt(), 19); |
1272 | QCOMPARE(object->property("j" ).toInt(), 19); |
1273 | QCOMPARE(object->property("k" ).toInt(), 42); |
1274 | QCOMPARE(object->property("l" ).toInt(), 333); |
1275 | |
1276 | delete object; |
1277 | } |
1278 | // Non-existent enums |
1279 | { |
1280 | QUrl file = testFileUrl(fileName: "enums.2.qml" ); |
1281 | QString w1 = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'MyEnum' for property 'MyUnregisteredEnumTypeObject::enumProperty'" ); |
1282 | QString w2 = QLatin1String("QQmlExpression: Expression " ) + testFileUrl(fileName: "enums.2.qml" ).toString() + QLatin1String(":9:5 depends on non-NOTIFYable properties:" ); |
1283 | QString w3 = QLatin1String(" MyUnregisteredEnumTypeObject::enumProperty" ); |
1284 | QString w4 = file.toString() + ":7:5: Unable to assign [undefined] to int" ; |
1285 | QString w5 = file.toString() + ":8:5: Unable to assign [undefined] to int" ; |
1286 | QString w6 = file.toString() + ":9:5: Unable to assign [undefined] to int" ; |
1287 | QString w7 = file.toString() + ":13:9: Unable to assign [undefined] to [unknown property type]" ; |
1288 | QString w8 = file.toString() + ":31:9: Unable to assign int to [unknown property type]" ; |
1289 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w1)); |
1290 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w2)); |
1291 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w3)); |
1292 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w4)); |
1293 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w5)); |
1294 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w6)); |
1295 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w7)); |
1296 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w8)); |
1297 | |
1298 | QQmlComponent component(&engine, testFileUrl(fileName: "enums.2.qml" )); |
1299 | QObject *object = component.create(); |
1300 | QVERIFY(object != nullptr); |
1301 | QCOMPARE(object->property("a" ).toInt(), 0); |
1302 | QCOMPARE(object->property("b" ).toInt(), 0); |
1303 | QCOMPARE(object->property("c" ).toInt(), 0); |
1304 | |
1305 | QString w9 = file.toString() + ":18: Error: Cannot assign JavaScript function to [unknown property type]" ; |
1306 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w9)); |
1307 | QMetaObject::invokeMethod(obj: object, member: "testAssignmentOne" ); |
1308 | |
1309 | QString w10 = file.toString() + ":21: Error: Cannot assign [undefined] to [unknown property type]" ; |
1310 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w10)); |
1311 | QMetaObject::invokeMethod(obj: object, member: "testAssignmentTwo" ); |
1312 | |
1313 | QString w11 = file.toString() + ":24: Error: Cannot assign [undefined] to [unknown property type]" ; |
1314 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w11)); |
1315 | QMetaObject::invokeMethod(obj: object, member: "testAssignmentThree" ); |
1316 | |
1317 | QString w12 = file.toString() + ":34: Error: Cannot assign int to an unregistered type" ; |
1318 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w12)); |
1319 | QMetaObject::invokeMethod(obj: object, member: "testAssignmentFour" ); |
1320 | |
1321 | delete object; |
1322 | } |
1323 | // Enums as literals |
1324 | { |
1325 | QQmlComponent component(&engine, testFileUrl(fileName: "enums.3.qml" )); |
1326 | QObject *object = component.create(); |
1327 | QVERIFY(object != nullptr); |
1328 | |
1329 | // check the values are what we expect |
1330 | QCOMPARE(object->property("a" ).toInt(), 4); |
1331 | QCOMPARE(object->property("b" ).toInt(), 5); |
1332 | QCOMPARE(object->property("c" ).toInt(), 9); |
1333 | QCOMPARE(object->property("d" ).toInt(), 13); |
1334 | QCOMPARE(object->property("e" ).toInt(), 2); |
1335 | QCOMPARE(object->property("f" ).toInt(), 3); |
1336 | QCOMPARE(object->property("h" ).toInt(), 2); |
1337 | QCOMPARE(object->property("i" ).toInt(), 3); |
1338 | QCOMPARE(object->property("j" ).toInt(), -1); |
1339 | QCOMPARE(object->property("k" ).toInt(), 42); |
1340 | |
1341 | // count of change signals |
1342 | QCOMPARE(object->property("ac" ).toInt(), 0); |
1343 | QCOMPARE(object->property("bc" ).toInt(), 0); |
1344 | QCOMPARE(object->property("cc" ).toInt(), 0); |
1345 | QCOMPARE(object->property("dc" ).toInt(), 0); |
1346 | QCOMPARE(object->property("ec" ).toInt(), 0); |
1347 | QCOMPARE(object->property("fc" ).toInt(), 0); |
1348 | QCOMPARE(object->property("hc" ).toInt(), 1); // namespace -> binding |
1349 | QCOMPARE(object->property("ic" ).toInt(), 1); // namespace -> binding |
1350 | QCOMPARE(object->property("jc" ).toInt(), 0); |
1351 | QCOMPARE(object->property("kc" ).toInt(), 0); |
1352 | |
1353 | delete object; |
1354 | } |
1355 | } |
1356 | |
1357 | void tst_qqmlecmascript::valueTypeFunctions() |
1358 | { |
1359 | QQmlEngine engine; |
1360 | QQmlComponent component(&engine, testFileUrl(fileName: "valueTypeFunctions.qml" )); |
1361 | MyTypeObject *obj = qobject_cast<MyTypeObject*>(object: component.create()); |
1362 | QVERIFY(obj != nullptr); |
1363 | QCOMPARE(obj->rectProperty(), QRect(0,0,100,100)); |
1364 | QCOMPARE(obj->rectFProperty(), QRectF(0,0.5,100,99.5)); |
1365 | |
1366 | delete obj; |
1367 | } |
1368 | |
1369 | /* |
1370 | Tests that writing a constant to a property with a binding on it disables the |
1371 | binding. |
1372 | */ |
1373 | void tst_qqmlecmascript::constantsOverrideBindings() |
1374 | { |
1375 | QQmlEngine engine; |
1376 | |
1377 | // From ECMAScript |
1378 | { |
1379 | QQmlComponent component(&engine, testFileUrl(fileName: "constantsOverrideBindings.1.qml" )); |
1380 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1381 | QVERIFY(object != nullptr); |
1382 | |
1383 | QCOMPARE(object->property("c2" ).toInt(), 0); |
1384 | object->setProperty(name: "c1" , value: QVariant(9)); |
1385 | QCOMPARE(object->property("c2" ).toInt(), 9); |
1386 | |
1387 | emit object->basicSignal(); |
1388 | |
1389 | QCOMPARE(object->property("c2" ).toInt(), 13); |
1390 | object->setProperty(name: "c1" , value: QVariant(8)); |
1391 | QCOMPARE(object->property("c2" ).toInt(), 13); |
1392 | |
1393 | delete object; |
1394 | } |
1395 | |
1396 | // During construction |
1397 | { |
1398 | QQmlComponent component(&engine, testFileUrl(fileName: "constantsOverrideBindings.2.qml" )); |
1399 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1400 | QVERIFY(object != nullptr); |
1401 | |
1402 | QCOMPARE(object->property("c1" ).toInt(), 0); |
1403 | QCOMPARE(object->property("c2" ).toInt(), 10); |
1404 | object->setProperty(name: "c1" , value: QVariant(9)); |
1405 | QCOMPARE(object->property("c1" ).toInt(), 9); |
1406 | QCOMPARE(object->property("c2" ).toInt(), 10); |
1407 | |
1408 | delete object; |
1409 | } |
1410 | |
1411 | #if 0 |
1412 | // From C++ |
1413 | { |
1414 | QQmlComponent component(&engine, testFileUrl("constantsOverrideBindings.3.qml" )); |
1415 | MyQmlObject *object = qobject_cast<MyQmlObject *>(component.create()); |
1416 | QVERIFY(object != 0); |
1417 | |
1418 | QCOMPARE(object->property("c2" ).toInt(), 0); |
1419 | object->setProperty("c1" , QVariant(9)); |
1420 | QCOMPARE(object->property("c2" ).toInt(), 9); |
1421 | |
1422 | object->setProperty("c2" , QVariant(13)); |
1423 | QCOMPARE(object->property("c2" ).toInt(), 13); |
1424 | object->setProperty("c1" , QVariant(7)); |
1425 | QCOMPARE(object->property("c1" ).toInt(), 7); |
1426 | QCOMPARE(object->property("c2" ).toInt(), 13); |
1427 | |
1428 | delete object; |
1429 | } |
1430 | #endif |
1431 | |
1432 | // Using an alias |
1433 | { |
1434 | QQmlComponent component(&engine, testFileUrl(fileName: "constantsOverrideBindings.4.qml" )); |
1435 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1436 | QVERIFY(object != nullptr); |
1437 | |
1438 | QCOMPARE(object->property("c1" ).toInt(), 0); |
1439 | QCOMPARE(object->property("c3" ).toInt(), 10); |
1440 | object->setProperty(name: "c1" , value: QVariant(9)); |
1441 | QCOMPARE(object->property("c1" ).toInt(), 9); |
1442 | QCOMPARE(object->property("c3" ).toInt(), 10); |
1443 | |
1444 | delete object; |
1445 | } |
1446 | } |
1447 | |
1448 | /* |
1449 | Tests that assigning a binding to a property that already has a binding causes |
1450 | the original binding to be disabled. |
1451 | */ |
1452 | void tst_qqmlecmascript::outerBindingOverridesInnerBinding() |
1453 | { |
1454 | QQmlEngine engine; |
1455 | QQmlComponent component(&engine, |
1456 | testFileUrl(fileName: "outerBindingOverridesInnerBinding.qml" )); |
1457 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1458 | QVERIFY(object != nullptr); |
1459 | |
1460 | QCOMPARE(object->property("c1" ).toInt(), 0); |
1461 | QCOMPARE(object->property("c2" ).toInt(), 0); |
1462 | QCOMPARE(object->property("c3" ).toInt(), 0); |
1463 | |
1464 | object->setProperty(name: "c1" , value: QVariant(9)); |
1465 | QCOMPARE(object->property("c1" ).toInt(), 9); |
1466 | QCOMPARE(object->property("c2" ).toInt(), 0); |
1467 | QCOMPARE(object->property("c3" ).toInt(), 0); |
1468 | |
1469 | object->setProperty(name: "c3" , value: QVariant(8)); |
1470 | QCOMPARE(object->property("c1" ).toInt(), 9); |
1471 | QCOMPARE(object->property("c2" ).toInt(), 8); |
1472 | QCOMPARE(object->property("c3" ).toInt(), 8); |
1473 | |
1474 | delete object; |
1475 | } |
1476 | |
1477 | /* |
1478 | Access a non-existent attached object. |
1479 | |
1480 | Tests for a regression where this used to crash. |
1481 | */ |
1482 | void tst_qqmlecmascript::nonExistentAttachedObject() |
1483 | { |
1484 | QQmlEngine engine; |
1485 | QQmlComponent component(&engine, testFileUrl(fileName: "nonExistentAttachedObject.qml" )); |
1486 | |
1487 | QString warning = component.url().toString() + ":4:5: Unable to assign [undefined] to QString" ; |
1488 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
1489 | |
1490 | QObject *object = component.create(); |
1491 | QVERIFY(object != nullptr); |
1492 | |
1493 | delete object; |
1494 | } |
1495 | |
1496 | void tst_qqmlecmascript::scope() |
1497 | { |
1498 | QQmlEngine engine; |
1499 | |
1500 | { |
1501 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.qml" )); |
1502 | QObject *object = component.create(); |
1503 | QVERIFY(object != nullptr); |
1504 | |
1505 | QCOMPARE(object->property("test1" ).toInt(), 1); |
1506 | QCOMPARE(object->property("test2" ).toInt(), 2); |
1507 | QCOMPARE(object->property("test3" ).toString(), QString("1Test" )); |
1508 | QCOMPARE(object->property("test4" ).toString(), QString("2Test" )); |
1509 | QCOMPARE(object->property("test5" ).toInt(), 1); |
1510 | QCOMPARE(object->property("test6" ).toInt(), 1); |
1511 | QCOMPARE(object->property("test7" ).toInt(), 2); |
1512 | QCOMPARE(object->property("test8" ).toInt(), 2); |
1513 | QCOMPARE(object->property("test9" ).toInt(), 1); |
1514 | QCOMPARE(object->property("test10" ).toInt(), 3); |
1515 | |
1516 | delete object; |
1517 | } |
1518 | |
1519 | { |
1520 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.2.qml" )); |
1521 | QObject *object = component.create(); |
1522 | QVERIFY(object != nullptr); |
1523 | |
1524 | QCOMPARE(object->property("test1" ).toInt(), 19); |
1525 | QCOMPARE(object->property("test2" ).toInt(), 19); |
1526 | QCOMPARE(object->property("test3" ).toInt(), 14); |
1527 | QCOMPARE(object->property("test4" ).toInt(), 14); |
1528 | QCOMPARE(object->property("test5" ).toInt(), 24); |
1529 | QCOMPARE(object->property("test6" ).toInt(), 24); |
1530 | |
1531 | delete object; |
1532 | } |
1533 | |
1534 | { |
1535 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.3.qml" )); |
1536 | QObject *object = component.create(); |
1537 | QVERIFY(object != nullptr); |
1538 | |
1539 | QCOMPARE(object->property("test1" ).toBool(), true); |
1540 | QEXPECT_FAIL("" , "Properties resolvable at compile time come before the global object, which is not 100% compatible with older QML versions" , Continue); |
1541 | QCOMPARE(object->property("test2" ).toBool(), true); |
1542 | QCOMPARE(object->property("test3" ).toBool(), true); |
1543 | |
1544 | delete object; |
1545 | } |
1546 | |
1547 | // Signal argument scope |
1548 | { |
1549 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.4.qml" )); |
1550 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1551 | QVERIFY(object != nullptr); |
1552 | |
1553 | QCOMPARE(object->property("test" ).toInt(), 0); |
1554 | QCOMPARE(object->property("test2" ).toString(), QString()); |
1555 | |
1556 | emit object->argumentSignal(a: 13, b: "Argument Scope" , c: 9, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
1557 | |
1558 | QCOMPARE(object->property("test" ).toInt(), 13); |
1559 | QCOMPARE(object->property("test2" ).toString(), QString("Argument Scope" )); |
1560 | |
1561 | delete object; |
1562 | } |
1563 | |
1564 | { |
1565 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.5.qml" )); |
1566 | QObject *object = component.create(); |
1567 | QVERIFY(object != nullptr); |
1568 | |
1569 | QCOMPARE(object->property("test1" ).toBool(), true); |
1570 | QCOMPARE(object->property("test2" ).toBool(), true); |
1571 | |
1572 | delete object; |
1573 | } |
1574 | |
1575 | { |
1576 | QQmlComponent component(&engine, testFileUrl(fileName: "scope.6.qml" )); |
1577 | QObject *object = component.create(); |
1578 | QVERIFY(object != nullptr); |
1579 | |
1580 | QCOMPARE(object->property("test" ).toBool(), true); |
1581 | |
1582 | delete object; |
1583 | } |
1584 | } |
1585 | |
1586 | // In 4.7, non-library javascript files that had no imports shared the imports of their |
1587 | // importing context |
1588 | void tst_qqmlecmascript::importScope() |
1589 | { |
1590 | QQmlEngine engine; |
1591 | QQmlComponent component(&engine, testFileUrl(fileName: "importScope.qml" )); |
1592 | QObject *o = component.create(); |
1593 | QVERIFY(o != nullptr); |
1594 | |
1595 | QCOMPARE(o->property("test" ).toInt(), 240); |
1596 | |
1597 | delete o; |
1598 | } |
1599 | |
1600 | /* |
1601 | Tests that "any" type passes through a synthesized signal parameter. This |
1602 | is essentially a test of QQmlMetaType::copy() |
1603 | */ |
1604 | void tst_qqmlecmascript::signalParameterTypes() |
1605 | { |
1606 | QQmlEngine engine; |
1607 | QQmlComponent component(&engine, testFileUrl(fileName: "signalParameterTypes.qml" )); |
1608 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
1609 | QVERIFY(object != nullptr); |
1610 | |
1611 | emit object->basicSignal(); |
1612 | |
1613 | QCOMPARE(object->property("intProperty" ).toInt(), 10); |
1614 | QCOMPARE(object->property("realProperty" ).toReal(), 19.2); |
1615 | QVERIFY(object->property("colorProperty" ).value<QColor>() == QColor(255, 255, 0, 255)); |
1616 | QVERIFY(object->property("variantProperty" ) == QVariant::fromValue(QColor(255, 0, 255, 255))); |
1617 | QVERIFY(object->property("enumProperty" ) == MyQmlObject::EnumValue3); |
1618 | QVERIFY(object->property("qtEnumProperty" ) == Qt::LeftButton); |
1619 | |
1620 | emit object->qjsValueEmittingSignal(value: QJSValue()); |
1621 | QVERIFY(object->property("emittedQjsValueWasUndefined" ).toBool()); |
1622 | emit object->qjsValueEmittingSignal(value: QJSValue(42)); |
1623 | QVERIFY(!object->property("emittedQjsValueWasUndefined" ).toBool()); |
1624 | QCOMPARE(object->property("emittedQjsValueAsInt" ).value<int>(), 42); |
1625 | |
1626 | delete object; |
1627 | } |
1628 | |
1629 | /* |
1630 | Test that two JS objects for the same QObject compare as equal. |
1631 | */ |
1632 | void tst_qqmlecmascript::objectsCompareAsEqual() |
1633 | { |
1634 | QQmlEngine engine; |
1635 | QQmlComponent component(&engine, testFileUrl(fileName: "objectsCompareAsEqual.qml" )); |
1636 | QObject *object = component.create(); |
1637 | QVERIFY(object != nullptr); |
1638 | |
1639 | QCOMPARE(object->property("test1" ).toBool(), true); |
1640 | QCOMPARE(object->property("test2" ).toBool(), true); |
1641 | QCOMPARE(object->property("test3" ).toBool(), true); |
1642 | QCOMPARE(object->property("test4" ).toBool(), true); |
1643 | QCOMPARE(object->property("test5" ).toBool(), true); |
1644 | |
1645 | delete object; |
1646 | } |
1647 | |
1648 | /* |
1649 | Confirm bindings and alias properties can coexist. |
1650 | |
1651 | Tests for a regression where the binding would not reevaluate. |
1652 | */ |
1653 | void tst_qqmlecmascript::aliasPropertyAndBinding() |
1654 | { |
1655 | QQmlEngine engine; |
1656 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasPropertyAndBinding.qml" )); |
1657 | QObject *object = component.create(); |
1658 | QVERIFY(object != nullptr); |
1659 | |
1660 | QCOMPARE(object->property("c2" ).toInt(), 3); |
1661 | QCOMPARE(object->property("c3" ).toInt(), 3); |
1662 | |
1663 | object->setProperty(name: "c2" , value: QVariant(19)); |
1664 | |
1665 | QCOMPARE(object->property("c2" ).toInt(), 19); |
1666 | QCOMPARE(object->property("c3" ).toInt(), 19); |
1667 | |
1668 | delete object; |
1669 | } |
1670 | |
1671 | /* |
1672 | Ensure that we can write undefined value to an alias property, |
1673 | and that the aliased property is reset correctly if possible. |
1674 | */ |
1675 | void tst_qqmlecmascript::aliasPropertyReset() |
1676 | { |
1677 | QQmlEngine engine; |
1678 | QObject *object = nullptr; |
1679 | |
1680 | // test that a manual write (of undefined) to a resettable aliased property succeeds |
1681 | QQmlComponent c1(&engine, testFileUrl(fileName: "aliasreset/aliasPropertyReset.1.qml" )); |
1682 | object = c1.create(); |
1683 | QVERIFY(object != nullptr); |
1684 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() != 0); |
1685 | QCOMPARE(object->property("aliasIsUndefined" ), QVariant(false)); |
1686 | QMetaObject::invokeMethod(obj: object, member: "resetAliased" ); |
1687 | QVERIFY(!object->property("sourceComponentAlias" ).value<QQmlComponent*>()); |
1688 | QCOMPARE(object->property("aliasIsUndefined" ), QVariant(true)); |
1689 | delete object; |
1690 | |
1691 | // test that a manual write (of undefined) to a resettable alias property succeeds |
1692 | QQmlComponent c2(&engine, testFileUrl(fileName: "aliasreset/aliasPropertyReset.2.qml" )); |
1693 | object = c2.create(); |
1694 | QVERIFY(object != nullptr); |
1695 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() != 0); |
1696 | QCOMPARE(object->property("loaderSourceComponentIsUndefined" ), QVariant(false)); |
1697 | QMetaObject::invokeMethod(obj: object, member: "resetAlias" ); |
1698 | QVERIFY(!object->property("sourceComponentAlias" ).value<QQmlComponent*>()); |
1699 | QCOMPARE(object->property("loaderSourceComponentIsUndefined" ), QVariant(true)); |
1700 | delete object; |
1701 | |
1702 | // test that an alias to a bound property works correctly |
1703 | QQmlComponent c3(&engine, testFileUrl(fileName: "aliasreset/aliasPropertyReset.3.qml" )); |
1704 | object = c3.create(); |
1705 | QVERIFY(object != nullptr); |
1706 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() != 0); |
1707 | QCOMPARE(object->property("loaderOneSourceComponentIsUndefined" ), QVariant(false)); |
1708 | QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined" ), QVariant(false)); |
1709 | QMetaObject::invokeMethod(obj: object, member: "resetAlias" ); |
1710 | QVERIFY(!object->property("sourceComponentAlias" ).value<QQmlComponent*>()); |
1711 | QCOMPARE(object->property("loaderOneSourceComponentIsUndefined" ), QVariant(true)); |
1712 | QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined" ), QVariant(false)); |
1713 | delete object; |
1714 | |
1715 | // test that a manual write (of undefined) to a resettable alias property |
1716 | // whose aliased property's object has been deleted, does not crash. |
1717 | QQmlComponent c4(&engine, testFileUrl(fileName: "aliasreset/aliasPropertyReset.4.qml" )); |
1718 | object = c4.create(); |
1719 | QVERIFY(object != nullptr); |
1720 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() != 0); |
1721 | QObject *loader = object->findChild<QObject*>(aName: "loader" ); |
1722 | QVERIFY(loader != nullptr); |
1723 | delete loader; |
1724 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() == 0); // deletion should have caused value unset. |
1725 | QMetaObject::invokeMethod(obj: object, member: "resetAlias" ); // shouldn't crash. |
1726 | QVERIFY(!object->property("sourceComponentAlias" ).value<QQmlComponent*>()); |
1727 | QMetaObject::invokeMethod(obj: object, member: "setAlias" ); // shouldn't crash, and shouldn't change value (since it's no longer referencing anything). |
1728 | QVERIFY(!object->property("sourceComponentAlias" ).value<QQmlComponent*>()); |
1729 | delete object; |
1730 | |
1731 | // test that binding an alias property to an undefined value works correctly |
1732 | QQmlComponent c5(&engine, testFileUrl(fileName: "aliasreset/aliasPropertyReset.5.qml" )); |
1733 | object = c5.create(); |
1734 | QVERIFY(object != nullptr); |
1735 | QVERIFY(object->property("sourceComponentAlias" ).value<QQmlComponent*>() == 0); // bound to undefined value. |
1736 | delete object; |
1737 | |
1738 | // test that a manual write (of undefined) to a non-resettable property fails properly |
1739 | QUrl url = testFileUrl(fileName: "aliasreset/aliasPropertyReset.error.1.qml" ); |
1740 | QString warning1 = url.toString() + QLatin1String(":15: Error: Cannot assign [undefined] to int" ); |
1741 | QQmlComponent e1(&engine, url); |
1742 | object = e1.create(); |
1743 | QVERIFY(object != nullptr); |
1744 | QCOMPARE(object->property("intAlias" ).value<int>(), 12); |
1745 | QCOMPARE(object->property("aliasedIntIsUndefined" ), QVariant(false)); |
1746 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
1747 | QMetaObject::invokeMethod(obj: object, member: "resetAlias" ); |
1748 | QCOMPARE(object->property("intAlias" ).value<int>(), 12); |
1749 | QCOMPARE(object->property("aliasedIntIsUndefined" ), QVariant(false)); |
1750 | delete object; |
1751 | } |
1752 | |
1753 | void tst_qqmlecmascript::componentCreation_data() |
1754 | { |
1755 | QTest::addColumn<QString>(name: "method" ); |
1756 | QTest::addColumn<QString>(name: "creationError" ); |
1757 | QTest::addColumn<QString>(name: "createdParent" ); |
1758 | |
1759 | QTest::newRow(dataTag: "url" ) |
1760 | << "url" |
1761 | << "" |
1762 | << "" ; |
1763 | QTest::newRow(dataTag: "urlMode" ) |
1764 | << "urlMode" |
1765 | << "" |
1766 | << "" ; |
1767 | QTest::newRow(dataTag: "urlParent" ) |
1768 | << "urlParent" |
1769 | << "" |
1770 | << "obj" ; |
1771 | QTest::newRow(dataTag: "urlNullParent" ) |
1772 | << "urlNullParent" |
1773 | << "" |
1774 | << "null" ; |
1775 | QTest::newRow(dataTag: "urlModeParent" ) |
1776 | << "urlModeParent" |
1777 | << "" |
1778 | << "obj" ; |
1779 | QTest::newRow(dataTag: "urlModeNullParent" ) |
1780 | << "urlModeNullParent" |
1781 | << "" |
1782 | << "null" ; |
1783 | QTest::newRow(dataTag: "invalidSecondArg" ) |
1784 | << "invalidSecondArg" |
1785 | << ":40: Error: Qt.createComponent(): Invalid arguments" |
1786 | << "" ; |
1787 | QTest::newRow(dataTag: "invalidThirdArg" ) |
1788 | << "invalidThirdArg" |
1789 | << ":45: Error: Qt.createComponent(): Invalid parent object" |
1790 | << "" ; |
1791 | QTest::newRow(dataTag: "invalidMode" ) |
1792 | << "invalidMode" |
1793 | << ":50: Error: Qt.createComponent(): Invalid arguments" |
1794 | << "" ; |
1795 | } |
1796 | |
1797 | /* |
1798 | Test using createComponent to dynamically generate a component. |
1799 | */ |
1800 | void tst_qqmlecmascript::componentCreation() |
1801 | { |
1802 | QFETCH(QString, method); |
1803 | QFETCH(QString, creationError); |
1804 | QFETCH(QString, createdParent); |
1805 | |
1806 | QQmlEngine engine; |
1807 | QUrl testUrl(testFileUrl(fileName: "componentCreation.qml" )); |
1808 | |
1809 | if (!creationError.isEmpty()) { |
1810 | QString warning = testUrl.toString() + creationError; |
1811 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
1812 | } |
1813 | |
1814 | QQmlComponent component(&engine, testUrl); |
1815 | QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject*>(object: component.create())); |
1816 | QVERIFY(object != nullptr); |
1817 | |
1818 | QMetaObject::invokeMethod(obj: object.get(), member: method.toUtf8()); |
1819 | QQmlComponent *created = object->componentProperty(); |
1820 | |
1821 | if (creationError.isEmpty()) { |
1822 | QVERIFY(created); |
1823 | |
1824 | QObject *expectedParent = reinterpret_cast<QObject *>(quintptr(-1)); |
1825 | if (createdParent == QLatin1String("obj" )) { |
1826 | expectedParent = object.get(); |
1827 | } else if ((createdParent == QLatin1String("null" )) || createdParent.isEmpty()) { |
1828 | expectedParent = nullptr; |
1829 | } |
1830 | QCOMPARE(created->parent(), expectedParent); |
1831 | } |
1832 | } |
1833 | |
1834 | void tst_qqmlecmascript::dynamicCreation_data() |
1835 | { |
1836 | QTest::addColumn<QString>(name: "method" ); |
1837 | QTest::addColumn<QString>(name: "createdName" ); |
1838 | |
1839 | QTest::newRow(dataTag: "One" ) << "createOne" << "objectOne" ; |
1840 | QTest::newRow(dataTag: "Two" ) << "createTwo" << "objectTwo" ; |
1841 | QTest::newRow(dataTag: "Three" ) << "createThree" << "objectThree" ; |
1842 | } |
1843 | |
1844 | /* |
1845 | Test using createQmlObject to dynamically generate an item |
1846 | Also using createComponent is tested. |
1847 | */ |
1848 | void tst_qqmlecmascript::dynamicCreation() |
1849 | { |
1850 | QFETCH(QString, method); |
1851 | QFETCH(QString, createdName); |
1852 | |
1853 | QQmlEngine engine; |
1854 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicCreation.qml" )); |
1855 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
1856 | QVERIFY(object != nullptr); |
1857 | |
1858 | QMetaObject::invokeMethod(obj: object, member: method.toUtf8()); |
1859 | QObject *created = object->objectProperty(); |
1860 | QVERIFY(created); |
1861 | QCOMPARE(created->objectName(), createdName); |
1862 | |
1863 | delete object; |
1864 | } |
1865 | |
1866 | /* |
1867 | Tests the destroy function |
1868 | */ |
1869 | void tst_qqmlecmascript::dynamicDestruction() |
1870 | { |
1871 | QQmlEngine engine; |
1872 | |
1873 | { |
1874 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicDeletion.qml" )); |
1875 | QPointer<MyQmlObject> object = qobject_cast<MyQmlObject*>(object: component.create()); |
1876 | QVERIFY(object != nullptr); |
1877 | QPointer<QObject> createdQmlObject = nullptr; |
1878 | |
1879 | QMetaObject::invokeMethod(obj: object, member: "create" ); |
1880 | createdQmlObject = object->objectProperty(); |
1881 | QVERIFY(createdQmlObject); |
1882 | QCOMPARE(createdQmlObject->objectName(), QString("emptyObject" )); |
1883 | |
1884 | QMetaObject::invokeMethod(obj: object, member: "killOther" ); |
1885 | QVERIFY(createdQmlObject); |
1886 | |
1887 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
1888 | QCoreApplication::processEvents(); |
1889 | QVERIFY(createdQmlObject); |
1890 | for (int ii = 0; createdQmlObject && ii < 50; ++ii) { // After 5 seconds we should give up |
1891 | if (createdQmlObject) { |
1892 | QTest::qWait(ms: 100); |
1893 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
1894 | QCoreApplication::processEvents(); |
1895 | } |
1896 | } |
1897 | QVERIFY(!createdQmlObject); |
1898 | |
1899 | QQmlEngine::setObjectOwnership(object, QQmlEngine::JavaScriptOwnership); |
1900 | QMetaObject::invokeMethod(obj: object, member: "killMe" ); |
1901 | QVERIFY(object); |
1902 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
1903 | QCoreApplication::processEvents(); |
1904 | QVERIFY(!object); |
1905 | } |
1906 | |
1907 | { |
1908 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicDeletion.2.qml" )); |
1909 | QObject *o = component.create(); |
1910 | QVERIFY(o != nullptr); |
1911 | |
1912 | QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty" ))); |
1913 | |
1914 | QMetaObject::invokeMethod(obj: o, member: "create" ); |
1915 | |
1916 | QVERIFY(qvariant_cast<QObject*>(o->property("objectProperty" )) != 0); |
1917 | |
1918 | QMetaObject::invokeMethod(obj: o, member: "destroy" ); |
1919 | |
1920 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
1921 | QCoreApplication::processEvents(); |
1922 | |
1923 | QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty" ))); |
1924 | |
1925 | delete o; |
1926 | } |
1927 | |
1928 | { |
1929 | // QTBUG-23451 |
1930 | QPointer<QObject> createdQmlObject = nullptr; |
1931 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicDeletion.3.qml" )); |
1932 | QObject *o = component.create(); |
1933 | QVERIFY(o != nullptr); |
1934 | QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty" ))); |
1935 | QMetaObject::invokeMethod(obj: o, member: "create" ); |
1936 | createdQmlObject = qvariant_cast<QObject*>(v: o->property(name: "objectProperty" )); |
1937 | QVERIFY(createdQmlObject); |
1938 | QMetaObject::invokeMethod(obj: o, member: "destroy" ); |
1939 | QCOMPARE(qvariant_cast<bool>(o->property("test" )), false); |
1940 | for (int ii = 0; createdQmlObject && ii < 50; ++ii) { // After 5 seconds we should give up |
1941 | QTest::qWait(ms: 100); |
1942 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
1943 | QCoreApplication::processEvents(); |
1944 | } |
1945 | QVERIFY(!qvariant_cast<QObject*>(o->property("objectProperty" ))); |
1946 | QCOMPARE(qvariant_cast<bool>(o->property("test" )), true); |
1947 | delete o; |
1948 | } |
1949 | } |
1950 | |
1951 | /* |
1952 | tests that id.toString() works |
1953 | */ |
1954 | void tst_qqmlecmascript::objectToString() |
1955 | { |
1956 | QQmlEngine engine; |
1957 | QQmlComponent component(&engine, testFileUrl(fileName: "qmlToString.qml" )); |
1958 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
1959 | QVERIFY(object != nullptr); |
1960 | QMetaObject::invokeMethod(obj: object, member: "testToString" ); |
1961 | QVERIFY(object->stringProperty().startsWith("MyQmlObject_QML_" )); |
1962 | QVERIFY(object->stringProperty().endsWith(", \"objName\")" )); |
1963 | |
1964 | delete object; |
1965 | } |
1966 | |
1967 | /* |
1968 | tests that id.hasOwnProperty() works |
1969 | */ |
1970 | void tst_qqmlecmascript::objectHasOwnProperty() |
1971 | { |
1972 | QUrl url = testFileUrl(fileName: "qmlHasOwnProperty.qml" ); |
1973 | QString warning1 = url.toString() + ":59: TypeError: Cannot call method 'hasOwnProperty' of undefined" ; |
1974 | QString warning2 = url.toString() + ":64: TypeError: Cannot call method 'hasOwnProperty' of undefined" ; |
1975 | QString warning3 = url.toString() + ":69: TypeError: Cannot call method 'hasOwnProperty' of undefined" ; |
1976 | |
1977 | QQmlEngine engine; |
1978 | QQmlComponent component(&engine, url); |
1979 | QObject *object = component.create(); |
1980 | QVERIFY(object != nullptr); |
1981 | |
1982 | // test QObjects in QML |
1983 | QMetaObject::invokeMethod(obj: object, member: "testHasOwnPropertySuccess" ); |
1984 | QVERIFY(object->property("result" ).value<bool>()); |
1985 | QMetaObject::invokeMethod(obj: object, member: "testHasOwnPropertyFailure" ); |
1986 | QVERIFY(!object->property("result" ).value<bool>()); |
1987 | |
1988 | // now test other types in QML |
1989 | QObject *child = object->findChild<QObject*>(aName: "typeObj" ); |
1990 | QVERIFY(child != nullptr); |
1991 | QMetaObject::invokeMethod(obj: child, member: "testHasOwnPropertySuccess" ); |
1992 | QCOMPARE(child->property("valueTypeHasOwnProperty" ).toBool(), true); |
1993 | QCOMPARE(child->property("valueTypeHasOwnProperty2" ).toBool(), true); |
1994 | QCOMPARE(child->property("variantTypeHasOwnProperty" ).toBool(), true); |
1995 | QCOMPARE(child->property("stringTypeHasOwnProperty" ).toBool(), true); |
1996 | QCOMPARE(child->property("listTypeHasOwnProperty" ).toBool(), true); |
1997 | QCOMPARE(child->property("emptyListTypeHasOwnProperty" ).toBool(), true); |
1998 | QCOMPARE(child->property("enumTypeHasOwnProperty" ).toBool(), true); |
1999 | QCOMPARE(child->property("typenameHasOwnProperty" ).toBool(), true); |
2000 | QCOMPARE(child->property("typenameHasOwnProperty2" ).toBool(), true); |
2001 | QCOMPARE(child->property("singletonTypeTypeHasOwnProperty" ).toBool(), true); |
2002 | QCOMPARE(child->property("singletonTypePropertyTypeHasOwnProperty" ).toBool(), true); |
2003 | |
2004 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
2005 | QMetaObject::invokeMethod(obj: child, member: "testHasOwnPropertyFailureOne" ); |
2006 | QCOMPARE(child->property("enumNonValueHasOwnProperty" ).toBool(), false); |
2007 | QTest::ignoreMessage(type: QtWarningMsg, message: warning2.toLatin1().constData()); |
2008 | QMetaObject::invokeMethod(obj: child, member: "testHasOwnPropertyFailureTwo" ); |
2009 | QCOMPARE(child->property("singletonTypeNonPropertyHasOwnProperty" ).toBool(), false); |
2010 | QTest::ignoreMessage(type: QtWarningMsg, message: warning3.toLatin1().constData()); |
2011 | QMetaObject::invokeMethod(obj: child, member: "testHasOwnPropertyFailureThree" ); |
2012 | QCOMPARE(child->property("listAtInvalidHasOwnProperty" ).toBool(), false); |
2013 | |
2014 | delete object; |
2015 | } |
2016 | |
2017 | /* |
2018 | Tests bindings that indirectly cause their own deletion work. |
2019 | |
2020 | This test is best run under valgrind to ensure no invalid memory access occur. |
2021 | */ |
2022 | void tst_qqmlecmascript::selfDeletingBinding() |
2023 | { |
2024 | QQmlEngine engine; |
2025 | |
2026 | { |
2027 | QQmlComponent component(&engine, testFileUrl(fileName: "selfDeletingBinding.qml" )); |
2028 | QObject *object = component.create(); |
2029 | QVERIFY(object != nullptr); |
2030 | object->setProperty(name: "triggerDelete" , value: true); |
2031 | delete object; |
2032 | } |
2033 | |
2034 | { |
2035 | QQmlComponent component(&engine, testFileUrl(fileName: "selfDeletingBinding.2.qml" )); |
2036 | QObject *object = component.create(); |
2037 | QVERIFY(object != nullptr); |
2038 | object->setProperty(name: "triggerDelete" , value: true); |
2039 | delete object; |
2040 | } |
2041 | } |
2042 | |
2043 | /* |
2044 | Test that extended object properties can be accessed. |
2045 | |
2046 | This test a regression where this used to crash. The issue was specificially |
2047 | for extended objects that did not include a synthesized meta object (so non-root |
2048 | and no synthesiszed properties). |
2049 | */ |
2050 | void tst_qqmlecmascript::extendedObjectPropertyLookup() |
2051 | { |
2052 | QQmlEngine engine; |
2053 | QQmlComponent component(&engine, testFileUrl(fileName: "extendedObjectPropertyLookup.qml" )); |
2054 | QObject *object = component.create(); |
2055 | QVERIFY(object != nullptr); |
2056 | delete object; |
2057 | } |
2058 | |
2059 | /* |
2060 | Test that extended object properties can be accessed correctly. |
2061 | */ |
2062 | void tst_qqmlecmascript::extendedObjectPropertyLookup2() |
2063 | { |
2064 | QQmlEngine engine; |
2065 | QQmlComponent component(&engine, testFileUrl(fileName: "extendedObjectPropertyLookup2.qml" )); |
2066 | QObject *object = component.create(); |
2067 | QVERIFY(object != nullptr); |
2068 | |
2069 | QVariant returnValue; |
2070 | QVERIFY(QMetaObject::invokeMethod(object, "getValue" , Q_RETURN_ARG(QVariant, returnValue))); |
2071 | QCOMPARE(returnValue.toInt(), 42); |
2072 | |
2073 | delete object; |
2074 | } |
2075 | |
2076 | /* |
2077 | Test failure when trying to create and uncreatable extended type object. |
2078 | */ |
2079 | void tst_qqmlecmascript::uncreatableExtendedObjectFailureCheck() |
2080 | { |
2081 | QQmlEngine engine; |
2082 | QQmlComponent component(&engine, testFileUrl(fileName: "uncreatableExtendedObjectFailureCheck.qml" )); |
2083 | |
2084 | QObject *object = component.create(); |
2085 | QVERIFY(!object); |
2086 | } |
2087 | |
2088 | /* |
2089 | Test that an subclass of an uncreatable extended object contains all the required properties. |
2090 | */ |
2091 | void tst_qqmlecmascript::extendedObjectPropertyLookup3() |
2092 | { |
2093 | QQmlEngine engine; |
2094 | QQmlComponent component(&engine, testFileUrl(fileName: "extendedObjectPropertyLookup3.qml" )); |
2095 | |
2096 | QObject *object = component.create(); |
2097 | QVERIFY(object != nullptr); |
2098 | |
2099 | QVariant returnValue; |
2100 | QVERIFY(QMetaObject::invokeMethod(object, "getAbstractProperty" , Q_RETURN_ARG(QVariant, returnValue))); |
2101 | QCOMPARE(returnValue.toInt(), -1); |
2102 | QVERIFY(QMetaObject::invokeMethod(object, "getImplementedProperty" , Q_RETURN_ARG(QVariant, returnValue))); |
2103 | QCOMPARE(returnValue.toInt(), 883); |
2104 | QVERIFY(QMetaObject::invokeMethod(object, "getExtendedProperty" , Q_RETURN_ARG(QVariant, returnValue))); |
2105 | QCOMPARE(returnValue.toInt(), 42); |
2106 | |
2107 | delete object; |
2108 | } |
2109 | /* |
2110 | Test file/lineNumbers for binding/Script errors. |
2111 | */ |
2112 | void tst_qqmlecmascript::scriptErrors() |
2113 | { |
2114 | QQmlEngine engine; |
2115 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptErrors.qml" )); |
2116 | QString url = component.url().toString(); |
2117 | |
2118 | QString warning1 = url.left(n: url.length() - 3) + "js:2: Error: Invalid write to global property \"a\"" ; |
2119 | QString warning2 = url + ":5: ReferenceError: a is not defined" ; |
2120 | QString warning3 = url.left(n: url.length() - 3) + "js:4: Error: Invalid write to global property \"a\"" ; |
2121 | QString warning4 = url + ":13: ReferenceError: a is not defined" ; |
2122 | QString warning5 = url + ":11: ReferenceError: a is not defined" ; |
2123 | QString warning6 = url + ":10:5: Unable to assign [undefined] to int" ; |
2124 | QString warning7 = url + ":15: TypeError: Cannot assign to read-only property \"trueProperty\"" ; |
2125 | QString warning8 = url + ":16: Error: Cannot assign to non-existent property \"fakeProperty\"" ; |
2126 | |
2127 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
2128 | QTest::ignoreMessage(type: QtWarningMsg, message: warning2.toLatin1().constData()); |
2129 | QTest::ignoreMessage(type: QtWarningMsg, message: warning3.toLatin1().constData()); |
2130 | QTest::ignoreMessage(type: QtWarningMsg, message: warning5.toLatin1().constData()); |
2131 | QTest::ignoreMessage(type: QtWarningMsg, message: warning6.toLatin1().constData()); |
2132 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
2133 | QVERIFY(object != nullptr); |
2134 | |
2135 | QTest::ignoreMessage(type: QtWarningMsg, message: warning4.toLatin1().constData()); |
2136 | emit object->basicSignal(); |
2137 | |
2138 | QTest::ignoreMessage(type: QtWarningMsg, message: warning7.toLatin1().constData()); |
2139 | emit object->anotherBasicSignal(); |
2140 | |
2141 | QTest::ignoreMessage(type: QtWarningMsg, message: warning8.toLatin1().constData()); |
2142 | emit object->thirdBasicSignal(); |
2143 | |
2144 | delete object; |
2145 | } |
2146 | |
2147 | /* |
2148 | Test file/lineNumbers for inline functions. |
2149 | */ |
2150 | void tst_qqmlecmascript::functionErrors() |
2151 | { |
2152 | QQmlEngine engine; |
2153 | QQmlComponent component(&engine, testFileUrl(fileName: "functionErrors.qml" )); |
2154 | QString url = component.url().toString(); |
2155 | |
2156 | QString warning = url + ":5: Error: Invalid write to global property \"a\"" ; |
2157 | |
2158 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
2159 | |
2160 | QObject *object = component.create(); |
2161 | QVERIFY(object != nullptr); |
2162 | delete object; |
2163 | |
2164 | // test that if an exception occurs while invoking js function from cpp, it is reported as expected. |
2165 | QQmlComponent componentTwo(&engine, testFileUrl(fileName: "scarceResourceFunctionFail.var.qml" )); |
2166 | url = componentTwo.url().toString(); |
2167 | object = componentTwo.create(); |
2168 | QVERIFY(object != nullptr); |
2169 | |
2170 | QObject *resource = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
2171 | warning = url + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function" ); |
2172 | warning = warning.arg(a: QString::number(quintptr(resource), base: 16)); |
2173 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); // we expect a meaningful warning to be printed. |
2174 | QMetaObject::invokeMethod(obj: object, member: "retrieveScarceResource" ); |
2175 | delete object; |
2176 | } |
2177 | |
2178 | /* |
2179 | Test various errors that can occur when assigning a property from script |
2180 | */ |
2181 | void tst_qqmlecmascript::propertyAssignmentErrors() |
2182 | { |
2183 | QQmlEngine engine; |
2184 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyAssignmentErrors.qml" )); |
2185 | |
2186 | QString url = component.url().toString(); |
2187 | |
2188 | QObject *object = component.create(); |
2189 | QVERIFY(object != nullptr); |
2190 | |
2191 | QCOMPARE(object->property("test1" ).toBool(), true); |
2192 | QCOMPARE(object->property("test2" ).toBool(), true); |
2193 | |
2194 | delete object; |
2195 | } |
2196 | |
2197 | /* |
2198 | Test bindings still work when the reeval is triggered from within |
2199 | a signal script. |
2200 | */ |
2201 | void tst_qqmlecmascript::signalTriggeredBindings() |
2202 | { |
2203 | QQmlEngine engine; |
2204 | QQmlComponent component(&engine, testFileUrl(fileName: "signalTriggeredBindings.qml" )); |
2205 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2206 | QVERIFY(object != nullptr); |
2207 | |
2208 | QCOMPARE(object->property("base" ).toReal(), 50.); |
2209 | QCOMPARE(object->property("test1" ).toReal(), 50.); |
2210 | QCOMPARE(object->property("test2" ).toReal(), 50.); |
2211 | |
2212 | object->basicSignal(); |
2213 | |
2214 | QCOMPARE(object->property("base" ).toReal(), 200.); |
2215 | QCOMPARE(object->property("test1" ).toReal(), 200.); |
2216 | QCOMPARE(object->property("test2" ).toReal(), 200.); |
2217 | |
2218 | object->argumentSignal(a: 10, b: QString(), c: 10, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
2219 | |
2220 | QCOMPARE(object->property("base" ).toReal(), 400.); |
2221 | QCOMPARE(object->property("test1" ).toReal(), 400.); |
2222 | QCOMPARE(object->property("test2" ).toReal(), 400.); |
2223 | |
2224 | delete object; |
2225 | } |
2226 | |
2227 | /* |
2228 | Test that list properties can be iterated from ECMAScript |
2229 | */ |
2230 | void tst_qqmlecmascript::listProperties() |
2231 | { |
2232 | QQmlEngine engine; |
2233 | QQmlComponent component(&engine, testFileUrl(fileName: "listProperties.qml" )); |
2234 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2235 | QVERIFY(object != nullptr); |
2236 | |
2237 | QCOMPARE(object->property("test1" ).toInt(), 21); |
2238 | QCOMPARE(object->property("test2" ).toInt(), 2); |
2239 | QCOMPARE(object->property("test3" ).toBool(), true); |
2240 | QCOMPARE(object->property("test4" ).toBool(), true); |
2241 | |
2242 | delete object; |
2243 | } |
2244 | |
2245 | void tst_qqmlecmascript::exceptionClearsOnReeval() |
2246 | { |
2247 | QQmlEngine engine; |
2248 | QQmlComponent component(&engine, testFileUrl(fileName: "exceptionClearsOnReeval.qml" )); |
2249 | QString url = component.url().toString(); |
2250 | |
2251 | QString warning = url + ":4: TypeError: Cannot read property 'objectProperty' of null" ; |
2252 | |
2253 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
2254 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2255 | QVERIFY(object != nullptr); |
2256 | |
2257 | QCOMPARE(object->property("test" ).toBool(), false); |
2258 | |
2259 | MyQmlObject object2; |
2260 | MyQmlObject object3; |
2261 | object2.setObjectProperty(&object3); |
2262 | object->setObjectProperty(&object2); |
2263 | |
2264 | QCOMPARE(object->property("test" ).toBool(), true); |
2265 | |
2266 | delete object; |
2267 | } |
2268 | |
2269 | void tst_qqmlecmascript::exceptionSlotProducesWarning() |
2270 | { |
2271 | QQmlEngine engine; |
2272 | QQmlComponent component(&engine, testFileUrl(fileName: "exceptionProducesWarning.qml" )); |
2273 | QString url = component.url().toString(); |
2274 | |
2275 | QString warning = component.url().toString() + ":6: Error: JS exception" ; |
2276 | |
2277 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
2278 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2279 | QVERIFY(object != nullptr); |
2280 | delete object; |
2281 | } |
2282 | |
2283 | void tst_qqmlecmascript::exceptionBindingProducesWarning() |
2284 | { |
2285 | QQmlEngine engine; |
2286 | QQmlComponent component(&engine, testFileUrl(fileName: "exceptionProducesWarning2.qml" )); |
2287 | QString url = component.url().toString(); |
2288 | |
2289 | QString warning = component.url().toString() + ":5: Error: JS exception" ; |
2290 | |
2291 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
2292 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2293 | QVERIFY(object != nullptr); |
2294 | delete object; |
2295 | } |
2296 | |
2297 | void tst_qqmlecmascript::compileInvalidBinding() |
2298 | { |
2299 | // QTBUG-23387: ensure that invalid bindings don't cause a crash. |
2300 | QQmlEngine engine; |
2301 | QQmlComponent component(&engine, testFileUrl(fileName: "v8bindingException.qml" )); |
2302 | QObject *object = component.create(); |
2303 | QVERIFY(object != nullptr); |
2304 | delete object; |
2305 | } |
2306 | |
2307 | // Check that transient binding errors are not displayed |
2308 | void tst_qqmlecmascript::transientErrors() |
2309 | { |
2310 | QQmlEngine engine; |
2311 | |
2312 | { |
2313 | QQmlComponent component(&engine, testFileUrl(fileName: "transientErrors.qml" )); |
2314 | |
2315 | QQmlTestMessageHandler messageHandler; |
2316 | |
2317 | QObject *object = component.create(); |
2318 | QVERIFY(object != nullptr); |
2319 | |
2320 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
2321 | |
2322 | delete object; |
2323 | } |
2324 | |
2325 | // One binding erroring multiple times, but then resolving |
2326 | { |
2327 | QQmlComponent component(&engine, testFileUrl(fileName: "transientErrors.2.qml" )); |
2328 | |
2329 | QQmlTestMessageHandler messageHandler; |
2330 | |
2331 | QObject *object = component.create(); |
2332 | QVERIFY(object != nullptr); |
2333 | |
2334 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
2335 | |
2336 | delete object; |
2337 | } |
2338 | } |
2339 | |
2340 | // Check that errors during shutdown are minimized |
2341 | void tst_qqmlecmascript::shutdownErrors() |
2342 | { |
2343 | QQmlEngine engine; |
2344 | QQmlComponent component(&engine, testFileUrl(fileName: "shutdownErrors.qml" )); |
2345 | QObject *object = component.create(); |
2346 | QVERIFY(object != nullptr); |
2347 | |
2348 | QQmlTestMessageHandler messageHandler; |
2349 | |
2350 | delete object; |
2351 | |
2352 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
2353 | } |
2354 | |
2355 | void tst_qqmlecmascript::compositePropertyType() |
2356 | { |
2357 | QQmlEngine engine; |
2358 | QQmlComponent component(&engine, testFileUrl(fileName: "compositePropertyType.qml" )); |
2359 | |
2360 | QTest::ignoreMessage(type: QtDebugMsg, message: "hello world" ); |
2361 | QObject *object = qobject_cast<QObject *>(object: component.create()); |
2362 | delete object; |
2363 | } |
2364 | |
2365 | // QTBUG-5759 |
2366 | void tst_qqmlecmascript::jsObject() |
2367 | { |
2368 | QQmlEngine engine; |
2369 | QQmlComponent component(&engine, testFileUrl(fileName: "jsObject.qml" )); |
2370 | QObject *object = component.create(); |
2371 | QVERIFY(object != nullptr); |
2372 | |
2373 | QCOMPARE(object->property("test" ).toInt(), 92); |
2374 | |
2375 | delete object; |
2376 | } |
2377 | |
2378 | void tst_qqmlecmascript::undefinedResetsProperty() |
2379 | { |
2380 | QQmlEngine engine; |
2381 | |
2382 | { |
2383 | QQmlComponent component(&engine, testFileUrl(fileName: "undefinedResetsProperty.qml" )); |
2384 | QObject *object = component.create(); |
2385 | QVERIFY(object != nullptr); |
2386 | |
2387 | QCOMPARE(object->property("resettableProperty" ).toInt(), 92); |
2388 | |
2389 | object->setProperty(name: "setUndefined" , value: true); |
2390 | |
2391 | QCOMPARE(object->property("resettableProperty" ).toInt(), 13); |
2392 | |
2393 | object->setProperty(name: "setUndefined" , value: false); |
2394 | |
2395 | QCOMPARE(object->property("resettableProperty" ).toInt(), 92); |
2396 | |
2397 | delete object; |
2398 | } |
2399 | { |
2400 | QQmlComponent component(&engine, testFileUrl(fileName: "undefinedResetsProperty.2.qml" )); |
2401 | QObject *object = component.create(); |
2402 | QVERIFY(object != nullptr); |
2403 | |
2404 | QCOMPARE(object->property("resettableProperty" ).toInt(), 19); |
2405 | |
2406 | QMetaObject::invokeMethod(obj: object, member: "doReset" ); |
2407 | |
2408 | QCOMPARE(object->property("resettableProperty" ).toInt(), 13); |
2409 | |
2410 | delete object; |
2411 | } |
2412 | } |
2413 | |
2414 | // Aliases to variant properties should work |
2415 | void tst_qqmlecmascript::qtbug_22464() |
2416 | { |
2417 | QQmlEngine engine; |
2418 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_22464.qml" )); |
2419 | QObject *object = component.create(); |
2420 | QVERIFY(object != nullptr); |
2421 | |
2422 | QCOMPARE(object->property("test" ).toBool(), true); |
2423 | |
2424 | delete object; |
2425 | } |
2426 | |
2427 | void tst_qqmlecmascript::qtbug_21580() |
2428 | { |
2429 | QQmlEngine engine; |
2430 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_21580.qml" )); |
2431 | |
2432 | QObject *object = component.create(); |
2433 | QVERIFY(object != nullptr); |
2434 | |
2435 | QCOMPARE(object->property("test" ).toBool(), true); |
2436 | |
2437 | delete object; |
2438 | } |
2439 | |
2440 | // Causes a v8 binding, but not all v8 bindings to be destroyed during evaluation |
2441 | void tst_qqmlecmascript::singleV8BindingDestroyedDuringEvaluation() |
2442 | { |
2443 | QQmlEngine engine; |
2444 | QQmlComponent component(&engine, testFileUrl(fileName: "singleV8BindingDestroyedDuringEvaluation.qml" )); |
2445 | |
2446 | QObject *object = component.create(); |
2447 | QVERIFY(object != nullptr); |
2448 | delete object; |
2449 | } |
2450 | |
2451 | // QTBUG-6781 |
2452 | void tst_qqmlecmascript::bug1() |
2453 | { |
2454 | QQmlEngine engine; |
2455 | QQmlComponent component(&engine, testFileUrl(fileName: "bug.1.qml" )); |
2456 | QObject *object = component.create(); |
2457 | QVERIFY(object != nullptr); |
2458 | |
2459 | QCOMPARE(object->property("test" ).toInt(), 14); |
2460 | |
2461 | object->setProperty(name: "a" , value: 11); |
2462 | |
2463 | QCOMPARE(object->property("test" ).toInt(), 3); |
2464 | |
2465 | object->setProperty(name: "b" , value: true); |
2466 | |
2467 | QCOMPARE(object->property("test" ).toInt(), 9); |
2468 | |
2469 | delete object; |
2470 | } |
2471 | |
2472 | #ifndef QT_NO_WIDGETS |
2473 | void tst_qqmlecmascript::bug2() |
2474 | { |
2475 | QQmlEngine engine; |
2476 | QQmlComponent component(&engine); |
2477 | component.setData("import Qt.test 1.0;\nQPlainTextEdit { width: 100 }" , baseUrl: QUrl()); |
2478 | |
2479 | QObject *object = component.create(); |
2480 | QVERIFY(object != nullptr); |
2481 | |
2482 | delete object; |
2483 | } |
2484 | #endif |
2485 | |
2486 | // Don't crash in createObject when the component has errors. |
2487 | void tst_qqmlecmascript::dynamicCreationCrash() |
2488 | { |
2489 | QQmlEngine engine; |
2490 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicCreation.qml" )); |
2491 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2492 | QVERIFY(object != nullptr); |
2493 | |
2494 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
2495 | QMetaObject::invokeMethod(obj: object, member: "dontCrash" ); |
2496 | QObject *created = object->objectProperty(); |
2497 | QVERIFY(!created); |
2498 | |
2499 | delete object; |
2500 | } |
2501 | |
2502 | // ownership transferred to JS, ensure that GC runs the dtor |
2503 | void tst_qqmlecmascript::dynamicCreationOwnership() |
2504 | { |
2505 | int dtorCount = 0; |
2506 | int expectedDtorCount = 1; // start at 1 since we expect mdcdo to dtor too. |
2507 | |
2508 | // allow the engine to go out of scope too. |
2509 | { |
2510 | QQmlEngine dcoEngine; |
2511 | QQmlComponent component(&dcoEngine, testFileUrl(fileName: "dynamicCreationOwnership.qml" )); |
2512 | QObject *object = component.create(); |
2513 | QVERIFY(object != nullptr); |
2514 | MyDynamicCreationDestructionObject *mdcdo = object->findChild<MyDynamicCreationDestructionObject*>(aName: "mdcdo" ); |
2515 | QVERIFY(mdcdo != nullptr); |
2516 | mdcdo->setDtorCount(&dtorCount); |
2517 | |
2518 | for (int i = 1; i < 105; ++i, ++expectedDtorCount) { |
2519 | QMetaObject::invokeMethod(obj: object, member: "dynamicallyCreateJsOwnedObject" ); |
2520 | if (i % 90 == 0) { |
2521 | // we do this once manually, but it should be done automatically |
2522 | // when the engine goes out of scope (since it should gc in dtor) |
2523 | QMetaObject::invokeMethod(obj: object, member: "performGc" ); |
2524 | } |
2525 | if (i % 10 == 0) { |
2526 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
2527 | QCoreApplication::processEvents(); |
2528 | } |
2529 | } |
2530 | |
2531 | delete object; |
2532 | } |
2533 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
2534 | QCoreApplication::processEvents(); |
2535 | QCOMPARE(dtorCount, expectedDtorCount); |
2536 | } |
2537 | |
2538 | void tst_qqmlecmascript::regExpBug() |
2539 | { |
2540 | QQmlEngine engine; |
2541 | |
2542 | //QTBUG-9367 |
2543 | { |
2544 | QQmlComponent component(&engine, testFileUrl(fileName: "regExp.qml" )); |
2545 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2546 | QVERIFY(object != nullptr); |
2547 | QCOMPARE(object->regExp().pattern(), QLatin1String("[a-zA-z]" )); |
2548 | delete object; |
2549 | } |
2550 | |
2551 | { |
2552 | QQmlComponent component(&engine, testFileUrl(fileName: "regularExpression.qml" )); |
2553 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
2554 | QVERIFY(!object.isNull()); |
2555 | QCOMPARE(object->regularExpression().pattern(), QLatin1String("[a-zA-z]" )); |
2556 | } |
2557 | |
2558 | //QTBUG-23068 |
2559 | { |
2560 | QString err = QString(QLatin1String("%1:6 Invalid property assignment: regular expression expected; use /pattern/ syntax\n" )).arg(a: testFileUrl(fileName: "regExp.2.qml" ).toString()); |
2561 | QQmlComponent component(&engine, testFileUrl(fileName: "regExp.2.qml" )); |
2562 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
2563 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2564 | QVERIFY(!object); |
2565 | QCOMPARE(component.errorString(), err); |
2566 | } |
2567 | |
2568 | { |
2569 | const QString err = QString::fromLatin1(str: "%1:6 Invalid property assignment: " |
2570 | "regular expression expected; " |
2571 | "use /pattern/ syntax\n" ) |
2572 | .arg(a: testFileUrl(fileName: "regularExpression.2.qml" ).toString()); |
2573 | QQmlComponent component(&engine, testFileUrl(fileName: "regularExpression.2.qml" )); |
2574 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
2575 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create()); |
2576 | QVERIFY(!object); |
2577 | QCOMPARE(component.errorString(), err); |
2578 | } |
2579 | } |
2580 | |
2581 | static inline bool evaluate_error(QV4::ExecutionEngine *v4, const QV4::Value &o, const char *source) |
2582 | { |
2583 | QString functionSource = QLatin1String("(function(object) { return " ) + |
2584 | QLatin1String(source) + QLatin1String(" })" ); |
2585 | |
2586 | QV4::Scope scope(v4); |
2587 | QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource); |
2588 | program.inheritContext = true; |
2589 | |
2590 | QV4::ScopedFunctionObject function(scope, program.run()); |
2591 | if (scope.engine->hasException) { |
2592 | scope.engine->catchException(); |
2593 | return true; |
2594 | } |
2595 | QV4::JSCallData jsCallData(scope, 1); |
2596 | jsCallData->args[0] = o; |
2597 | *jsCallData->thisObject = v4->global(); |
2598 | function->call(data: jsCallData); |
2599 | if (scope.engine->hasException) { |
2600 | scope.engine->catchException(); |
2601 | return true; |
2602 | } |
2603 | return false; |
2604 | } |
2605 | |
2606 | static inline bool evaluate_value(QV4::ExecutionEngine *v4, const QV4::Value &o, |
2607 | const char *source, const QV4::Value &result) |
2608 | { |
2609 | QString functionSource = QLatin1String("(function(object) { return " ) + |
2610 | QLatin1String(source) + QLatin1String(" })" ); |
2611 | |
2612 | QV4::Scope scope(v4); |
2613 | QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource); |
2614 | program.inheritContext = true; |
2615 | |
2616 | QV4::ScopedFunctionObject function(scope, program.run()); |
2617 | if (scope.engine->hasException) { |
2618 | scope.engine->catchException(); |
2619 | return false; |
2620 | } |
2621 | if (!function) |
2622 | return false; |
2623 | |
2624 | QV4::ScopedValue value(scope); |
2625 | QV4::JSCallData jsCallData(scope, 1); |
2626 | jsCallData->args[0] = o; |
2627 | *jsCallData->thisObject = v4->global(); |
2628 | value = function->call(data: jsCallData); |
2629 | if (scope.engine->hasException) { |
2630 | scope.engine->catchException(); |
2631 | return false; |
2632 | } |
2633 | return QV4::Runtime::StrictEqual::call(value, result); |
2634 | } |
2635 | |
2636 | static inline QV4::ReturnedValue evaluate(QV4::ExecutionEngine *v4, const QV4::Value &o, |
2637 | const char *source) |
2638 | { |
2639 | QString functionSource = QLatin1String("(function(object) { return " ) + |
2640 | QLatin1String(source) + QLatin1String(" })" ); |
2641 | |
2642 | QV4::Scope scope(v4); |
2643 | |
2644 | QV4::Script program(QV4::ScopedContext(scope, scope.engine->rootContext()), QV4::Compiler::ContextType::Eval, functionSource); |
2645 | program.inheritContext = true; |
2646 | |
2647 | QV4::ScopedFunctionObject function(scope, program.run()); |
2648 | if (scope.engine->hasException) { |
2649 | scope.engine->catchException(); |
2650 | return QV4::Encode::undefined(); |
2651 | } |
2652 | if (!function) |
2653 | return QV4::Encode::undefined(); |
2654 | QV4::JSCallData jsCallData(scope, 1); |
2655 | jsCallData->args[0] = o; |
2656 | *jsCallData->thisObject = v4->global(); |
2657 | QV4::ScopedValue result(scope, function->call(data: jsCallData)); |
2658 | if (scope.engine->hasException) { |
2659 | scope.engine->catchException(); |
2660 | return QV4::Encode::undefined(); |
2661 | } |
2662 | return result->asReturnedValue(); |
2663 | } |
2664 | |
2665 | #define EVALUATE_ERROR(source) evaluate_error(engine, object, source) |
2666 | #define EVALUATE_VALUE(source, result) evaluate_value(engine, object, source, result) |
2667 | #define EVALUATE(source) evaluate(engine, object, source) |
2668 | |
2669 | void tst_qqmlecmascript::callQtInvokables() |
2670 | { |
2671 | // This object has JS ownership, as the call to method_NoArgs_QObject() in this test will return |
2672 | // it, which will set the indestructible flag to false. |
2673 | MyInvokableObject *o = new MyInvokableObject(); |
2674 | |
2675 | QQmlEngine qmlengine; |
2676 | |
2677 | QV4::ExecutionEngine *engine = qmlengine.handle(); |
2678 | QV4::Scope scope(engine); |
2679 | |
2680 | QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine, object: o)); |
2681 | |
2682 | // Non-existent methods |
2683 | o->reset(); |
2684 | QVERIFY(EVALUATE_ERROR("object.method_nonexistent()" )); |
2685 | QCOMPARE(o->error(), false); |
2686 | QCOMPARE(o->invoked(), -1); |
2687 | QCOMPARE(o->actuals().count(), 0); |
2688 | |
2689 | o->reset(); |
2690 | QVERIFY(EVALUATE_ERROR("object.method_nonexistent(10, 11)" )); |
2691 | QCOMPARE(o->error(), false); |
2692 | QCOMPARE(o->invoked(), -1); |
2693 | QCOMPARE(o->actuals().count(), 0); |
2694 | |
2695 | // Insufficient arguments |
2696 | o->reset(); |
2697 | QVERIFY(EVALUATE_ERROR("object.method_int()" )); |
2698 | QCOMPARE(o->error(), false); |
2699 | QCOMPARE(o->invoked(), -1); |
2700 | QCOMPARE(o->actuals().count(), 0); |
2701 | |
2702 | o->reset(); |
2703 | QVERIFY(EVALUATE_ERROR("object.method_intint(10)" )); |
2704 | QCOMPARE(o->error(), false); |
2705 | QCOMPARE(o->invoked(), -1); |
2706 | QCOMPARE(o->actuals().count(), 0); |
2707 | |
2708 | // Excessive arguments |
2709 | o->reset(); |
2710 | QVERIFY(EVALUATE_VALUE("object.method_int(10, 11)" , QV4::Primitive::undefinedValue())); |
2711 | QCOMPARE(o->error(), false); |
2712 | QCOMPARE(o->invoked(), 8); |
2713 | QCOMPARE(o->actuals().count(), 1); |
2714 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
2715 | |
2716 | o->reset(); |
2717 | QVERIFY(EVALUATE_VALUE("object.method_intint(10, 11, 12)" , QV4::Primitive::undefinedValue())); |
2718 | QCOMPARE(o->error(), false); |
2719 | QCOMPARE(o->invoked(), 9); |
2720 | QCOMPARE(o->actuals().count(), 2); |
2721 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
2722 | QCOMPARE(o->actuals().at(1), QVariant(11)); |
2723 | |
2724 | // Test return types |
2725 | o->reset(); |
2726 | QVERIFY(EVALUATE_VALUE("object.method_NoArgs()" , QV4::Primitive::undefinedValue())); |
2727 | QCOMPARE(o->error(), false); |
2728 | QCOMPARE(o->invoked(), 0); |
2729 | QCOMPARE(o->actuals().count(), 0); |
2730 | |
2731 | o->reset(); |
2732 | QVERIFY(EVALUATE_VALUE("object.method_NoArgs_int()" , QV4::Primitive::fromInt32(6))); |
2733 | QCOMPARE(o->error(), false); |
2734 | QCOMPARE(o->invoked(), 1); |
2735 | QCOMPARE(o->actuals().count(), 0); |
2736 | |
2737 | o->reset(); |
2738 | QVERIFY(EVALUATE_VALUE("object.method_NoArgs_real()" , QV4::Primitive::fromDouble(19.75))); |
2739 | QCOMPARE(o->error(), false); |
2740 | QCOMPARE(o->invoked(), 2); |
2741 | QCOMPARE(o->actuals().count(), 0); |
2742 | |
2743 | o->reset(); |
2744 | { |
2745 | QV4::ScopedValue ret(scope, EVALUATE("object.method_NoArgs_QPointF()" )); |
2746 | QVERIFY(!ret->isUndefined()); |
2747 | QCOMPARE(scope.engine->toVariant(ret, -1), QVariant(QPointF(123, 4.5))); |
2748 | QCOMPARE(o->error(), false); |
2749 | QCOMPARE(o->invoked(), 3); |
2750 | QCOMPARE(o->actuals().count(), 0); |
2751 | } |
2752 | |
2753 | o->reset(); |
2754 | { |
2755 | QV4::Scoped<QV4::QObjectWrapper> qobjectWrapper(scope, EVALUATE("object.method_NoArgs_QObject()" )); |
2756 | QVERIFY(qobjectWrapper); |
2757 | QCOMPARE(qobjectWrapper->object(), (QObject *)o); |
2758 | QCOMPARE(o->error(), false); |
2759 | QCOMPARE(o->invoked(), 4); |
2760 | QCOMPARE(o->actuals().count(), 0); |
2761 | } |
2762 | |
2763 | o->reset(); |
2764 | QVERIFY(EVALUATE_ERROR("object.method_NoArgs_unknown()" )); |
2765 | QCOMPARE(o->error(), false); |
2766 | QCOMPARE(o->invoked(), -1); |
2767 | QCOMPARE(o->actuals().count(), 0); |
2768 | |
2769 | o->reset(); |
2770 | { |
2771 | QV4::ScopedValue ret(scope, EVALUATE("object.method_NoArgs_QScriptValue()" )); |
2772 | QVERIFY(ret->isString()); |
2773 | QCOMPARE(ret->toQStringNoThrow(), QString("Hello world" )); |
2774 | QCOMPARE(o->error(), false); |
2775 | QCOMPARE(o->invoked(), 6); |
2776 | QCOMPARE(o->actuals().count(), 0); |
2777 | } |
2778 | |
2779 | o->reset(); |
2780 | QVERIFY(EVALUATE_VALUE("object.method_NoArgs_QVariant()" , QV4::ScopedValue(scope, scope.engine->newString("QML rocks" )))); |
2781 | QCOMPARE(o->error(), false); |
2782 | QCOMPARE(o->invoked(), 7); |
2783 | QCOMPARE(o->actuals().count(), 0); |
2784 | |
2785 | // Test arg types |
2786 | o->reset(); |
2787 | QVERIFY(EVALUATE_VALUE("object.method_int(94)" , QV4::Primitive::undefinedValue())); |
2788 | QCOMPARE(o->error(), false); |
2789 | QCOMPARE(o->invoked(), 8); |
2790 | QCOMPARE(o->actuals().count(), 1); |
2791 | QCOMPARE(o->actuals().at(0), QVariant(94)); |
2792 | |
2793 | o->reset(); |
2794 | QVERIFY(EVALUATE_VALUE("object.method_int(\"94\")" , QV4::Primitive::undefinedValue())); |
2795 | QCOMPARE(o->error(), false); |
2796 | QCOMPARE(o->invoked(), 8); |
2797 | QCOMPARE(o->actuals().count(), 1); |
2798 | QCOMPARE(o->actuals().at(0), QVariant(94)); |
2799 | |
2800 | o->reset(); |
2801 | QVERIFY(EVALUATE_VALUE("object.method_int(\"not a number\")" , QV4::Primitive::undefinedValue())); |
2802 | QCOMPARE(o->error(), false); |
2803 | QCOMPARE(o->invoked(), 8); |
2804 | QCOMPARE(o->actuals().count(), 1); |
2805 | QCOMPARE(o->actuals().at(0), QVariant(0)); |
2806 | |
2807 | o->reset(); |
2808 | QVERIFY(EVALUATE_VALUE("object.method_int(null)" , QV4::Primitive::undefinedValue())); |
2809 | QCOMPARE(o->error(), false); |
2810 | QCOMPARE(o->invoked(), 8); |
2811 | QCOMPARE(o->actuals().count(), 1); |
2812 | QCOMPARE(o->actuals().at(0), QVariant(0)); |
2813 | |
2814 | o->reset(); |
2815 | QVERIFY(EVALUATE_VALUE("object.method_int(undefined)" , QV4::Primitive::undefinedValue())); |
2816 | QCOMPARE(o->error(), false); |
2817 | QCOMPARE(o->invoked(), 8); |
2818 | QCOMPARE(o->actuals().count(), 1); |
2819 | QCOMPARE(o->actuals().at(0), QVariant(0)); |
2820 | |
2821 | o->reset(); |
2822 | QVERIFY(EVALUATE_VALUE("object.method_int(object)" , QV4::Primitive::undefinedValue())); |
2823 | QCOMPARE(o->error(), false); |
2824 | QCOMPARE(o->invoked(), 8); |
2825 | QCOMPARE(o->actuals().count(), 1); |
2826 | QCOMPARE(o->actuals().at(0), QVariant(0)); |
2827 | |
2828 | o->reset(); |
2829 | QVERIFY(EVALUATE_VALUE("object.method_intint(122, 9)" , QV4::Primitive::undefinedValue())); |
2830 | QCOMPARE(o->error(), false); |
2831 | QCOMPARE(o->invoked(), 9); |
2832 | QCOMPARE(o->actuals().count(), 2); |
2833 | QCOMPARE(o->actuals().at(0), QVariant(122)); |
2834 | QCOMPARE(o->actuals().at(1), QVariant(9)); |
2835 | |
2836 | o->reset(); |
2837 | QVERIFY(EVALUATE_VALUE("object.method_real(94.3)" , QV4::Primitive::undefinedValue())); |
2838 | QCOMPARE(o->error(), false); |
2839 | QCOMPARE(o->invoked(), 10); |
2840 | QCOMPARE(o->actuals().count(), 1); |
2841 | QCOMPARE(o->actuals().at(0), QVariant(94.3)); |
2842 | |
2843 | o->reset(); |
2844 | QVERIFY(EVALUATE_VALUE("object.method_real(\"94.3\")" , QV4::Primitive::undefinedValue())); |
2845 | QCOMPARE(o->error(), false); |
2846 | QCOMPARE(o->invoked(), 10); |
2847 | QCOMPARE(o->actuals().count(), 1); |
2848 | QCOMPARE(o->actuals().at(0), QVariant(94.3)); |
2849 | |
2850 | o->reset(); |
2851 | QVERIFY(EVALUATE_VALUE("object.method_real(\"not a number\")" , QV4::Primitive::undefinedValue())); |
2852 | QCOMPARE(o->error(), false); |
2853 | QCOMPARE(o->invoked(), 10); |
2854 | QCOMPARE(o->actuals().count(), 1); |
2855 | QVERIFY(qIsNaN(o->actuals().at(0).toDouble())); |
2856 | |
2857 | o->reset(); |
2858 | QVERIFY(EVALUATE_VALUE("object.method_real(null)" , QV4::Primitive::undefinedValue())); |
2859 | QCOMPARE(o->error(), false); |
2860 | QCOMPARE(o->invoked(), 10); |
2861 | QCOMPARE(o->actuals().count(), 1); |
2862 | QCOMPARE(o->actuals().at(0), QVariant(0)); |
2863 | |
2864 | o->reset(); |
2865 | QVERIFY(EVALUATE_VALUE("object.method_real(undefined)" , QV4::Primitive::undefinedValue())); |
2866 | QCOMPARE(o->error(), false); |
2867 | QCOMPARE(o->invoked(), 10); |
2868 | QCOMPARE(o->actuals().count(), 1); |
2869 | QVERIFY(qIsNaN(o->actuals().at(0).toDouble())); |
2870 | |
2871 | o->reset(); |
2872 | QVERIFY(EVALUATE_VALUE("object.method_real(object)" , QV4::Primitive::undefinedValue())); |
2873 | QCOMPARE(o->error(), false); |
2874 | QCOMPARE(o->invoked(), 10); |
2875 | QCOMPARE(o->actuals().count(), 1); |
2876 | QVERIFY(qIsNaN(o->actuals().at(0).toDouble())); |
2877 | |
2878 | o->reset(); |
2879 | QVERIFY(EVALUATE_VALUE("object.method_QString(\"Hello world\")" , QV4::Primitive::undefinedValue())); |
2880 | QCOMPARE(o->error(), false); |
2881 | QCOMPARE(o->invoked(), 11); |
2882 | QCOMPARE(o->actuals().count(), 1); |
2883 | QCOMPARE(o->actuals().at(0), QVariant("Hello world" )); |
2884 | |
2885 | o->reset(); |
2886 | QVERIFY(EVALUATE_VALUE("object.method_QString(19)" , QV4::Primitive::undefinedValue())); |
2887 | QCOMPARE(o->error(), false); |
2888 | QCOMPARE(o->invoked(), 11); |
2889 | QCOMPARE(o->actuals().count(), 1); |
2890 | QCOMPARE(o->actuals().at(0), QVariant("19" )); |
2891 | |
2892 | o->reset(); |
2893 | { |
2894 | QString expected = "MyInvokableObject(0x" + QString::number((quintptr)o, base: 16) + ")" ; |
2895 | QVERIFY(EVALUATE_VALUE("object.method_QString(object)" , QV4::Primitive::undefinedValue())); |
2896 | QCOMPARE(o->error(), false); |
2897 | QCOMPARE(o->invoked(), 11); |
2898 | QCOMPARE(o->actuals().count(), 1); |
2899 | QCOMPARE(o->actuals().at(0), QVariant(expected)); |
2900 | } |
2901 | |
2902 | o->reset(); |
2903 | QVERIFY(EVALUATE_VALUE("object.method_QString(null)" , QV4::Primitive::undefinedValue())); |
2904 | QCOMPARE(o->error(), false); |
2905 | QCOMPARE(o->invoked(), 11); |
2906 | QCOMPARE(o->actuals().count(), 1); |
2907 | QCOMPARE(o->actuals().at(0), QVariant(QString())); |
2908 | |
2909 | o->reset(); |
2910 | QVERIFY(EVALUATE_VALUE("object.method_QString(undefined)" , QV4::Primitive::undefinedValue())); |
2911 | QCOMPARE(o->error(), false); |
2912 | QCOMPARE(o->invoked(), 11); |
2913 | QCOMPARE(o->actuals().count(), 1); |
2914 | QCOMPARE(o->actuals().at(0), QVariant(QString())); |
2915 | |
2916 | o->reset(); |
2917 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(0)" , QV4::Primitive::undefinedValue())); |
2918 | QCOMPARE(o->error(), false); |
2919 | QCOMPARE(o->invoked(), 12); |
2920 | QCOMPARE(o->actuals().count(), 1); |
2921 | QCOMPARE(o->actuals().at(0), QVariant(QPointF())); |
2922 | |
2923 | o->reset(); |
2924 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(null)" , QV4::Primitive::undefinedValue())); |
2925 | QCOMPARE(o->error(), false); |
2926 | QCOMPARE(o->invoked(), 12); |
2927 | QCOMPARE(o->actuals().count(), 1); |
2928 | QCOMPARE(o->actuals().at(0), QVariant(QPointF())); |
2929 | |
2930 | o->reset(); |
2931 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(undefined)" , QV4::Primitive::undefinedValue())); |
2932 | QCOMPARE(o->error(), false); |
2933 | QCOMPARE(o->invoked(), 12); |
2934 | QCOMPARE(o->actuals().count(), 1); |
2935 | QCOMPARE(o->actuals().at(0), QVariant(QPointF())); |
2936 | |
2937 | o->reset(); |
2938 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(object)" , QV4::Primitive::undefinedValue())); |
2939 | QCOMPARE(o->error(), false); |
2940 | QCOMPARE(o->invoked(), 12); |
2941 | QCOMPARE(o->actuals().count(), 1); |
2942 | QCOMPARE(o->actuals().at(0), QVariant(QPointF())); |
2943 | |
2944 | o->reset(); |
2945 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(object.method_get_QPointF())" , QV4::Primitive::undefinedValue())); |
2946 | QCOMPARE(o->error(), false); |
2947 | QCOMPARE(o->invoked(), 12); |
2948 | QCOMPARE(o->actuals().count(), 1); |
2949 | QCOMPARE(o->actuals().at(0), QVariant(QPointF(99.3, -10.2))); |
2950 | |
2951 | o->reset(); |
2952 | QVERIFY(EVALUATE_VALUE("object.method_QPointF(object.method_get_QPoint())" , QV4::Primitive::undefinedValue())); |
2953 | QCOMPARE(o->error(), false); |
2954 | QCOMPARE(o->invoked(), 12); |
2955 | QCOMPARE(o->actuals().count(), 1); |
2956 | QCOMPARE(o->actuals().at(0), QVariant(QPointF(9, 12))); |
2957 | |
2958 | o->reset(); |
2959 | QVERIFY(EVALUATE_VALUE("object.method_QObject(0)" , QV4::Primitive::undefinedValue())); |
2960 | QCOMPARE(o->error(), false); |
2961 | QCOMPARE(o->invoked(), 13); |
2962 | QCOMPARE(o->actuals().count(), 1); |
2963 | QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); |
2964 | |
2965 | o->reset(); |
2966 | QVERIFY(EVALUATE_VALUE("object.method_QObject(\"Hello world\")" , QV4::Primitive::undefinedValue())); |
2967 | QCOMPARE(o->error(), false); |
2968 | QCOMPARE(o->invoked(), 13); |
2969 | QCOMPARE(o->actuals().count(), 1); |
2970 | QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); |
2971 | |
2972 | o->reset(); |
2973 | QVERIFY(EVALUATE_VALUE("object.method_QObject(null)" , QV4::Primitive::undefinedValue())); |
2974 | QCOMPARE(o->error(), false); |
2975 | QCOMPARE(o->invoked(), 13); |
2976 | QCOMPARE(o->actuals().count(), 1); |
2977 | QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); |
2978 | |
2979 | o->reset(); |
2980 | QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)" , QV4::Primitive::undefinedValue())); |
2981 | QCOMPARE(o->error(), false); |
2982 | QCOMPARE(o->invoked(), 13); |
2983 | QCOMPARE(o->actuals().count(), 1); |
2984 | QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)nullptr)); |
2985 | |
2986 | o->reset(); |
2987 | QVERIFY(EVALUATE_VALUE("object.method_QObject(object)" , QV4::Primitive::undefinedValue())); |
2988 | QCOMPARE(o->error(), false); |
2989 | QCOMPARE(o->invoked(), 13); |
2990 | QCOMPARE(o->actuals().count(), 1); |
2991 | QCOMPARE(o->actuals().at(0), QVariant::fromValue((QObject *)o)); |
2992 | |
2993 | o->reset(); |
2994 | QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(null)" , QV4::Primitive::undefinedValue())); |
2995 | QCOMPARE(o->error(), false); |
2996 | QCOMPARE(o->invoked(), 14); |
2997 | QCOMPARE(o->actuals().count(), 1); |
2998 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isNull()); |
2999 | |
3000 | o->reset(); |
3001 | QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(undefined)" , QV4::Primitive::undefinedValue())); |
3002 | QCOMPARE(o->error(), false); |
3003 | QCOMPARE(o->invoked(), 14); |
3004 | QCOMPARE(o->actuals().count(), 1); |
3005 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isUndefined()); |
3006 | |
3007 | o->reset(); |
3008 | QVERIFY(EVALUATE_VALUE("object.method_QScriptValue(19)" , QV4::Primitive::undefinedValue())); |
3009 | QCOMPARE(o->error(), false); |
3010 | QCOMPARE(o->invoked(), 14); |
3011 | QCOMPARE(o->actuals().count(), 1); |
3012 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).strictlyEquals(QJSValue(19))); |
3013 | |
3014 | o->reset(); |
3015 | QVERIFY(EVALUATE_VALUE("object.method_QScriptValue([19, 20])" , QV4::Primitive::undefinedValue())); |
3016 | QCOMPARE(o->error(), false); |
3017 | QCOMPARE(o->invoked(), 14); |
3018 | QCOMPARE(o->actuals().count(), 1); |
3019 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(0)).isArray()); |
3020 | |
3021 | o->reset(); |
3022 | QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(4, null)" , QV4::Primitive::undefinedValue())); |
3023 | QCOMPARE(o->error(), false); |
3024 | QCOMPARE(o->invoked(), 15); |
3025 | QCOMPARE(o->actuals().count(), 2); |
3026 | QCOMPARE(o->actuals().at(0), QVariant(4)); |
3027 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isNull()); |
3028 | |
3029 | o->reset(); |
3030 | QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(8, undefined)" , QV4::Primitive::undefinedValue())); |
3031 | QCOMPARE(o->error(), false); |
3032 | QCOMPARE(o->invoked(), 15); |
3033 | QCOMPARE(o->actuals().count(), 2); |
3034 | QCOMPARE(o->actuals().at(0), QVariant(8)); |
3035 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isUndefined()); |
3036 | |
3037 | o->reset(); |
3038 | QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(3, 19)" , QV4::Primitive::undefinedValue())); |
3039 | QCOMPARE(o->error(), false); |
3040 | QCOMPARE(o->invoked(), 15); |
3041 | QCOMPARE(o->actuals().count(), 2); |
3042 | QCOMPARE(o->actuals().at(0), QVariant(3)); |
3043 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).strictlyEquals(QJSValue(19))); |
3044 | |
3045 | o->reset(); |
3046 | QVERIFY(EVALUATE_VALUE("object.method_intQScriptValue(44, [19, 20])" , QV4::Primitive::undefinedValue())); |
3047 | QCOMPARE(o->error(), false); |
3048 | QCOMPARE(o->invoked(), 15); |
3049 | QCOMPARE(o->actuals().count(), 2); |
3050 | QCOMPARE(o->actuals().at(0), QVariant(44)); |
3051 | QVERIFY(qvariant_cast<QJSValue>(o->actuals().at(1)).isArray()); |
3052 | |
3053 | o->reset(); |
3054 | QVERIFY(EVALUATE_ERROR("object.method_overload()" )); |
3055 | QCOMPARE(o->error(), false); |
3056 | QCOMPARE(o->invoked(), -1); |
3057 | QCOMPARE(o->actuals().count(), 0); |
3058 | |
3059 | o->reset(); |
3060 | QVERIFY(EVALUATE_VALUE("object.method_overload(10)" , QV4::Primitive::undefinedValue())); |
3061 | QCOMPARE(o->error(), false); |
3062 | QCOMPARE(o->invoked(), 16); |
3063 | QCOMPARE(o->actuals().count(), 1); |
3064 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
3065 | |
3066 | o->reset(); |
3067 | QVERIFY(EVALUATE_VALUE("object.method_overload(10, 11)" , QV4::Primitive::undefinedValue())); |
3068 | QCOMPARE(o->error(), false); |
3069 | QCOMPARE(o->invoked(), 17); |
3070 | QCOMPARE(o->actuals().count(), 2); |
3071 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
3072 | QCOMPARE(o->actuals().at(1), QVariant(11)); |
3073 | |
3074 | o->reset(); |
3075 | QVERIFY(EVALUATE_VALUE("object.method_overload(\"Hello\")" , QV4::Primitive::undefinedValue())); |
3076 | QCOMPARE(o->error(), false); |
3077 | QCOMPARE(o->invoked(), 18); |
3078 | QCOMPARE(o->actuals().count(), 1); |
3079 | QCOMPARE(o->actuals().at(0), QVariant(QString("Hello" ))); |
3080 | |
3081 | o->reset(); |
3082 | QVERIFY(EVALUATE_VALUE("object.method_with_enum(9)" , QV4::Primitive::undefinedValue())); |
3083 | QCOMPARE(o->error(), false); |
3084 | QCOMPARE(o->invoked(), 19); |
3085 | QCOMPARE(o->actuals().count(), 1); |
3086 | QCOMPARE(o->actuals().at(0), QVariant(9)); |
3087 | |
3088 | o->reset(); |
3089 | QVERIFY(EVALUATE_VALUE("object.method_default(10)" , QV4::Primitive::fromInt32(19))); |
3090 | QCOMPARE(o->error(), false); |
3091 | QCOMPARE(o->invoked(), 20); |
3092 | QCOMPARE(o->actuals().count(), 2); |
3093 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
3094 | QCOMPARE(o->actuals().at(1), QVariant(19)); |
3095 | |
3096 | o->reset(); |
3097 | QVERIFY(EVALUATE_VALUE("object.method_default(10, 13)" , QV4::Primitive::fromInt32(13))); |
3098 | QCOMPARE(o->error(), false); |
3099 | QCOMPARE(o->invoked(), 20); |
3100 | QCOMPARE(o->actuals().count(), 2); |
3101 | QCOMPARE(o->actuals().at(0), QVariant(10)); |
3102 | QCOMPARE(o->actuals().at(1), QVariant(13)); |
3103 | |
3104 | o->reset(); |
3105 | QVERIFY(EVALUATE_VALUE("object.method_inherited(9)" , QV4::Primitive::undefinedValue())); |
3106 | QCOMPARE(o->error(), false); |
3107 | QCOMPARE(o->invoked(), -3); |
3108 | QCOMPARE(o->actuals().count(), 1); |
3109 | QCOMPARE(o->actuals().at(0), QVariant(9)); |
3110 | |
3111 | o->reset(); |
3112 | QVERIFY(EVALUATE_VALUE("object.method_QVariant(9)" , QV4::Primitive::undefinedValue())); |
3113 | QCOMPARE(o->error(), false); |
3114 | QCOMPARE(o->invoked(), 21); |
3115 | QCOMPARE(o->actuals().count(), 2); |
3116 | QCOMPARE(o->actuals().at(0), QVariant(9)); |
3117 | QCOMPARE(o->actuals().at(1), QVariant()); |
3118 | |
3119 | o->reset(); |
3120 | QVERIFY(EVALUATE_VALUE("object.method_QVariant(\"Hello\", \"World\")" , QV4::Primitive::undefinedValue())); |
3121 | QCOMPARE(o->error(), false); |
3122 | QCOMPARE(o->invoked(), 21); |
3123 | QCOMPARE(o->actuals().count(), 2); |
3124 | QCOMPARE(o->actuals().at(0), QVariant(QString("Hello" ))); |
3125 | QCOMPARE(o->actuals().at(1), QVariant(QString("World" ))); |
3126 | |
3127 | o->reset(); |
3128 | QVERIFY(EVALUATE_VALUE("object.method_QJsonObject({foo:123})" , QV4::Primitive::undefinedValue())); |
3129 | QCOMPARE(o->error(), false); |
3130 | QCOMPARE(o->invoked(), 22); |
3131 | QCOMPARE(o->actuals().count(), 1); |
3132 | QCOMPARE(qvariant_cast<QJsonObject>(o->actuals().at(0)), QJsonDocument::fromJson("{\"foo\":123}" ).object()); |
3133 | |
3134 | o->reset(); |
3135 | QVERIFY(EVALUATE_VALUE("object.method_QJsonArray([123])" , QV4::Primitive::undefinedValue())); |
3136 | QCOMPARE(o->error(), false); |
3137 | QCOMPARE(o->invoked(), 23); |
3138 | QCOMPARE(o->actuals().count(), 1); |
3139 | QCOMPARE(qvariant_cast<QJsonArray>(o->actuals().at(0)), QJsonDocument::fromJson("[123]" ).array()); |
3140 | |
3141 | o->reset(); |
3142 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(123)" , QV4::Primitive::undefinedValue())); |
3143 | QCOMPARE(o->error(), false); |
3144 | QCOMPARE(o->invoked(), 24); |
3145 | QCOMPARE(o->actuals().count(), 1); |
3146 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(123)); |
3147 | |
3148 | o->reset(); |
3149 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(42.35)" , QV4::Primitive::undefinedValue())); |
3150 | QCOMPARE(o->error(), false); |
3151 | QCOMPARE(o->invoked(), 24); |
3152 | QCOMPARE(o->actuals().count(), 1); |
3153 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(42.35)); |
3154 | |
3155 | o->reset(); |
3156 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue('ciao')" , QV4::Primitive::undefinedValue())); |
3157 | QCOMPARE(o->error(), false); |
3158 | QCOMPARE(o->invoked(), 24); |
3159 | QCOMPARE(o->actuals().count(), 1); |
3160 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QStringLiteral("ciao" ))); |
3161 | |
3162 | o->reset(); |
3163 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(true)" , QV4::Primitive::undefinedValue())); |
3164 | QCOMPARE(o->error(), false); |
3165 | QCOMPARE(o->invoked(), 24); |
3166 | QCOMPARE(o->actuals().count(), 1); |
3167 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(true)); |
3168 | |
3169 | o->reset(); |
3170 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(false)" , QV4::Primitive::undefinedValue())); |
3171 | QCOMPARE(o->error(), false); |
3172 | QCOMPARE(o->invoked(), 24); |
3173 | QCOMPARE(o->actuals().count(), 1); |
3174 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(false)); |
3175 | |
3176 | o->reset(); |
3177 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(null)" , QV4::Primitive::undefinedValue())); |
3178 | QCOMPARE(o->error(), false); |
3179 | QCOMPARE(o->invoked(), 24); |
3180 | QCOMPARE(o->actuals().count(), 1); |
3181 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Null)); |
3182 | |
3183 | o->reset(); |
3184 | QVERIFY(EVALUATE_VALUE("object.method_QJsonValue(undefined)" , QV4::Primitive::undefinedValue())); |
3185 | QCOMPARE(o->error(), false); |
3186 | QCOMPARE(o->invoked(), 24); |
3187 | QCOMPARE(o->actuals().count(), 1); |
3188 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Undefined)); |
3189 | |
3190 | o->reset(); |
3191 | QVERIFY(EVALUATE_VALUE("object.method_overload({foo:123})" , QV4::Primitive::undefinedValue())); |
3192 | QCOMPARE(o->error(), false); |
3193 | QCOMPARE(o->invoked(), 25); |
3194 | QCOMPARE(o->actuals().count(), 1); |
3195 | QCOMPARE(qvariant_cast<QJsonObject>(o->actuals().at(0)), QJsonDocument::fromJson("{\"foo\":123}" ).object()); |
3196 | |
3197 | o->reset(); |
3198 | QVERIFY(EVALUATE_VALUE("object.method_overload([123])" , QV4::Primitive::undefinedValue())); |
3199 | QCOMPARE(o->error(), false); |
3200 | QCOMPARE(o->invoked(), 26); |
3201 | QCOMPARE(o->actuals().count(), 1); |
3202 | QCOMPARE(qvariant_cast<QJsonArray>(o->actuals().at(0)), QJsonDocument::fromJson("[123]" ).array()); |
3203 | |
3204 | o->reset(); |
3205 | QVERIFY(EVALUATE_VALUE("object.method_overload(null)" , QV4::Primitive::undefinedValue())); |
3206 | QCOMPARE(o->error(), false); |
3207 | QCOMPARE(o->invoked(), 27); |
3208 | QCOMPARE(o->actuals().count(), 1); |
3209 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Null)); |
3210 | |
3211 | o->reset(); |
3212 | QVERIFY(EVALUATE_VALUE("object.method_overload(undefined)" , QV4::Primitive::undefinedValue())); |
3213 | QCOMPARE(o->error(), false); |
3214 | QCOMPARE(o->invoked(), 27); |
3215 | QCOMPARE(o->actuals().count(), 1); |
3216 | QCOMPARE(qvariant_cast<QJsonValue>(o->actuals().at(0)), QJsonValue(QJsonValue::Undefined)); |
3217 | |
3218 | o->reset(); |
3219 | QVERIFY(EVALUATE_ERROR("object.method_unknown(null)" )); |
3220 | QCOMPARE(o->error(), false); |
3221 | QCOMPARE(o->invoked(), -1); |
3222 | QCOMPARE(o->actuals().count(), 0); |
3223 | |
3224 | o->reset(); |
3225 | QVERIFY(EVALUATE_VALUE("object.method_QByteArray(\"Hello\")" , QV4::Primitive::undefinedValue())); |
3226 | QCOMPARE(o->error(), false); |
3227 | QCOMPARE(o->invoked(), 29); |
3228 | QCOMPARE(o->actuals().count(), 1); |
3229 | QCOMPARE(qvariant_cast<QByteArray>(o->actuals().at(0)), QByteArray("Hello" )); |
3230 | |
3231 | o->reset(); |
3232 | QV4::ScopedValue ret(scope, EVALUATE("object.method_intQJSValue(123, function() { return \"Hello world!\";})" )); |
3233 | QCOMPARE(o->error(), false); |
3234 | QCOMPARE(o->invoked(), 30); |
3235 | QVERIFY(ret->isString()); |
3236 | QCOMPARE(ret->toQStringNoThrow(), QString("Hello world!" )); |
3237 | QCOMPARE(o->actuals().count(), 2); |
3238 | QCOMPARE(o->actuals().at(0), QVariant(123)); |
3239 | QJSValue callback = qvariant_cast<QJSValue>(v: o->actuals().at(i: 1)); |
3240 | QVERIFY(!callback.isNull()); |
3241 | QVERIFY(callback.isCallable()); |
3242 | } |
3243 | |
3244 | void tst_qqmlecmascript::resolveClashingProperties() |
3245 | { |
3246 | QScopedPointer<ClashingNames> o(new ClashingNames()); |
3247 | QQmlEngine qmlengine; |
3248 | |
3249 | QV4::ExecutionEngine *engine = qmlengine.handle(); |
3250 | QV4::Scope scope(engine); |
3251 | |
3252 | QV4::ScopedValue object(scope, QV4::QObjectWrapper::wrap(engine, object: o.get())); |
3253 | QV4::ObjectIterator it(scope, object->as<QV4::Object>(), QV4::ObjectIterator::EnumerableOnly); |
3254 | QV4::ScopedValue name(scope); |
3255 | QV4::ScopedValue value(scope); |
3256 | |
3257 | bool seenProperty = false; |
3258 | bool seenMethod = false; |
3259 | while (true) { |
3260 | QV4::Value v; |
3261 | name = it.nextPropertyNameAsString(value: &v); |
3262 | if (name->isNull()) |
3263 | break; |
3264 | QString key = name->toQStringNoThrow(); |
3265 | if (key == QLatin1String("clashes" )) { |
3266 | value = v; |
3267 | QV4::ScopedValue typeString(scope, QV4::Runtime::TypeofValue::call(engine, value)); |
3268 | QString type = typeString->toQStringNoThrow(); |
3269 | if (type == QLatin1String("boolean" )) { |
3270 | QVERIFY(!seenProperty); |
3271 | seenProperty = true; |
3272 | } else if (type == QLatin1String("function" )) { |
3273 | QVERIFY(!seenMethod); |
3274 | seenMethod = true; |
3275 | } else { |
3276 | QFAIL(qPrintable(QString::fromLatin1("found 'clashes' property of type %1" ) |
3277 | .arg(type))); |
3278 | } |
3279 | } |
3280 | } |
3281 | QVERIFY(seenProperty); |
3282 | QVERIFY(seenMethod); |
3283 | } |
3284 | |
3285 | // QTBUG-13047 (check that you can pass registered object types as args) |
3286 | void tst_qqmlecmascript::invokableObjectArg() |
3287 | { |
3288 | QQmlEngine engine; |
3289 | QQmlComponent component(&engine, testFileUrl(fileName: "invokableObjectArg.qml" )); |
3290 | |
3291 | QObject *o = component.create(); |
3292 | QVERIFY(o); |
3293 | MyQmlObject *qmlobject = qobject_cast<MyQmlObject *>(object: o); |
3294 | QVERIFY(qmlobject); |
3295 | QCOMPARE(qmlobject->myinvokableObject, qmlobject); |
3296 | |
3297 | delete o; |
3298 | } |
3299 | |
3300 | // QTBUG-13047 (check that you can return registered object types from methods) |
3301 | void tst_qqmlecmascript::invokableObjectRet() |
3302 | { |
3303 | QQmlEngine engine; |
3304 | QQmlComponent component(&engine, testFileUrl(fileName: "invokableObjectRet.qml" )); |
3305 | |
3306 | QObject *o = component.create(); |
3307 | QVERIFY(o); |
3308 | QCOMPARE(o->property("test" ).toBool(), true); |
3309 | delete o; |
3310 | } |
3311 | |
3312 | void tst_qqmlecmascript::invokableEnumRet() |
3313 | { |
3314 | QQmlEngine engine; |
3315 | QQmlComponent component(&engine, testFileUrl(fileName: "invokableEnumRet.qml" )); |
3316 | |
3317 | QObject *o = component.create(); |
3318 | QVERIFY(o); |
3319 | QCOMPARE(o->property("test" ).toBool(), true); |
3320 | delete o; |
3321 | } |
3322 | |
3323 | // QTBUG-5675 |
3324 | void tst_qqmlecmascript::listToVariant() |
3325 | { |
3326 | QQmlEngine engine; |
3327 | QQmlComponent component(&engine, testFileUrl(fileName: "listToVariant.qml" )); |
3328 | |
3329 | MyQmlContainer container; |
3330 | |
3331 | QQmlContext context(engine.rootContext()); |
3332 | context.setContextObject(&container); |
3333 | |
3334 | QObject *object = component.create(context: &context); |
3335 | QVERIFY(object != nullptr); |
3336 | |
3337 | QVariant v = object->property(name: "test" ); |
3338 | QCOMPARE(v.userType(), qMetaTypeId<QQmlListReference>()); |
3339 | QCOMPARE(qvariant_cast<QQmlListReference>(v).object(), &container); |
3340 | |
3341 | delete object; |
3342 | } |
3343 | |
3344 | // QTBUG-16316 |
3345 | void tst_qqmlecmascript::listAssignment() |
3346 | { |
3347 | QQmlEngine engine; |
3348 | QQmlComponent component(&engine, testFileUrl(fileName: "listAssignment.qml" )); |
3349 | QObject *obj = component.create(); |
3350 | QCOMPARE(obj->property("list1length" ).toInt(), 2); |
3351 | QQmlListProperty<MyQmlObject> list1 = obj->property(name: "list1" ).value<QQmlListProperty<MyQmlObject> >(); |
3352 | QQmlListProperty<MyQmlObject> list2 = obj->property(name: "list2" ).value<QQmlListProperty<MyQmlObject> >(); |
3353 | QCOMPARE(list1.count(&list1), list2.count(&list2)); |
3354 | QCOMPARE(list1.at(&list1, 0), list2.at(&list2, 0)); |
3355 | QCOMPARE(list1.at(&list1, 1), list2.at(&list2, 1)); |
3356 | delete obj; |
3357 | } |
3358 | |
3359 | // QTBUG-7957 |
3360 | void tst_qqmlecmascript::multiEngineObject() |
3361 | { |
3362 | MyQmlObject obj; |
3363 | obj.setStringProperty("Howdy planet" ); |
3364 | |
3365 | QQmlEngine e1; |
3366 | e1.rootContext()->setContextProperty("thing" , &obj); |
3367 | QQmlComponent c1(&e1, testFileUrl(fileName: "multiEngineObject.qml" )); |
3368 | |
3369 | QQmlEngine e2; |
3370 | e2.rootContext()->setContextProperty("thing" , &obj); |
3371 | QQmlComponent c2(&e2, testFileUrl(fileName: "multiEngineObject.qml" )); |
3372 | |
3373 | QObject *o1 = c1.create(); |
3374 | QObject *o2 = c2.create(); |
3375 | |
3376 | QCOMPARE(o1->property("test" ).toString(), QString("Howdy planet" )); |
3377 | QCOMPARE(o2->property("test" ).toString(), QString("Howdy planet" )); |
3378 | |
3379 | delete o2; |
3380 | delete o1; |
3381 | } |
3382 | |
3383 | // Test that references to QObjects are cleanup when the object is destroyed |
3384 | void tst_qqmlecmascript::deletedObject() |
3385 | { |
3386 | QQmlEngine engine; |
3387 | QQmlComponent component(&engine, testFileUrl(fileName: "deletedObject.qml" )); |
3388 | |
3389 | QObject *object = component.create(); |
3390 | |
3391 | QCOMPARE(object->property("test1" ).toBool(), true); |
3392 | QCOMPARE(object->property("test2" ).toBool(), true); |
3393 | QCOMPARE(object->property("test3" ).toBool(), true); |
3394 | QCOMPARE(object->property("test4" ).toBool(), true); |
3395 | |
3396 | delete object; |
3397 | } |
3398 | |
3399 | void tst_qqmlecmascript::attachedPropertyScope() |
3400 | { |
3401 | QQmlEngine engine; |
3402 | QQmlComponent component(&engine, testFileUrl(fileName: "attachedPropertyScope.qml" )); |
3403 | |
3404 | QObject *object = component.create(); |
3405 | QVERIFY(object != nullptr); |
3406 | |
3407 | MyQmlAttachedObject *attached = |
3408 | qobject_cast<MyQmlAttachedObject *>(object: qmlAttachedPropertiesObject<MyQmlObject>(obj: object)); |
3409 | QVERIFY(attached != nullptr); |
3410 | |
3411 | QCOMPARE(object->property("value2" ).toInt(), 0); |
3412 | |
3413 | attached->emitMySignal(); |
3414 | |
3415 | QCOMPARE(object->property("value2" ).toInt(), 9); |
3416 | |
3417 | delete object; |
3418 | } |
3419 | |
3420 | void tst_qqmlecmascript::scriptConnect() |
3421 | { |
3422 | QQmlEngine engine; |
3423 | |
3424 | { |
3425 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.1.qml" )); |
3426 | |
3427 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3428 | QVERIFY(object != nullptr); |
3429 | |
3430 | QCOMPARE(object->property("test" ).toBool(), false); |
3431 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3432 | QCOMPARE(object->property("test" ).toBool(), true); |
3433 | |
3434 | delete object; |
3435 | } |
3436 | |
3437 | { |
3438 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.2.qml" )); |
3439 | |
3440 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3441 | QVERIFY(object != nullptr); |
3442 | |
3443 | QCOMPARE(object->property("test" ).toBool(), false); |
3444 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3445 | QCOMPARE(object->property("test" ).toBool(), true); |
3446 | |
3447 | delete object; |
3448 | } |
3449 | |
3450 | { |
3451 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.3.qml" )); |
3452 | |
3453 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3454 | QVERIFY(object != nullptr); |
3455 | |
3456 | QCOMPARE(object->property("test" ).toBool(), false); |
3457 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3458 | QCOMPARE(object->property("test" ).toBool(), true); |
3459 | |
3460 | delete object; |
3461 | } |
3462 | |
3463 | { |
3464 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.4.qml" )); |
3465 | |
3466 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3467 | QVERIFY(object != nullptr); |
3468 | |
3469 | QCOMPARE(object->methodCalled(), false); |
3470 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3471 | QCOMPARE(object->methodCalled(), true); |
3472 | |
3473 | delete object; |
3474 | } |
3475 | |
3476 | { |
3477 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.5.qml" )); |
3478 | |
3479 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3480 | QVERIFY(object != nullptr); |
3481 | |
3482 | QCOMPARE(object->methodCalled(), false); |
3483 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3484 | QCOMPARE(object->methodCalled(), true); |
3485 | |
3486 | delete object; |
3487 | } |
3488 | |
3489 | { |
3490 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptConnect.6.qml" )); |
3491 | |
3492 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3493 | QVERIFY(object != nullptr); |
3494 | |
3495 | QCOMPARE(object->property("test" ).toInt(), 0); |
3496 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3497 | QCOMPARE(object->property("test" ).toInt(), 2); |
3498 | |
3499 | delete object; |
3500 | } |
3501 | } |
3502 | |
3503 | void tst_qqmlecmascript::scriptDisconnect() |
3504 | { |
3505 | QQmlEngine engine; |
3506 | |
3507 | { |
3508 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptDisconnect.1.qml" )); |
3509 | |
3510 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3511 | QVERIFY(object != nullptr); |
3512 | |
3513 | QCOMPARE(object->property("test" ).toInt(), 0); |
3514 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3515 | QCOMPARE(object->property("test" ).toInt(), 1); |
3516 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3517 | QCOMPARE(object->property("test" ).toInt(), 2); |
3518 | emit object->basicSignal(); |
3519 | QCOMPARE(object->property("test" ).toInt(), 2); |
3520 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3521 | QCOMPARE(object->property("test" ).toInt(), 2); |
3522 | |
3523 | delete object; |
3524 | } |
3525 | |
3526 | { |
3527 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptDisconnect.2.qml" )); |
3528 | |
3529 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3530 | QVERIFY(object != nullptr); |
3531 | |
3532 | QCOMPARE(object->property("test" ).toInt(), 0); |
3533 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3534 | QCOMPARE(object->property("test" ).toInt(), 1); |
3535 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3536 | QCOMPARE(object->property("test" ).toInt(), 2); |
3537 | emit object->basicSignal(); |
3538 | QCOMPARE(object->property("test" ).toInt(), 2); |
3539 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3540 | QCOMPARE(object->property("test" ).toInt(), 2); |
3541 | |
3542 | delete object; |
3543 | } |
3544 | |
3545 | { |
3546 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptDisconnect.3.qml" )); |
3547 | |
3548 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3549 | QVERIFY(object != nullptr); |
3550 | |
3551 | QCOMPARE(object->property("test" ).toInt(), 0); |
3552 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3553 | QCOMPARE(object->property("test" ).toInt(), 1); |
3554 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3555 | QCOMPARE(object->property("test" ).toInt(), 2); |
3556 | emit object->basicSignal(); |
3557 | QCOMPARE(object->property("test" ).toInt(), 2); |
3558 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3559 | QCOMPARE(object->property("test" ).toInt(), 3); |
3560 | |
3561 | delete object; |
3562 | } |
3563 | { |
3564 | QQmlComponent component(&engine, testFileUrl(fileName: "scriptDisconnect.4.qml" )); |
3565 | |
3566 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3567 | QVERIFY(object != nullptr); |
3568 | |
3569 | QCOMPARE(object->property("test" ).toInt(), 0); |
3570 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3571 | QCOMPARE(object->property("test" ).toInt(), 1); |
3572 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3573 | QCOMPARE(object->property("test" ).toInt(), 2); |
3574 | emit object->basicSignal(); |
3575 | QCOMPARE(object->property("test" ).toInt(), 2); |
3576 | emit object->argumentSignal(a: 19, b: "Hello world!" , c: 10.25, d: MyQmlObject::EnumValue4, e: Qt::RightButton); |
3577 | QCOMPARE(object->property("test" ).toInt(), 3); |
3578 | |
3579 | delete object; |
3580 | } |
3581 | } |
3582 | |
3583 | class OwnershipObject : public QObject |
3584 | { |
3585 | Q_OBJECT |
3586 | public: |
3587 | OwnershipObject() { object = new QObject; } |
3588 | |
3589 | QPointer<QObject> object; |
3590 | |
3591 | public slots: |
3592 | QObject *getObject() { return object; } |
3593 | }; |
3594 | |
3595 | void tst_qqmlecmascript::ownership() |
3596 | { |
3597 | QQmlEngine engine; |
3598 | OwnershipObject own; |
3599 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
3600 | context->setContextObject(&own); |
3601 | |
3602 | { |
3603 | QQmlComponent component(&engine, testFileUrl(fileName: "ownership.qml" )); |
3604 | |
3605 | QVERIFY(own.object != nullptr); |
3606 | |
3607 | QObject *object = component.create(context); |
3608 | |
3609 | engine.collectGarbage(); |
3610 | |
3611 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3612 | QCoreApplication::processEvents(); |
3613 | |
3614 | QVERIFY(own.object.isNull()); |
3615 | |
3616 | delete object; |
3617 | } |
3618 | |
3619 | own.object = new QObject(&own); |
3620 | |
3621 | { |
3622 | QQmlComponent component(&engine, testFileUrl(fileName: "ownership.qml" )); |
3623 | |
3624 | QVERIFY(own.object != nullptr); |
3625 | |
3626 | QObject *object = component.create(context); |
3627 | |
3628 | engine.collectGarbage(); |
3629 | |
3630 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3631 | QCoreApplication::processEvents(); |
3632 | |
3633 | QVERIFY(own.object != nullptr); |
3634 | |
3635 | delete object; |
3636 | } |
3637 | |
3638 | delete context; |
3639 | } |
3640 | |
3641 | class CppOwnershipReturnValue : public QObject |
3642 | { |
3643 | Q_OBJECT |
3644 | public: |
3645 | CppOwnershipReturnValue() : value(nullptr) {} |
3646 | ~CppOwnershipReturnValue() { delete value; } |
3647 | |
3648 | Q_INVOKABLE QObject *create() { |
3649 | value = new QObject; |
3650 | QQmlEngine::setObjectOwnership(value, QQmlEngine::CppOwnership); |
3651 | return value; |
3652 | } |
3653 | |
3654 | Q_INVOKABLE MyQmlObject *createQmlObject() { |
3655 | MyQmlObject *rv = new MyQmlObject; |
3656 | value = rv; |
3657 | return rv; |
3658 | } |
3659 | |
3660 | QPointer<QObject> value; |
3661 | }; |
3662 | |
3663 | // QTBUG-15695. |
3664 | // Test setObjectOwnership(CppOwnership) works even when there is no QQmlData |
3665 | void tst_qqmlecmascript::cppOwnershipReturnValue() |
3666 | { |
3667 | CppOwnershipReturnValue source; |
3668 | |
3669 | { |
3670 | QQmlEngine engine; |
3671 | engine.rootContext()->setContextProperty("source" , &source); |
3672 | |
3673 | QVERIFY(source.value.isNull()); |
3674 | |
3675 | QQmlComponent component(&engine); |
3676 | component.setData("import QtQuick 2.0\nQtObject {\nComponent.onCompleted: { var a = source.create(); }\n}\n" , baseUrl: QUrl()); |
3677 | |
3678 | QObject *object = component.create(); |
3679 | |
3680 | QVERIFY(object != nullptr); |
3681 | QVERIFY(source.value != nullptr); |
3682 | |
3683 | delete object; |
3684 | } |
3685 | |
3686 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3687 | QCoreApplication::processEvents(); |
3688 | |
3689 | QVERIFY(source.value != nullptr); |
3690 | } |
3691 | |
3692 | // QTBUG-15697 |
3693 | void tst_qqmlecmascript::ownershipCustomReturnValue() |
3694 | { |
3695 | QQmlEngine engine; |
3696 | CppOwnershipReturnValue source; |
3697 | |
3698 | { |
3699 | QQmlEngine engine; |
3700 | engine.rootContext()->setContextProperty("source" , &source); |
3701 | |
3702 | QVERIFY(source.value.isNull()); |
3703 | |
3704 | QQmlComponent component(&engine); |
3705 | component.setData("import QtQuick 2.0\nQtObject {\nComponent.onCompleted: { var a = source.createQmlObject(); }\n}\n" , baseUrl: QUrl()); |
3706 | |
3707 | QObject *object = component.create(); |
3708 | |
3709 | QVERIFY(object != nullptr); |
3710 | QVERIFY(source.value != nullptr); |
3711 | |
3712 | delete object; |
3713 | } |
3714 | |
3715 | engine.collectGarbage(); |
3716 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3717 | QCoreApplication::processEvents(); |
3718 | |
3719 | QVERIFY(source.value.isNull()); |
3720 | } |
3721 | |
3722 | //the return value from getObject will be JS ownership, |
3723 | //unless strong Cpp ownership has been set |
3724 | class OwnershipChangingObject : public QObject |
3725 | { |
3726 | Q_OBJECT |
3727 | public: |
3728 | OwnershipChangingObject(): object(nullptr) { } |
3729 | |
3730 | QPointer<QObject> object; |
3731 | |
3732 | public slots: |
3733 | QObject *getObject() { return object; } |
3734 | void setObject(QObject *obj) { object = obj; } |
3735 | }; |
3736 | |
3737 | void tst_qqmlecmascript::ownershipRootObject() |
3738 | { |
3739 | QQmlEngine engine; |
3740 | OwnershipChangingObject own; |
3741 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
3742 | context->setContextObject(&own); |
3743 | |
3744 | QQmlComponent component(&engine, testFileUrl(fileName: "ownershipRootObject.qml" )); |
3745 | QPointer<QObject> object = component.create(context); |
3746 | QVERIFY(object); |
3747 | |
3748 | engine.collectGarbage(); |
3749 | |
3750 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3751 | QCoreApplication::processEvents(); |
3752 | |
3753 | QVERIFY(own.object != nullptr); |
3754 | |
3755 | delete context; |
3756 | delete object; |
3757 | } |
3758 | |
3759 | void tst_qqmlecmascript::ownershipConsistency() |
3760 | { |
3761 | QQmlEngine engine; |
3762 | OwnershipChangingObject own; |
3763 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
3764 | context->setContextObject(&own); |
3765 | |
3766 | QString expectedWarning = testFileUrl(fileName: "ownershipConsistency.qml" ).toString() + QLatin1String(":19: Error: Invalid attempt to destroy() an indestructible object" ); |
3767 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
3768 | expectedWarning = testFileUrl(fileName: "ownershipConsistency.qml" ).toString() + QLatin1String(":15: Error: Invalid attempt to destroy() an indestructible object" ); |
3769 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
3770 | expectedWarning = testFileUrl(fileName: "ownershipConsistency.qml" ).toString() + QLatin1String(":6: Error: Invalid attempt to destroy() an indestructible object" ); |
3771 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
3772 | expectedWarning = testFileUrl(fileName: "ownershipConsistency.qml" ).toString() + QLatin1String(":10: Error: Invalid attempt to destroy() an indestructible object" ); |
3773 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
3774 | |
3775 | QQmlComponent component(&engine, testFileUrl(fileName: "ownershipConsistency.qml" )); |
3776 | QPointer<QObject> object = component.create(context); |
3777 | QVERIFY(object); |
3778 | |
3779 | engine.collectGarbage(); |
3780 | |
3781 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3782 | QCoreApplication::processEvents(); |
3783 | |
3784 | QVERIFY(own.object != nullptr); |
3785 | |
3786 | delete context; |
3787 | delete object; |
3788 | } |
3789 | |
3790 | void tst_qqmlecmascript::ownershipQmlIncubated() |
3791 | { |
3792 | QQmlEngine engine; |
3793 | QQmlComponent component(&engine, testFileUrl(fileName: "ownershipQmlIncubated.qml" )); |
3794 | QObject *object = component.create(); |
3795 | QVERIFY(object); |
3796 | |
3797 | QTRY_VERIFY(object->property("incubatedItem" ).value<QObject*>() != 0); |
3798 | |
3799 | QMetaObject::invokeMethod(obj: object, member: "deleteIncubatedItem" ); |
3800 | |
3801 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
3802 | QCoreApplication::processEvents(); |
3803 | |
3804 | QVERIFY(!object->property("incubatedItem" ).value<QObject*>()); |
3805 | |
3806 | delete object; |
3807 | } |
3808 | |
3809 | class QListQObjectMethodsObject : public QObject |
3810 | { |
3811 | Q_OBJECT |
3812 | public: |
3813 | QListQObjectMethodsObject() { |
3814 | m_objects.append(t: new MyQmlObject()); |
3815 | m_objects.append(t: new MyQmlObject()); |
3816 | } |
3817 | |
3818 | ~QListQObjectMethodsObject() { |
3819 | qDeleteAll(c: m_objects); |
3820 | } |
3821 | |
3822 | public slots: |
3823 | QList<QObject *> getObjects() { return m_objects; } |
3824 | |
3825 | private: |
3826 | QList<QObject *> m_objects; |
3827 | }; |
3828 | |
3829 | // Tests that returning a QList<QObject*> from a method works |
3830 | void tst_qqmlecmascript::qlistqobjectMethods() |
3831 | { |
3832 | QQmlEngine engine; |
3833 | QListQObjectMethodsObject obj; |
3834 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
3835 | context->setContextObject(&obj); |
3836 | |
3837 | QQmlComponent component(&engine, testFileUrl(fileName: "qlistqobjectMethods.qml" )); |
3838 | |
3839 | QObject *object = component.create(context); |
3840 | |
3841 | QCOMPARE(object->property("test" ).toInt(), 2); |
3842 | QCOMPARE(object->property("test2" ).toBool(), true); |
3843 | |
3844 | delete object; |
3845 | delete context; |
3846 | } |
3847 | |
3848 | // QTBUG-9205 |
3849 | void tst_qqmlecmascript::strictlyEquals() |
3850 | { |
3851 | QQmlEngine engine; |
3852 | QQmlComponent component(&engine, testFileUrl(fileName: "strictlyEquals.qml" )); |
3853 | |
3854 | QObject *object = component.create(); |
3855 | QVERIFY(object != nullptr); |
3856 | |
3857 | QCOMPARE(object->property("test1" ).toBool(), true); |
3858 | QCOMPARE(object->property("test2" ).toBool(), true); |
3859 | QCOMPARE(object->property("test3" ).toBool(), true); |
3860 | QCOMPARE(object->property("test4" ).toBool(), true); |
3861 | QCOMPARE(object->property("test5" ).toBool(), true); |
3862 | QCOMPARE(object->property("test6" ).toBool(), true); |
3863 | QCOMPARE(object->property("test7" ).toBool(), true); |
3864 | QCOMPARE(object->property("test8" ).toBool(), true); |
3865 | |
3866 | delete object; |
3867 | } |
3868 | |
3869 | void tst_qqmlecmascript::compiled() |
3870 | { |
3871 | QQmlEngine engine; |
3872 | QQmlComponent component(&engine, testFileUrl(fileName: "compiled.qml" )); |
3873 | |
3874 | QObject *object = component.create(); |
3875 | QVERIFY(object != nullptr); |
3876 | |
3877 | QCOMPARE(object->property("test1" ).toReal(), qreal(15.7)); |
3878 | QCOMPARE(object->property("test2" ).toReal(), qreal(-6.7)); |
3879 | QCOMPARE(object->property("test3" ).toBool(), true); |
3880 | QCOMPARE(object->property("test4" ).toBool(), false); |
3881 | QCOMPARE(object->property("test5" ).toBool(), false); |
3882 | QCOMPARE(object->property("test6" ).toBool(), true); |
3883 | |
3884 | QCOMPARE(object->property("test7" ).toInt(), 185); |
3885 | QCOMPARE(object->property("test8" ).toInt(), 167); |
3886 | QCOMPARE(object->property("test9" ).toBool(), true); |
3887 | QCOMPARE(object->property("test10" ).toBool(), false); |
3888 | QCOMPARE(object->property("test11" ).toBool(), false); |
3889 | QCOMPARE(object->property("test12" ).toBool(), true); |
3890 | |
3891 | QCOMPARE(object->property("test13" ).toString(), QLatin1String("HelloWorld" )); |
3892 | QCOMPARE(object->property("test14" ).toString(), QLatin1String("Hello World" )); |
3893 | QCOMPARE(object->property("test15" ).toBool(), false); |
3894 | QCOMPARE(object->property("test16" ).toBool(), true); |
3895 | |
3896 | QCOMPARE(object->property("test17" ).toInt(), 4); |
3897 | QCOMPARE(object->property("test18" ).toReal(), qreal(176)); |
3898 | QCOMPARE(object->property("test19" ).toInt(), 6); |
3899 | QCOMPARE(object->property("test20" ).toReal(), qreal(6.5)); |
3900 | QCOMPARE(object->property("test21" ).toString(), QLatin1String("6.5" )); |
3901 | QCOMPARE(object->property("test22" ).toString(), QLatin1String("!" )); |
3902 | QCOMPARE(object->property("test23" ).toBool(), true); |
3903 | QCOMPARE(qvariant_cast<QColor>(object->property("test24" )), QColor(0x11,0x22,0x33)); |
3904 | QCOMPARE(qvariant_cast<QColor>(object->property("test25" )), QColor(0x11,0x22,0x33,0xAA)); |
3905 | |
3906 | delete object; |
3907 | } |
3908 | |
3909 | // Test that numbers assigned in bindings as strings work consistently |
3910 | void tst_qqmlecmascript::numberAssignment() |
3911 | { |
3912 | QQmlEngine engine; |
3913 | QQmlComponent component(&engine, testFileUrl(fileName: "numberAssignment.qml" )); |
3914 | |
3915 | QObject *object = component.create(); |
3916 | QVERIFY(object != nullptr); |
3917 | |
3918 | QCOMPARE(object->property("test1" ), QVariant((qreal)6.7)); |
3919 | QCOMPARE(object->property("test2" ), QVariant((qreal)6.7)); |
3920 | QCOMPARE(object->property("test2" ), QVariant((qreal)6.7)); |
3921 | QCOMPARE(object->property("test3" ), QVariant((qreal)6)); |
3922 | QCOMPARE(object->property("test4" ), QVariant((qreal)6)); |
3923 | |
3924 | QCOMPARE(object->property("test5" ), QVariant((int)6)); |
3925 | QCOMPARE(object->property("test6" ), QVariant((int)7)); |
3926 | QCOMPARE(object->property("test7" ), QVariant((int)6)); |
3927 | QCOMPARE(object->property("test8" ), QVariant((int)6)); |
3928 | |
3929 | QCOMPARE(object->property("test9" ), QVariant((unsigned int)7)); |
3930 | QCOMPARE(object->property("test10" ), QVariant((unsigned int)7)); |
3931 | QCOMPARE(object->property("test11" ), QVariant((unsigned int)6)); |
3932 | QCOMPARE(object->property("test12" ), QVariant((unsigned int)6)); |
3933 | |
3934 | delete object; |
3935 | } |
3936 | |
3937 | void tst_qqmlecmascript::propertySplicing() |
3938 | { |
3939 | QQmlEngine engine; |
3940 | QQmlComponent component(&engine, testFileUrl(fileName: "propertySplicing.qml" )); |
3941 | |
3942 | QObject *object = component.create(); |
3943 | QVERIFY(object != nullptr); |
3944 | |
3945 | QCOMPARE(object->property("test" ).toBool(), true); |
3946 | |
3947 | delete object; |
3948 | } |
3949 | |
3950 | // QTBUG-16683 |
3951 | void tst_qqmlecmascript::signalWithUnknownTypes() |
3952 | { |
3953 | QQmlEngine engine; |
3954 | QQmlComponent component(&engine, testFileUrl(fileName: "signalWithUnknownTypes.qml" )); |
3955 | |
3956 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
3957 | QVERIFY(object != nullptr); |
3958 | |
3959 | MyQmlObject::MyType type; |
3960 | type.value = 0x8971123; |
3961 | emit object->signalWithUnknownType(arg: type); |
3962 | |
3963 | MyQmlObject::MyType result = qvariant_cast<MyQmlObject::MyType>(v: object->variant()); |
3964 | |
3965 | QCOMPARE(result.value, type.value); |
3966 | |
3967 | MyQmlObject::MyOtherType othertype; |
3968 | othertype.value = 17; |
3969 | emit object->signalWithCompletelyUnknownType(arg: othertype); |
3970 | |
3971 | QVERIFY(!object->variant().isValid()); |
3972 | |
3973 | delete object; |
3974 | } |
3975 | |
3976 | void tst_qqmlecmascript::signalWithJSValueInVariant_data() |
3977 | { |
3978 | QTest::addColumn<QString>(name: "expression" ); |
3979 | QTest::addColumn<QString>(name: "compare" ); |
3980 | |
3981 | QString compareStrict("(function(a, b) { return a === b; })" ); |
3982 | QTest::newRow(dataTag: "true" ) << "true" << compareStrict; |
3983 | QTest::newRow(dataTag: "undefined" ) << "undefined" << compareStrict; |
3984 | QTest::newRow(dataTag: "null" ) << "null" << compareStrict; |
3985 | QTest::newRow(dataTag: "123" ) << "123" << compareStrict; |
3986 | QTest::newRow(dataTag: "'ciao'" ) << "'ciao'" << compareStrict; |
3987 | |
3988 | QString comparePropertiesStrict( |
3989 | "(function compareMe(a, b) {" |
3990 | " if (typeof b != 'object')" |
3991 | " return a === b;" |
3992 | " var props = Object.getOwnPropertyNames(b);" |
3993 | " for (var i = 0; i < props.length; ++i) {" |
3994 | " var p = props[i];" |
3995 | " return compareMe(a[p], b[p]);" |
3996 | " }" |
3997 | "})" ); |
3998 | QTest::newRow(dataTag: "{ foo: 'bar' }" ) << "({ foo: 'bar' })" << comparePropertiesStrict; |
3999 | QTest::newRow(dataTag: "[10,20,30]" ) << "[10,20,30]" << comparePropertiesStrict; |
4000 | } |
4001 | |
4002 | void tst_qqmlecmascript::signalWithJSValueInVariant() |
4003 | { |
4004 | QFETCH(QString, expression); |
4005 | QFETCH(QString, compare); |
4006 | |
4007 | QQmlEngine engine; |
4008 | QQmlComponent component(&engine, testFileUrl(fileName: "signalWithJSValueInVariant.qml" )); |
4009 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(object: component.create())); |
4010 | QVERIFY(object != nullptr); |
4011 | |
4012 | QJSValue value = engine.evaluate(program: expression); |
4013 | QVERIFY(!value.isError()); |
4014 | object->setProperty(name: "expression" , value: expression); |
4015 | object->setProperty(name: "compare" , value: compare); |
4016 | object->setProperty(name: "pass" , value: false); |
4017 | |
4018 | emit object->signalWithVariant(arg: QVariant::fromValue(value)); |
4019 | QVERIFY(object->property("pass" ).toBool()); |
4020 | } |
4021 | |
4022 | void tst_qqmlecmascript::signalWithJSValueInVariant_twoEngines_data() |
4023 | { |
4024 | signalWithJSValueInVariant_data(); |
4025 | } |
4026 | |
4027 | void tst_qqmlecmascript::signalWithJSValueInVariant_twoEngines() |
4028 | { |
4029 | QFETCH(QString, expression); |
4030 | QFETCH(QString, compare); |
4031 | |
4032 | QQmlEngine engine; |
4033 | QQmlComponent component(&engine, testFileUrl(fileName: "signalWithJSValueInVariant.qml" )); |
4034 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(object: component.create())); |
4035 | QVERIFY(object != nullptr); |
4036 | |
4037 | QJSEngine engine2; |
4038 | QJSValue value = engine2.evaluate(program: expression); |
4039 | QVERIFY(!value.isError()); |
4040 | object->setProperty(name: "expression" , value: expression); |
4041 | object->setProperty(name: "compare" , value: compare); |
4042 | object->setProperty(name: "pass" , value: false); |
4043 | |
4044 | QTest::ignoreMessage(type: QtWarningMsg, message: "JSValue can't be reassigned to another engine." ); |
4045 | emit object->signalWithVariant(arg: QVariant::fromValue(value)); |
4046 | if (expression == "undefined" ) |
4047 | // if the engine is wrong, we return undefined to the other engine, |
4048 | // making this one case pass |
4049 | return; |
4050 | QVERIFY(!object->property("pass" ).toBool()); |
4051 | } |
4052 | |
4053 | void tst_qqmlecmascript::signalWithQJSValue_data() |
4054 | { |
4055 | signalWithJSValueInVariant_data(); |
4056 | } |
4057 | |
4058 | void tst_qqmlecmascript::signalWithQJSValue() |
4059 | { |
4060 | QFETCH(QString, expression); |
4061 | QFETCH(QString, compare); |
4062 | |
4063 | QQmlEngine engine; |
4064 | QQmlComponent component(&engine, testFileUrl(fileName: "signalWithQJSValue.qml" )); |
4065 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject *>(object: component.create())); |
4066 | QVERIFY(object != nullptr); |
4067 | |
4068 | QJSValue value = engine.evaluate(program: expression); |
4069 | QVERIFY(!value.isError()); |
4070 | object->setProperty(name: "expression" , value: expression); |
4071 | object->setProperty(name: "compare" , value: compare); |
4072 | object->setProperty(name: "pass" , value: false); |
4073 | |
4074 | emit object->signalWithQJSValue(arg: value); |
4075 | |
4076 | QVERIFY(object->property("pass" ).toBool()); |
4077 | QVERIFY(object->qjsvalue().strictlyEquals(value)); |
4078 | } |
4079 | |
4080 | void tst_qqmlecmascript::singletonType_data() |
4081 | { |
4082 | QTest::addColumn<QUrl>(name: "testfile" ); |
4083 | QTest::addColumn<QString>(name: "errorMessage" ); |
4084 | QTest::addColumn<QStringList>(name: "warningMessages" ); |
4085 | QTest::addColumn<QStringList>(name: "readProperties" ); |
4086 | QTest::addColumn<QVariantList>(name: "readExpectedValues" ); |
4087 | QTest::addColumn<QStringList>(name: "writeProperties" ); |
4088 | QTest::addColumn<QVariantList>(name: "writeValues" ); |
4089 | QTest::addColumn<QStringList>(name: "readBackProperties" ); |
4090 | QTest::addColumn<QVariantList>(name: "readBackExpectedValues" ); |
4091 | |
4092 | QTest::newRow(dataTag: "qobject, register + read + method [no qualifier]" ) |
4093 | << testFileUrl(fileName: "singletontype/qobjectSingletonTypeNoQualifier.qml" ) |
4094 | << QString() |
4095 | << QStringList() |
4096 | << (QStringList() << "qobjectPropertyTest" << "qobjectMethodTest" ) |
4097 | << (QVariantList() << 20 << 1) |
4098 | << QStringList() |
4099 | << QVariantList() |
4100 | << QStringList() |
4101 | << QVariantList(); |
4102 | |
4103 | QTest::newRow(dataTag: "script, register + read [no qualifier]" ) |
4104 | << testFileUrl(fileName: "singletontype/scriptSingletonTypeNoQualifier.qml" ) |
4105 | << QString() |
4106 | << QStringList() |
4107 | << (QStringList() << "scriptTest" ) |
4108 | << (QVariantList() << 13) |
4109 | << QStringList() |
4110 | << QVariantList() |
4111 | << QStringList() |
4112 | << QVariantList(); |
4113 | |
4114 | QTest::newRow(dataTag: "qobject, register + read + method" ) |
4115 | << testFileUrl(fileName: "singletontype/qobjectSingletonType.qml" ) |
4116 | << QString() |
4117 | << QStringList() |
4118 | << (QStringList() << "existingUriTest" << "qobjectTest" << "qobjectMethodTest" |
4119 | << "qobjectMinorVersionMethodTest" << "qobjectMinorVersionTest" |
4120 | << "qobjectMajorVersionTest" << "qobjectParentedTest" ) |
4121 | << (QVariantList() << 20 << 20 << 2 << 1 << 20 << 20 << 26) |
4122 | << QStringList() |
4123 | << QVariantList() |
4124 | << QStringList() |
4125 | << QVariantList(); |
4126 | |
4127 | QTest::newRow(dataTag: "script, register + read" ) |
4128 | << testFileUrl(fileName: "singletontype/scriptSingletonType.qml" ) |
4129 | << QString() |
4130 | << QStringList() |
4131 | << (QStringList() << "scriptTest" ) |
4132 | << (QVariantList() << 14) // will have incremented, since we create a new engine each row in this test. |
4133 | << QStringList() |
4134 | << QVariantList() |
4135 | << QStringList() |
4136 | << QVariantList(); |
4137 | |
4138 | QTest::newRow(dataTag: "qobject, writing + readonly constraints" ) |
4139 | << testFileUrl(fileName: "singletontype/qobjectSingletonTypeWriting.qml" ) |
4140 | << QString() |
4141 | << (QStringList() << |
4142 | QString(testFileUrl(fileName: "singletontype/qobjectSingletonTypeWriting.qml" ).toString() + QLatin1String(":15: TypeError: Cannot assign to read-only property \"qobjectTestProperty\"" ))) |
4143 | << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty" ) |
4144 | << (QVariantList() << 20 << 50 << 10) |
4145 | << (QStringList() << "firstProperty" << "secondProperty" ) |
4146 | << (QVariantList() << 30 << 30) |
4147 | << (QStringList() << "readOnlyProperty" << "writableProperty" << "writableFinalProperty" ) |
4148 | << (QVariantList() << 20 << 30 << 30); |
4149 | |
4150 | QTest::newRow(dataTag: "script, writing + readonly constraints" ) |
4151 | << testFileUrl(fileName: "singletontype/scriptSingletonTypeWriting.qml" ) |
4152 | << QString() |
4153 | << (QStringList()) |
4154 | << (QStringList() << "readBack" << "unchanged" ) |
4155 | << (QVariantList() << 15 << 42) |
4156 | << (QStringList() << "firstProperty" << "secondProperty" ) |
4157 | << (QVariantList() << 30 << 30) |
4158 | << (QStringList() << "readBack" << "unchanged" ) |
4159 | << (QVariantList() << 30 << 42); |
4160 | |
4161 | QTest::newRow(dataTag: "qobject singleton Type enum values in JS" ) |
4162 | << testFileUrl(fileName: "singletontype/qobjectSingletonTypeEnums.qml" ) |
4163 | << QString() |
4164 | << QStringList() |
4165 | << (QStringList() << "enumValue" << "enumMethod" ) |
4166 | << (QVariantList() << 42 << 30) |
4167 | << QStringList() |
4168 | << QVariantList() |
4169 | << QStringList() |
4170 | << QVariantList(); |
4171 | |
4172 | QTest::newRow(dataTag: "qobject, invalid major version fail" ) |
4173 | << testFileUrl(fileName: "singletontype/singletonTypeMajorVersionFail.qml" ) |
4174 | << QString("QQmlComponent: Component is not ready" ) |
4175 | << QStringList() |
4176 | << QStringList() |
4177 | << QVariantList() |
4178 | << QStringList() |
4179 | << QVariantList() |
4180 | << QStringList() |
4181 | << QVariantList(); |
4182 | |
4183 | QTest::newRow(dataTag: "qobject, invalid minor version fail" ) |
4184 | << testFileUrl(fileName: "singletontype/singletonTypeMinorVersionFail.qml" ) |
4185 | << QString("QQmlComponent: Component is not ready" ) |
4186 | << QStringList() |
4187 | << QStringList() |
4188 | << QVariantList() |
4189 | << QStringList() |
4190 | << QVariantList() |
4191 | << QStringList() |
4192 | << QVariantList(); |
4193 | |
4194 | QTest::newRow(dataTag: "qobject, multiple in namespace" ) |
4195 | << testFileUrl(fileName: "singletontype/singletonTypeMultiple.qml" ) |
4196 | << QString() |
4197 | << QStringList() |
4198 | << (QStringList() << "first" << "second" ) |
4199 | << (QVariantList() << 35 << 42) |
4200 | << QStringList() |
4201 | << QVariantList() |
4202 | << QStringList() |
4203 | << QVariantList(); |
4204 | } |
4205 | |
4206 | void tst_qqmlecmascript::singletonType() |
4207 | { |
4208 | QFETCH(QUrl, testfile); |
4209 | QFETCH(QString, errorMessage); |
4210 | QFETCH(QStringList, warningMessages); |
4211 | QFETCH(QStringList, readProperties); |
4212 | QFETCH(QVariantList, readExpectedValues); |
4213 | QFETCH(QStringList, writeProperties); |
4214 | QFETCH(QVariantList, writeValues); |
4215 | QFETCH(QStringList, readBackProperties); |
4216 | QFETCH(QVariantList, readBackExpectedValues); |
4217 | |
4218 | QQmlEngine cleanEngine; // so tests don't interfere which each other, as singleton types are engine-singletons only. |
4219 | QQmlComponent component(&cleanEngine, testfile); |
4220 | |
4221 | if (!errorMessage.isEmpty()) |
4222 | QTest::ignoreMessage(type: QtWarningMsg, message: errorMessage.toLatin1().constData()); |
4223 | |
4224 | if (warningMessages.size()) |
4225 | foreach (const QString &warning, warningMessages) |
4226 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
4227 | |
4228 | QObject *object = component.create(); |
4229 | if (!errorMessage.isEmpty()) { |
4230 | QVERIFY(!object); |
4231 | } else { |
4232 | QVERIFY(object != nullptr); |
4233 | for (int i = 0; i < readProperties.size(); ++i) |
4234 | QCOMPARE(object->property(readProperties.at(i).toLatin1().constData()), readExpectedValues.at(i)); |
4235 | for (int i = 0; i < writeProperties.size(); ++i) |
4236 | QVERIFY(object->setProperty(writeProperties.at(i).toLatin1().constData(), writeValues.at(i))); |
4237 | for (int i = 0; i < readBackProperties.size(); ++i) |
4238 | QCOMPARE(object->property(readBackProperties.at(i).toLatin1().constData()), readBackExpectedValues.at(i)); |
4239 | delete object; |
4240 | } |
4241 | } |
4242 | |
4243 | void tst_qqmlecmascript::singletonTypeCaching_data() |
4244 | { |
4245 | QTest::addColumn<QUrl>(name: "testfile" ); |
4246 | QTest::addColumn<QStringList>(name: "readProperties" ); |
4247 | |
4248 | QTest::newRow(dataTag: "qobject, caching + read" ) |
4249 | << testFileUrl(fileName: "singletontype/qobjectSingletonTypeCaching.qml" ) |
4250 | << (QStringList() << "existingUriTest" << "qobjectParentedTest" ); |
4251 | |
4252 | QTest::newRow(dataTag: "script, caching + read" ) |
4253 | << testFileUrl(fileName: "singletontype/scriptSingletonTypeCaching.qml" ) |
4254 | << (QStringList() << "scriptTest" ); |
4255 | } |
4256 | |
4257 | void tst_qqmlecmascript::singletonTypeCaching() |
4258 | { |
4259 | QFETCH(QUrl, testfile); |
4260 | QFETCH(QStringList, readProperties); |
4261 | |
4262 | // ensure that the singleton type instances are cached per-engine. |
4263 | |
4264 | QQmlEngine cleanEngine; |
4265 | QQmlComponent component(&cleanEngine, testfile); |
4266 | QObject *object = component.create(); |
4267 | QVERIFY(object != nullptr); |
4268 | QList<QVariant> firstValues; |
4269 | QMetaObject::invokeMethod(obj: object, member: "modifyValues" ); |
4270 | for (int i = 0; i < readProperties.size(); ++i) |
4271 | firstValues << object->property(name: readProperties.at(i).toLatin1().constData()); |
4272 | delete object; |
4273 | |
4274 | QQmlComponent component2(&cleanEngine, testfile); |
4275 | QObject *object2 = component2.create(); |
4276 | QVERIFY(object2 != nullptr); |
4277 | for (int i = 0; i < readProperties.size(); ++i) |
4278 | QCOMPARE(object2->property(readProperties.at(i).toLatin1().constData()), firstValues.at(i)); // cached, shouldn't have changed. |
4279 | delete object2; |
4280 | } |
4281 | |
4282 | void tst_qqmlecmascript::singletonTypeImportOrder() |
4283 | { |
4284 | QQmlEngine engine; |
4285 | QQmlComponent component(&engine, testFileUrl(fileName: "singletontype/singletonTypeImportOrder.qml" )); |
4286 | QObject *object = component.create(); |
4287 | QVERIFY(object); |
4288 | QCOMPARE(object->property("v" ).toInt(), 1); |
4289 | delete object; |
4290 | } |
4291 | |
4292 | void tst_qqmlecmascript::singletonTypeResolution() |
4293 | { |
4294 | QQmlEngine engine; |
4295 | QQmlComponent component(&engine, testFileUrl(fileName: "singletontype/singletonTypeResolution.qml" )); |
4296 | QObject *object = component.create(); |
4297 | QVERIFY(object); |
4298 | QVERIFY(object->property("success" ).toBool()); |
4299 | delete object; |
4300 | } |
4301 | |
4302 | void tst_qqmlecmascript::verifyContextLifetime(QQmlContextData *ctxt) { |
4303 | QQmlContextData *childCtxt = ctxt->childContexts; |
4304 | |
4305 | if (!ctxt->importedScripts.isNullOrUndefined()) { |
4306 | QV4::ExecutionEngine *v4 = ctxt->engine->handle(); |
4307 | QV4::Scope scope(v4); |
4308 | QV4::ScopedArrayObject scripts(scope, ctxt->importedScripts.value()); |
4309 | QV4::Scoped<QV4::QQmlContextWrapper> qml(scope); |
4310 | for (quint32 i = 0; i < scripts->getLength(); ++i) { |
4311 | QQmlContextData *scriptContext, *newContext; |
4312 | qml = scripts->get(idx: i); |
4313 | |
4314 | scriptContext = qml ? qml->getContext() : nullptr; |
4315 | qml = QV4::Encode::undefined(); |
4316 | |
4317 | { |
4318 | QV4::Scope scope(v4); |
4319 | QV4::ScopedContext temporaryScope(scope, QV4::QmlContext::create(parent: scope.engine->rootContext(), context: scriptContext, scopeObject: nullptr)); |
4320 | Q_UNUSED(temporaryScope) |
4321 | } |
4322 | |
4323 | ctxt->engine->collectGarbage(); |
4324 | qml = scripts->get(idx: i); |
4325 | newContext = qml ? qml->getContext() : nullptr; |
4326 | QCOMPARE(scriptContext, newContext); |
4327 | } |
4328 | } |
4329 | |
4330 | while (childCtxt) { |
4331 | verifyContextLifetime(ctxt: childCtxt); |
4332 | |
4333 | childCtxt = childCtxt->nextChild; |
4334 | } |
4335 | } |
4336 | |
4337 | void tst_qqmlecmascript::importScripts_data() |
4338 | { |
4339 | QTest::addColumn<QUrl>(name: "testfile" ); |
4340 | QTest::addColumn<bool>(name: "compilationShouldSucceed" ); |
4341 | QTest::addColumn<QString>(name: "errorMessage" ); |
4342 | QTest::addColumn<QStringList>(name: "warningMessages" ); |
4343 | QTest::addColumn<QStringList>(name: "propertyNames" ); |
4344 | QTest::addColumn<QVariantList>(name: "propertyValues" ); |
4345 | |
4346 | QTest::newRow(dataTag: "basic functionality" ) |
4347 | << testFileUrl(fileName: "jsimport/testImport.qml" ) |
4348 | << true /* compilation should succeed */ |
4349 | << QString() |
4350 | << QStringList() |
4351 | << (QStringList() << QLatin1String("importedScriptStringValue" ) |
4352 | << QLatin1String("importedScriptFunctionValue" ) |
4353 | << QLatin1String("importedModuleAttachedPropertyValue" ) |
4354 | << QLatin1String("importedModuleEnumValue" )) |
4355 | << (QVariantList() << QVariant(QLatin1String("Hello, World!" )) |
4356 | << QVariant(20) |
4357 | << QVariant(19) |
4358 | << QVariant(2)); |
4359 | |
4360 | QTest::newRow(dataTag: "import scoping" ) |
4361 | << testFileUrl(fileName: "jsimport/testImportScoping.qml" ) |
4362 | << true /* compilation should succeed */ |
4363 | << QString() |
4364 | << QStringList() |
4365 | << (QStringList() << QLatin1String("componentError" )) |
4366 | << (QVariantList() << QVariant(5)); |
4367 | |
4368 | QTest::newRow(dataTag: "parent scope shouldn't be inherited by import with imports" ) |
4369 | << testFileUrl(fileName: "jsimportfail/failOne.qml" ) |
4370 | << true /* compilation should succeed */ |
4371 | << QString() |
4372 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/failOne.qml" ).toString() + QLatin1String(":6: TypeError: Cannot call method 'greetingString' of undefined" ))) |
4373 | << (QStringList() << QLatin1String("importScriptFunctionValue" )) |
4374 | << (QVariantList() << QVariant(QString())); |
4375 | |
4376 | QTest::newRow(dataTag: "javascript imports in an import should be private to the import scope" ) |
4377 | << testFileUrl(fileName: "jsimportfail/failTwo.qml" ) |
4378 | << true /* compilation should succeed */ |
4379 | << QString() |
4380 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/failTwo.qml" ).toString() + QLatin1String(":6: ReferenceError: ImportOneJs is not defined" ))) |
4381 | << (QStringList() << QLatin1String("importScriptFunctionValue" )) |
4382 | << (QVariantList() << QVariant(QString())); |
4383 | |
4384 | QTest::newRow(dataTag: "module imports in an import should be private to the import scope" ) |
4385 | << testFileUrl(fileName: "jsimportfail/failThree.qml" ) |
4386 | << true /* compilation should succeed */ |
4387 | << QString() |
4388 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/failThree.qml" ).toString() + QLatin1String(":7: TypeError: Cannot read property 'JsQtTest' of undefined" ))) |
4389 | << (QStringList() << QLatin1String("importedModuleAttachedPropertyValue" )) |
4390 | << (QVariantList() << QVariant(false)); |
4391 | |
4392 | QTest::newRow(dataTag: "typenames in an import should be private to the import scope" ) |
4393 | << testFileUrl(fileName: "jsimportfail/failFour.qml" ) |
4394 | << true /* compilation should succeed */ |
4395 | << QString() |
4396 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/failFour.qml" ).toString() + QLatin1String(":6: ReferenceError: JsQtTest is not defined" ))) |
4397 | << (QStringList() << QLatin1String("importedModuleEnumValue" )) |
4398 | << (QVariantList() << QVariant(0)); |
4399 | |
4400 | QTest::newRow(dataTag: "import with imports has it's own activation scope" ) |
4401 | << testFileUrl(fileName: "jsimportfail/failFive.qml" ) |
4402 | << true /* compilation should succeed */ |
4403 | << QString() |
4404 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/importWithImports.js" ).toString() + QLatin1String(":8: ReferenceError: Component is not defined" ))) |
4405 | << (QStringList() << QLatin1String("componentError" )) |
4406 | << (QVariantList() << QVariant(0)); |
4407 | |
4408 | QTest::newRow(dataTag: "import pragma library script" ) |
4409 | << testFileUrl(fileName: "jsimport/testImportPragmaLibrary.qml" ) |
4410 | << true /* compilation should succeed */ |
4411 | << QString() |
4412 | << QStringList() |
4413 | << (QStringList() << QLatin1String("testValue" )) |
4414 | << (QVariantList() << QVariant(31)); |
4415 | |
4416 | QTest::newRow(dataTag: "pragma library imports shouldn't inherit parent imports or scope" ) |
4417 | << testFileUrl(fileName: "jsimportfail/testImportPragmaLibrary.qml" ) |
4418 | << true /* compilation should succeed */ |
4419 | << QString() |
4420 | << (QStringList() << QString(testFileUrl(fileName: "jsimportfail/importPragmaLibrary.js" ).toString() + QLatin1String(":6: ReferenceError: Component is not defined" ))) |
4421 | << (QStringList() << QLatin1String("testValue" )) |
4422 | << (QVariantList() << QVariant(0)); |
4423 | |
4424 | QTest::newRow(dataTag: "import pragma library script which has an import" ) |
4425 | << testFileUrl(fileName: "jsimport/testImportPragmaLibraryWithImports.qml" ) |
4426 | << true /* compilation should succeed */ |
4427 | << QString() |
4428 | << QStringList() |
4429 | << (QStringList() << QLatin1String("testValue" )) |
4430 | << (QVariantList() << QVariant(55)); |
4431 | |
4432 | QTest::newRow(dataTag: "import pragma library script which has a pragma library import" ) |
4433 | << testFileUrl(fileName: "jsimport/testImportPragmaLibraryWithPragmaLibraryImports.qml" ) |
4434 | << true /* compilation should succeed */ |
4435 | << QString() |
4436 | << QStringList() |
4437 | << (QStringList() << QLatin1String("testValue" )) |
4438 | << (QVariantList() << QVariant(16)); |
4439 | |
4440 | QTest::newRow(dataTag: "import singleton type into js import" ) |
4441 | << testFileUrl(fileName: "jsimport/testImportSingletonType.qml" ) |
4442 | << true /* compilation should succeed */ |
4443 | << QString() |
4444 | << QStringList() |
4445 | << (QStringList() << QLatin1String("testValue" )) |
4446 | << (QVariantList() << QVariant(20)); |
4447 | |
4448 | QTest::newRow(dataTag: "import module which exports a script" ) |
4449 | << testFileUrl(fileName: "jsimport/testJsImport.qml" ) |
4450 | << true /* compilation should succeed */ |
4451 | << QString() |
4452 | << QStringList() |
4453 | << (QStringList() << QLatin1String("importedScriptStringValue" ) |
4454 | << QLatin1String("renamedScriptStringValue" ) |
4455 | << QLatin1String("reimportedScriptStringValue" )) |
4456 | << (QVariantList() << QVariant(QString("Hello" )) |
4457 | << QVariant(QString("Hello" )) |
4458 | << QVariant(QString("Hello" ))); |
4459 | |
4460 | QTest::newRow(dataTag: "import module which exports a script which imports a remote module" ) |
4461 | << testFileUrl(fileName: "jsimport/testJsRemoteImport.qml" ) |
4462 | << true /* compilation should succeed */ |
4463 | << QString() |
4464 | << QStringList() |
4465 | << (QStringList() << QLatin1String("importedScriptStringValue" ) |
4466 | << QLatin1String("renamedScriptStringValue" ) |
4467 | << QLatin1String("reimportedScriptStringValue" )) |
4468 | << (QVariantList() << QVariant(QString("Hello" )) |
4469 | << QVariant(QString("Hello" )) |
4470 | << QVariant(QString("Hello" ))); |
4471 | |
4472 | QTest::newRow(dataTag: "malformed import statement" ) |
4473 | << testFileUrl(fileName: "jsimportfail/malformedImport.qml" ) |
4474 | << false /* compilation should succeed */ |
4475 | << QString() |
4476 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedImport.js" ).toString() + QLatin1String(":1:2: Syntax error" )) |
4477 | << QStringList() |
4478 | << QVariantList(); |
4479 | |
4480 | QTest::newRow(dataTag: "malformed file name" ) |
4481 | << testFileUrl(fileName: "jsimportfail/malformedFile.qml" ) |
4482 | << false /* compilation should succeed */ |
4483 | << QString() |
4484 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedFile.js" ).toString() + QLatin1String(":1:9: Imported file must be a script" )) |
4485 | << QStringList() |
4486 | << QVariantList(); |
4487 | |
4488 | QTest::newRow(dataTag: "missing file qualifier" ) |
4489 | << testFileUrl(fileName: "jsimportfail/missingFileQualifier.qml" ) |
4490 | << false /* compilation should succeed */ |
4491 | << QString() |
4492 | << (QStringList() << testFileUrl(fileName: "jsimportfail/missingFileQualifier.js" ).toString() + QLatin1String(":1:1: File import requires a qualifier" )) |
4493 | << QStringList() |
4494 | << QVariantList(); |
4495 | |
4496 | QTest::newRow(dataTag: "malformed file qualifier" ) |
4497 | << testFileUrl(fileName: "jsimportfail/malformedFileQualifier.qml" ) |
4498 | << false /* compilation should succeed */ |
4499 | << QString() |
4500 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedFileQualifier.js" ).toString() + QLatin1String(":1:20: File import requires a qualifier" )) |
4501 | << QStringList() |
4502 | << QVariantList(); |
4503 | |
4504 | QTest::newRow(dataTag: "malformed module qualifier 2" ) |
4505 | << testFileUrl(fileName: "jsimportfail/malformedFileQualifier.2.qml" ) |
4506 | << false /* compilation should succeed */ |
4507 | << QString() |
4508 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedFileQualifier.2.js" ).toString() + QLatin1String(":1:23: Invalid import qualifier" )) |
4509 | << QStringList() |
4510 | << QVariantList(); |
4511 | |
4512 | QTest::newRow(dataTag: "malformed module uri" ) |
4513 | << testFileUrl(fileName: "jsimportfail/malformedModule.qml" ) |
4514 | << false /* compilation should succeed */ |
4515 | << QString() |
4516 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedModule.js" ).toString() + QLatin1String(":1:17: Invalid module URI" )) |
4517 | << QStringList() |
4518 | << QVariantList(); |
4519 | |
4520 | QTest::newRow(dataTag: "missing module version" ) |
4521 | << testFileUrl(fileName: "jsimportfail/missingModuleVersion.qml" ) |
4522 | << false /* compilation should succeed */ |
4523 | << QString() |
4524 | << (QStringList() << testFileUrl(fileName: "jsimportfail/missingModuleVersion.js" ).toString() + QLatin1String(":1:17: Module import requires a version" )) |
4525 | << QStringList() |
4526 | << QVariantList(); |
4527 | |
4528 | QTest::newRow(dataTag: "malformed module version" ) |
4529 | << testFileUrl(fileName: "jsimportfail/malformedModuleVersion.qml" ) |
4530 | << false /* compilation should succeed */ |
4531 | << QString() |
4532 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedModuleVersion.js" ).toString() + QLatin1String(":1:17: Module import requires a version" )) |
4533 | << QStringList() |
4534 | << QVariantList(); |
4535 | |
4536 | QTest::newRow(dataTag: "missing module qualifier" ) |
4537 | << testFileUrl(fileName: "jsimportfail/missingModuleQualifier.qml" ) |
4538 | << false /* compilation should succeed */ |
4539 | << QString() |
4540 | << (QStringList() << testFileUrl(fileName: "jsimportfail/missingModuleQualifier.js" ).toString() + QLatin1String(":1:1: Module import requires a qualifier" )) |
4541 | << QStringList() |
4542 | << QVariantList(); |
4543 | |
4544 | QTest::newRow(dataTag: "malformed module qualifier" ) |
4545 | << testFileUrl(fileName: "jsimportfail/malformedModuleQualifier.qml" ) |
4546 | << false /* compilation should succeed */ |
4547 | << QString() |
4548 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedModuleQualifier.js" ).toString() + QLatin1String(":1:21: Module import requires a qualifier" )) |
4549 | << QStringList() |
4550 | << QVariantList(); |
4551 | |
4552 | QTest::newRow(dataTag: "malformed module qualifier 2" ) |
4553 | << testFileUrl(fileName: "jsimportfail/malformedModuleQualifier.2.qml" ) |
4554 | << false /* compilation should succeed */ |
4555 | << QString() |
4556 | << (QStringList() << testFileUrl(fileName: "jsimportfail/malformedModuleQualifier.2.js" ).toString() + QLatin1String(":1:24: Invalid import qualifier" )) |
4557 | << QStringList() |
4558 | << QVariantList(); |
4559 | } |
4560 | |
4561 | void tst_qqmlecmascript::importScripts() |
4562 | { |
4563 | QFETCH(QUrl, testfile); |
4564 | QFETCH(bool, compilationShouldSucceed); |
4565 | QFETCH(QString, errorMessage); |
4566 | QFETCH(QStringList, warningMessages); // error messages if !compilationShouldSucceed |
4567 | QFETCH(QStringList, propertyNames); |
4568 | QFETCH(QVariantList, propertyValues); |
4569 | |
4570 | ThreadedTestHTTPServer server(dataDirectory() + "/remote" ); |
4571 | |
4572 | QQmlEngine engine; |
4573 | QString dataDir(dataDirectory() + QLatin1Char('/') + QLatin1String("lib" )); |
4574 | engine.addImportPath(dir: dataDir); |
4575 | |
4576 | QStringList importPathList = engine.importPathList(); |
4577 | |
4578 | QString remotePath(server.urlString(documentPath: "/" )); |
4579 | engine.addImportPath(dir: remotePath); |
4580 | |
4581 | QQmlComponent component(&engine, testfile); |
4582 | |
4583 | if (!errorMessage.isEmpty()) |
4584 | QTest::ignoreMessage(type: QtWarningMsg, message: errorMessage.toLatin1().constData()); |
4585 | |
4586 | if (compilationShouldSucceed && warningMessages.size()) |
4587 | foreach (const QString &warning, warningMessages) |
4588 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
4589 | |
4590 | if (compilationShouldSucceed) |
4591 | QTRY_VERIFY(component.isReady()); |
4592 | else { |
4593 | QVERIFY(component.isError()); |
4594 | QCOMPARE(warningMessages.size(), 1); |
4595 | QCOMPARE(component.errors().count(), 2); |
4596 | QCOMPARE(component.errors().at(1).toString(), warningMessages.first()); |
4597 | return; |
4598 | } |
4599 | |
4600 | QObject *object = component.create(); |
4601 | if (!errorMessage.isEmpty()) { |
4602 | QVERIFY(!object); |
4603 | } else { |
4604 | QVERIFY(object != nullptr); |
4605 | |
4606 | QQmlContextData *ctxt = QQmlContextData::get(context: engine.rootContext()); |
4607 | tst_qqmlecmascript::verifyContextLifetime(ctxt); |
4608 | |
4609 | for (int i = 0; i < propertyNames.size(); ++i) |
4610 | QCOMPARE(object->property(propertyNames.at(i).toLatin1().constData()), propertyValues.at(i)); |
4611 | delete object; |
4612 | } |
4613 | |
4614 | engine.setImportPathList(importPathList); |
4615 | } |
4616 | |
4617 | void tst_qqmlecmascript::importCreationContext() |
4618 | { |
4619 | QQmlEngine engine; |
4620 | QQmlComponent component(&engine, testFileUrl(fileName: "jsimport/creationContext.qml" )); |
4621 | QScopedPointer<QObject> object(component.create()); |
4622 | QVERIFY(!object.isNull()); |
4623 | bool success = object->property(name: "success" ).toBool(); |
4624 | if (!success) { |
4625 | QSignalSpy readySpy(object.data(), SIGNAL(loaded())); |
4626 | readySpy.wait(); |
4627 | } |
4628 | success = object->property(name: "success" ).toBool(); |
4629 | QVERIFY(success); |
4630 | } |
4631 | |
4632 | void tst_qqmlecmascript::scarceResources_other() |
4633 | { |
4634 | /* These tests require knowledge of state, since we test values after |
4635 | performing signal or function invocation. */ |
4636 | |
4637 | QPixmap origPixmap(100, 100); |
4638 | origPixmap.fill(fillColor: Qt::blue); |
4639 | QString srp_name, expectedWarning; |
4640 | QQmlEngine engine; |
4641 | QV4::ExecutionEngine *v4 = engine.handle(); |
4642 | ScarceResourceObject *eo = nullptr; |
4643 | QObject *srsc = nullptr; |
4644 | QObject *object = nullptr; |
4645 | |
4646 | /* property var semantics */ |
4647 | |
4648 | // test that scarce resources are handled properly in signal invocation |
4649 | QQmlComponent varComponentTen(&engine, testFileUrl(fileName: "scarceResourceSignal.var.qml" )); |
4650 | object = varComponentTen.create(); |
4651 | srsc = object->findChild<QObject*>(aName: "srsc" ); |
4652 | QVERIFY(srsc); |
4653 | QVERIFY(!srsc->property("scarceResourceCopy" ).isValid()); // hasn't been instantiated yet. |
4654 | QCOMPARE(srsc->property("width" ), QVariant(5)); // default value is 5. |
4655 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4656 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4657 | QMetaObject::invokeMethod(obj: srsc, member: "testSignal" ); |
4658 | QVERIFY(!srsc->property("scarceResourceCopy" ).isValid()); // still hasn't been instantiated |
4659 | QCOMPARE(srsc->property("width" ), QVariant(10)); // but width was assigned to 10. |
4660 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4661 | QVERIFY(eo->scarceResourceIsDetached()); // should still be no other copies of it at this stage. |
4662 | QMetaObject::invokeMethod(obj: srsc, member: "testSignal2" ); // assigns scarceResourceCopy to the scarce pixmap. |
4663 | QVERIFY(srsc->property("scarceResourceCopy" ).isValid()); |
4664 | QCOMPARE(srsc->property("scarceResourceCopy" ).value<QPixmap>(), origPixmap); |
4665 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4666 | QVERIFY(!(eo->scarceResourceIsDetached())); // should be another copy of the resource now. |
4667 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4668 | delete object; |
4669 | |
4670 | // test that scarce resources are handled properly from js functions in qml files |
4671 | QQmlComponent varComponentEleven(&engine, testFileUrl(fileName: "scarceResourceFunction.var.qml" )); |
4672 | object = varComponentEleven.create(); |
4673 | QVERIFY(object != nullptr); |
4674 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // not yet assigned, so should not be valid |
4675 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4676 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4677 | QMetaObject::invokeMethod(obj: object, member: "retrieveScarceResource" ); |
4678 | QVERIFY(object->property("scarceResourceCopy" ).isValid()); // assigned, so should be valid. |
4679 | QCOMPARE(object->property("scarceResourceCopy" ).value<QPixmap>(), origPixmap); |
4680 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4681 | QVERIFY(!eo->scarceResourceIsDetached()); // should be a copy of the resource at this stage. |
4682 | QMetaObject::invokeMethod(obj: object, member: "releaseScarceResource" ); |
4683 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // just released, so should not be valid |
4684 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4685 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4686 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4687 | delete object; |
4688 | |
4689 | // test that if an exception occurs while invoking js function from cpp, that the resources are released. |
4690 | QQmlComponent varComponentTwelve(&engine, testFileUrl(fileName: "scarceResourceFunctionFail.var.qml" )); |
4691 | object = varComponentTwelve.create(); |
4692 | QVERIFY(object != nullptr); |
4693 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // not yet assigned, so should not be valid |
4694 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4695 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4696 | expectedWarning = varComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function" ); |
4697 | expectedWarning = expectedWarning.arg(a: QString::number(quintptr(eo), base: 16)); |
4698 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
4699 | QMetaObject::invokeMethod(obj: object, member: "retrieveScarceResource" ); |
4700 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // due to exception, assignment will NOT have occurred. |
4701 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4702 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4703 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4704 | delete object; |
4705 | |
4706 | // test that if an Item which has JS ownership but has a scarce resource property is garbage collected, |
4707 | // that the scarce resource is removed from the engine's list of scarce resources to clean up. |
4708 | QQmlComponent varComponentThirteen(&engine, testFileUrl(fileName: "scarceResourceObjectGc.var.qml" )); |
4709 | object = varComponentThirteen.create(); |
4710 | QVERIFY(object != nullptr); |
4711 | QVERIFY(!object->property("varProperty" ).isValid()); // not assigned yet |
4712 | QMetaObject::invokeMethod(obj: object, member: "assignVarProperty" ); |
4713 | QVERIFY(v4->scarceResources.isEmpty()); // the scarce resource is a VME property. |
4714 | QMetaObject::invokeMethod(obj: object, member: "deassignVarProperty" ); |
4715 | gc(engine); |
4716 | QVERIFY(v4->scarceResources.isEmpty()); // should still be empty; the resource should have been released on gc. |
4717 | delete object; |
4718 | |
4719 | /* property variant semantics */ |
4720 | |
4721 | // test that scarce resources are handled properly in signal invocation |
4722 | QQmlComponent variantComponentTen(&engine, testFileUrl(fileName: "scarceResourceSignal.variant.qml" )); |
4723 | object = variantComponentTen.create(); |
4724 | QVERIFY(object != nullptr); |
4725 | srsc = object->findChild<QObject*>(aName: "srsc" ); |
4726 | QVERIFY(srsc); |
4727 | QVERIFY(!srsc->property("scarceResourceCopy" ).isValid()); // hasn't been instantiated yet. |
4728 | QCOMPARE(srsc->property("width" ), QVariant(5)); // default value is 5. |
4729 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4730 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4731 | QMetaObject::invokeMethod(obj: srsc, member: "testSignal" ); |
4732 | QVERIFY(!srsc->property("scarceResourceCopy" ).isValid()); // still hasn't been instantiated |
4733 | QCOMPARE(srsc->property("width" ), QVariant(10)); // but width was assigned to 10. |
4734 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4735 | QVERIFY(eo->scarceResourceIsDetached()); // should still be no other copies of it at this stage. |
4736 | QMetaObject::invokeMethod(obj: srsc, member: "testSignal2" ); // assigns scarceResourceCopy to the scarce pixmap. |
4737 | QVERIFY(srsc->property("scarceResourceCopy" ).isValid()); |
4738 | QCOMPARE(srsc->property("scarceResourceCopy" ).value<QPixmap>(), origPixmap); |
4739 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4740 | QVERIFY(!(eo->scarceResourceIsDetached())); // should be another copy of the resource now. |
4741 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4742 | delete object; |
4743 | |
4744 | // test that scarce resources are handled properly from js functions in qml files |
4745 | QQmlComponent variantComponentEleven(&engine, testFileUrl(fileName: "scarceResourceFunction.variant.qml" )); |
4746 | object = variantComponentEleven.create(); |
4747 | QVERIFY(object != nullptr); |
4748 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // not yet assigned, so should not be valid |
4749 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4750 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4751 | QMetaObject::invokeMethod(obj: object, member: "retrieveScarceResource" ); |
4752 | QVERIFY(object->property("scarceResourceCopy" ).isValid()); // assigned, so should be valid. |
4753 | QCOMPARE(object->property("scarceResourceCopy" ).value<QPixmap>(), origPixmap); |
4754 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4755 | QVERIFY(!eo->scarceResourceIsDetached()); // should be a copy of the resource at this stage. |
4756 | QMetaObject::invokeMethod(obj: object, member: "releaseScarceResource" ); |
4757 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // just released, so should not be valid |
4758 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4759 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4760 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4761 | delete object; |
4762 | |
4763 | // test that if an exception occurs while invoking js function from cpp, that the resources are released. |
4764 | QQmlComponent variantComponentTwelve(&engine, testFileUrl(fileName: "scarceResourceFunctionFail.variant.qml" )); |
4765 | object = variantComponentTwelve.create(); |
4766 | QVERIFY(object != nullptr); |
4767 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // not yet assigned, so should not be valid |
4768 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4769 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4770 | expectedWarning = variantComponentTwelve.url().toString() + QLatin1String(":16: TypeError: Property 'scarceResource' of object ScarceResourceObject(0x%1) is not a function" ); |
4771 | expectedWarning = expectedWarning.arg(a: QString::number(quintptr(eo), base: 16)); |
4772 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(expectedWarning)); // we expect a meaningful warning to be printed. |
4773 | QMetaObject::invokeMethod(obj: object, member: "retrieveScarceResource" ); |
4774 | QVERIFY(!object->property("scarceResourceCopy" ).isValid()); // due to exception, assignment will NOT have occurred. |
4775 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
4776 | QVERIFY(eo->scarceResourceIsDetached()); // should be no other copies of it at this stage. |
4777 | QVERIFY(v4->scarceResources.isEmpty()); // should have been released by this point. |
4778 | delete object; |
4779 | } |
4780 | |
4781 | void tst_qqmlecmascript::scarceResources_data() |
4782 | { |
4783 | QTest::addColumn<QUrl>(name: "qmlFile" ); |
4784 | QTest::addColumn<bool>(name: "readDetachStatus" ); |
4785 | QTest::addColumn<bool>(name: "expectedDetachStatus" ); |
4786 | QTest::addColumn<QStringList>(name: "propertyNames" ); |
4787 | QTest::addColumn<QVariantList>(name: "expectedValidity" ); |
4788 | QTest::addColumn<QVariantList>(name: "expectedValues" ); |
4789 | QTest::addColumn<QStringList>(name: "expectedErrors" ); |
4790 | |
4791 | QPixmap origPixmap(100, 100); |
4792 | origPixmap.fill(fillColor: Qt::blue); |
4793 | |
4794 | /* property var semantics */ |
4795 | |
4796 | // in the following three cases, the instance created from the component |
4797 | // has a property which is a copy of the scarce resource; hence, the |
4798 | // resource should NOT be detached prior to deletion of the object instance, |
4799 | // unless the resource is destroyed explicitly. |
4800 | QTest::newRow(dataTag: "var: import scarce resource copy directly" ) |
4801 | << testFileUrl(fileName: "scarceResourceCopy.var.qml" ) |
4802 | << true |
4803 | << false // won't be detached, because assigned to property and not explicitly released |
4804 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4805 | << (QList<QVariant>() << true) |
4806 | << (QList<QVariant>() << origPixmap) |
4807 | << QStringList(); |
4808 | |
4809 | QTest::newRow(dataTag: "var: import scarce resource copy from JS" ) |
4810 | << testFileUrl(fileName: "scarceResourceCopyFromJs.var.qml" ) |
4811 | << true |
4812 | << false // won't be detached, because assigned to property and not explicitly released |
4813 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4814 | << (QList<QVariant>() << true) |
4815 | << (QList<QVariant>() << origPixmap) |
4816 | << QStringList(); |
4817 | |
4818 | QTest::newRow(dataTag: "var: import released scarce resource copy from JS" ) |
4819 | << testFileUrl(fileName: "scarceResourceDestroyedCopy.var.qml" ) |
4820 | << true |
4821 | << true // explicitly released, so it will be detached |
4822 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4823 | << (QList<QVariant>() << false) |
4824 | << (QList<QVariant>() << QVariant()) |
4825 | << QStringList(); |
4826 | |
4827 | // in the following three cases, no other copy should exist in memory, |
4828 | // and so it should be detached (unless explicitly preserved). |
4829 | QTest::newRow(dataTag: "var: import auto-release SR from JS in binding side-effect" ) |
4830 | << testFileUrl(fileName: "scarceResourceTest.var.qml" ) |
4831 | << true |
4832 | << true // auto released, so it will be detached |
4833 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4834 | << (QList<QVariant>() << true) |
4835 | << (QList<QVariant>() << QVariant(100)) |
4836 | << QStringList(); |
4837 | QTest::newRow(dataTag: "var: import explicit-preserve SR from JS in binding side-effect" ) |
4838 | << testFileUrl(fileName: "scarceResourceTestPreserve.var.qml" ) |
4839 | << true |
4840 | << false // won't be detached because we explicitly preserve it |
4841 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4842 | << (QList<QVariant>() << true) |
4843 | << (QList<QVariant>() << QVariant(100)) |
4844 | << QStringList(); |
4845 | QTest::newRow(dataTag: "var: import explicit-preserve SR from JS in binding side-effect" ) |
4846 | << testFileUrl(fileName: "scarceResourceTestMultiple.var.qml" ) |
4847 | << true |
4848 | << true // will be detached because all resources were released manually or automatically. |
4849 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4850 | << (QList<QVariant>() << true) |
4851 | << (QList<QVariant>() << QVariant(100)) |
4852 | << QStringList(); |
4853 | |
4854 | // In the following three cases, test that scarce resources are handled |
4855 | // correctly for imports. |
4856 | QTest::newRow(dataTag: "var: import with no binding" ) |
4857 | << testFileUrl(fileName: "scarceResourceCopyImportNoBinding.var.qml" ) |
4858 | << false // cannot check detach status. |
4859 | << false |
4860 | << QStringList() |
4861 | << QList<QVariant>() |
4862 | << QList<QVariant>() |
4863 | << QStringList(); |
4864 | QTest::newRow(dataTag: "var: import with binding without explicit preserve" ) |
4865 | << testFileUrl(fileName: "scarceResourceCopyImportNoBinding.var.qml" ) |
4866 | << false |
4867 | << false |
4868 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4869 | << (QList<QVariant>() << false) // will have been released prior to evaluation of binding. |
4870 | << (QList<QVariant>() << QVariant()) |
4871 | << QStringList(); |
4872 | QTest::newRow(dataTag: "var: import with explicit release after binding evaluation" ) |
4873 | << testFileUrl(fileName: "scarceResourceCopyImport.var.qml" ) |
4874 | << false |
4875 | << false |
4876 | << (QStringList() << QLatin1String("scarceResourceImportedCopy" ) << QLatin1String("scarceResourceAssignedCopyOne" ) << QLatin1String("scarceResourceAssignedCopyTwo" ) << QLatin1String("arePropertiesEqual" )) |
4877 | << (QList<QVariant>() << false << false << false << true) // since property var = JS object reference, by releasing the provider's resource, all handles are invalidated. |
4878 | << (QList<QVariant>() << QVariant() << QVariant() << QVariant() << QVariant(true)) |
4879 | << QStringList(); |
4880 | QTest::newRow(dataTag: "var: import with different js objects" ) |
4881 | << testFileUrl(fileName: "scarceResourceCopyImportDifferent.var.qml" ) |
4882 | << false |
4883 | << false |
4884 | << (QStringList() << QLatin1String("scarceResourceAssignedCopyOne" ) << QLatin1String("scarceResourceAssignedCopyTwo" ) << QLatin1String("arePropertiesEqual" )) |
4885 | << (QList<QVariant>() << false << true << true) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object. |
4886 | << (QList<QVariant>() << QVariant() << QVariant(origPixmap) << QVariant(false)) |
4887 | << QStringList(); |
4888 | QTest::newRow(dataTag: "var: import with different js objects and explicit release" ) |
4889 | << testFileUrl(fileName: "scarceResourceMultipleDifferentNoBinding.var.qml" ) |
4890 | << false |
4891 | << false |
4892 | << (QStringList() << QLatin1String("resourceOne" ) << QLatin1String("resourceTwo" )) |
4893 | << (QList<QVariant>() << true << false) // invalidating one shouldn't invalidate the other, because they're not references to the same JS object. |
4894 | << (QList<QVariant>() << QVariant(origPixmap) << QVariant()) |
4895 | << QStringList(); |
4896 | QTest::newRow(dataTag: "var: import with same js objects and explicit release" ) |
4897 | << testFileUrl(fileName: "scarceResourceMultipleSameNoBinding.var.qml" ) |
4898 | << false |
4899 | << false |
4900 | << (QStringList() << QLatin1String("resourceOne" ) << QLatin1String("resourceTwo" )) |
4901 | << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object. |
4902 | << (QList<QVariant>() << QVariant() << QVariant()) |
4903 | << QStringList(); |
4904 | QTest::newRow(dataTag: "var: binding with same js objects and explicit release" ) |
4905 | << testFileUrl(fileName: "scarceResourceMultipleSameWithBinding.var.qml" ) |
4906 | << false |
4907 | << false |
4908 | << (QStringList() << QLatin1String("resourceOne" ) << QLatin1String("resourceTwo" )) |
4909 | << (QList<QVariant>() << false << false) // invalidating one should invalidate the other, because they're references to the same JS object. |
4910 | << (QList<QVariant>() << QVariant() << QVariant()) |
4911 | << QStringList(); |
4912 | |
4913 | |
4914 | /* property variant semantics */ |
4915 | |
4916 | // in the following three cases, the instance created from the component |
4917 | // has a property which is a copy of the scarce resource; hence, the |
4918 | // resource should NOT be detached prior to deletion of the object instance, |
4919 | // unless the resource is destroyed explicitly. |
4920 | QTest::newRow(dataTag: "variant: import scarce resource copy directly" ) |
4921 | << testFileUrl(fileName: "scarceResourceCopy.variant.qml" ) |
4922 | << true |
4923 | << false // won't be detached, because assigned to property and not explicitly released |
4924 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4925 | << (QList<QVariant>() << true) |
4926 | << (QList<QVariant>() << origPixmap) |
4927 | << QStringList(); |
4928 | |
4929 | QTest::newRow(dataTag: "variant: import scarce resource copy from JS" ) |
4930 | << testFileUrl(fileName: "scarceResourceCopyFromJs.variant.qml" ) |
4931 | << true |
4932 | << false // won't be detached, because assigned to property and not explicitly released |
4933 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4934 | << (QList<QVariant>() << true) |
4935 | << (QList<QVariant>() << origPixmap) |
4936 | << QStringList(); |
4937 | |
4938 | QTest::newRow(dataTag: "variant: import released scarce resource copy from JS" ) |
4939 | << testFileUrl(fileName: "scarceResourceDestroyedCopy.variant.qml" ) |
4940 | << true |
4941 | << true // explicitly released, so it will be detached |
4942 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4943 | << (QList<QVariant>() << false) |
4944 | << (QList<QVariant>() << QVariant()) |
4945 | << QStringList(); |
4946 | |
4947 | // in the following three cases, no other copy should exist in memory, |
4948 | // and so it should be detached (unless explicitly preserved). |
4949 | QTest::newRow(dataTag: "variant: import auto-release SR from JS in binding side-effect" ) |
4950 | << testFileUrl(fileName: "scarceResourceTest.variant.qml" ) |
4951 | << true |
4952 | << true // auto released, so it will be detached |
4953 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4954 | << (QList<QVariant>() << true) |
4955 | << (QList<QVariant>() << QVariant(100)) |
4956 | << QStringList(); |
4957 | QTest::newRow(dataTag: "variant: import explicit-preserve SR from JS in binding side-effect" ) |
4958 | << testFileUrl(fileName: "scarceResourceTestPreserve.variant.qml" ) |
4959 | << true |
4960 | << false // won't be detached because we explicitly preserve it |
4961 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4962 | << (QList<QVariant>() << true) |
4963 | << (QList<QVariant>() << QVariant(100)) |
4964 | << QStringList(); |
4965 | QTest::newRow(dataTag: "variant: import multiple scarce resources" ) |
4966 | << testFileUrl(fileName: "scarceResourceTestMultiple.variant.qml" ) |
4967 | << true |
4968 | << true // will be detached because all resources were released manually or automatically. |
4969 | << (QStringList() << QLatin1String("scarceResourceTest" )) |
4970 | << (QList<QVariant>() << true) |
4971 | << (QList<QVariant>() << QVariant(100)) |
4972 | << QStringList(); |
4973 | |
4974 | // In the following three cases, test that scarce resources are handled |
4975 | // correctly for imports. |
4976 | QTest::newRow(dataTag: "variant: import with no binding" ) |
4977 | << testFileUrl(fileName: "scarceResourceCopyImportNoBinding.variant.qml" ) |
4978 | << false // cannot check detach status. |
4979 | << false |
4980 | << QStringList() |
4981 | << QList<QVariant>() |
4982 | << QList<QVariant>() |
4983 | << QStringList(); |
4984 | QTest::newRow(dataTag: "variant: import with binding without explicit preserve" ) |
4985 | << testFileUrl(fileName: "scarceResourceCopyImportNoBinding.variant.qml" ) |
4986 | << false |
4987 | << false |
4988 | << (QStringList() << QLatin1String("scarceResourceCopy" )) |
4989 | << (QList<QVariant>() << false) // will have been released prior to evaluation of binding. |
4990 | << (QList<QVariant>() << QVariant()) |
4991 | << QStringList(); |
4992 | QTest::newRow(dataTag: "variant: import with explicit release after binding evaluation" ) |
4993 | << testFileUrl(fileName: "scarceResourceCopyImport.variant.qml" ) |
4994 | << false |
4995 | << false |
4996 | << (QStringList() << QLatin1String("scarceResourceImportedCopy" ) << QLatin1String("scarceResourceAssignedCopyOne" ) << QLatin1String("scarceResourceAssignedCopyTwo" )) |
4997 | << (QList<QVariant>() << true << true << false) // since property variant = variant copy, releasing the provider's resource does not invalidate previously assigned copies. |
4998 | << (QList<QVariant>() << origPixmap << origPixmap << QVariant()) |
4999 | << QStringList(); |
5000 | } |
5001 | |
5002 | void tst_qqmlecmascript::scarceResources() |
5003 | { |
5004 | QFETCH(QUrl, qmlFile); |
5005 | QFETCH(bool, readDetachStatus); |
5006 | QFETCH(bool, expectedDetachStatus); |
5007 | QFETCH(QStringList, propertyNames); |
5008 | QFETCH(QVariantList, expectedValidity); |
5009 | QFETCH(QVariantList, expectedValues); |
5010 | QFETCH(QStringList, expectedErrors); |
5011 | |
5012 | QQmlEngine engine; |
5013 | QV4::ExecutionEngine *v4 = engine.handle(); |
5014 | ScarceResourceObject *eo = nullptr; |
5015 | QObject *object = nullptr; |
5016 | |
5017 | QQmlComponent c(&engine, qmlFile); |
5018 | object = c.create(); |
5019 | QVERIFY(object != nullptr); |
5020 | for (int i = 0; i < propertyNames.size(); ++i) { |
5021 | QString prop = propertyNames.at(i); |
5022 | bool validity = expectedValidity.at(i).toBool(); |
5023 | QVariant value = expectedValues.at(i); |
5024 | |
5025 | QCOMPARE(object->property(prop.toLatin1().constData()).isValid(), validity); |
5026 | if (value.type() == QVariant::Int) { |
5027 | QCOMPARE(object->property(prop.toLatin1().constData()).toInt(), value.toInt()); |
5028 | } else if (value.type() == QVariant::Pixmap) { |
5029 | QCOMPARE(object->property(prop.toLatin1().constData()).value<QPixmap>(), value.value<QPixmap>()); |
5030 | } |
5031 | } |
5032 | |
5033 | if (readDetachStatus) { |
5034 | eo = qobject_cast<ScarceResourceObject*>(object: QQmlProperty::read(object, "a" ).value<QObject*>()); |
5035 | QCOMPARE(eo->scarceResourceIsDetached(), expectedDetachStatus); |
5036 | } |
5037 | |
5038 | QVERIFY(v4->scarceResources.isEmpty()); |
5039 | delete object; |
5040 | } |
5041 | |
5042 | void tst_qqmlecmascript::propertyChangeSlots() |
5043 | { |
5044 | // ensure that allowable property names are allowed and onPropertyNameChanged slots are generated correctly. |
5045 | QQmlEngine engine; |
5046 | QQmlComponent component(&engine, testFileUrl(fileName: "changeslots/propertyChangeSlots.qml" )); |
5047 | QObject *object = component.create(); |
5048 | QVERIFY(object != nullptr); |
5049 | delete object; |
5050 | |
5051 | // ensure that invalid property names fail properly. |
5052 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
5053 | QQmlComponent e1(&engine, testFileUrl(fileName: "changeslots/propertyChangeSlotErrors.1.qml" )); |
5054 | QString expectedErrorString = e1.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_nameWithUnderscoreChanged\"" ); |
5055 | QCOMPARE(e1.errors().at(0).toString(), expectedErrorString); |
5056 | object = e1.create(); |
5057 | QVERIFY(!object); |
5058 | delete object; |
5059 | |
5060 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
5061 | QQmlComponent e2(&engine, testFileUrl(fileName: "changeslots/propertyChangeSlotErrors.2.qml" )); |
5062 | expectedErrorString = e2.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on____nameWithUnderscoresChanged\"" ); |
5063 | QCOMPARE(e2.errors().at(0).toString(), expectedErrorString); |
5064 | object = e2.create(); |
5065 | QVERIFY(!object); |
5066 | delete object; |
5067 | |
5068 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
5069 | QQmlComponent e3(&engine, testFileUrl(fileName: "changeslots/propertyChangeSlotErrors.3.qml" )); |
5070 | expectedErrorString = e3.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on$NameWithDollarsignChanged\"" ); |
5071 | QCOMPARE(e3.errors().at(0).toString(), expectedErrorString); |
5072 | object = e3.create(); |
5073 | QVERIFY(!object); |
5074 | delete object; |
5075 | |
5076 | QTest::ignoreMessage(type: QtWarningMsg, message: "QQmlComponent: Component is not ready" ); |
5077 | QQmlComponent e4(&engine, testFileUrl(fileName: "changeslots/propertyChangeSlotErrors.4.qml" )); |
5078 | expectedErrorString = e4.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_6NameWithUnderscoreNumberChanged\"" ); |
5079 | QCOMPARE(e4.errors().at(0).toString(), expectedErrorString); |
5080 | object = e4.create(); |
5081 | QVERIFY(!object); |
5082 | delete object; |
5083 | } |
5084 | |
5085 | void tst_qqmlecmascript::propertyVar_data() |
5086 | { |
5087 | QTest::addColumn<QUrl>(name: "qmlFile" ); |
5088 | |
5089 | // valid |
5090 | QTest::newRow(dataTag: "non-bindable object subproperty changed" ) << testFileUrl(fileName: "propertyVar.1.qml" ); |
5091 | QTest::newRow(dataTag: "non-bindable object changed" ) << testFileUrl(fileName: "propertyVar.2.qml" ); |
5092 | QTest::newRow(dataTag: "primitive changed" ) << testFileUrl(fileName: "propertyVar.3.qml" ); |
5093 | QTest::newRow(dataTag: "javascript array modification" ) << testFileUrl(fileName: "propertyVar.4.qml" ); |
5094 | QTest::newRow(dataTag: "javascript map modification" ) << testFileUrl(fileName: "propertyVar.5.qml" ); |
5095 | QTest::newRow(dataTag: "javascript array assignment" ) << testFileUrl(fileName: "propertyVar.6.qml" ); |
5096 | QTest::newRow(dataTag: "javascript map assignment" ) << testFileUrl(fileName: "propertyVar.7.qml" ); |
5097 | QTest::newRow(dataTag: "literal property assignment" ) << testFileUrl(fileName: "propertyVar.8.qml" ); |
5098 | QTest::newRow(dataTag: "qobject property assignment" ) << testFileUrl(fileName: "propertyVar.9.qml" ); |
5099 | QTest::newRow(dataTag: "base class var property assignment" ) << testFileUrl(fileName: "propertyVar.10.qml" ); |
5100 | QTest::newRow(dataTag: "javascript function assignment" ) << testFileUrl(fileName: "propertyVar.11.qml" ); |
5101 | QTest::newRow(dataTag: "javascript special assignment" ) << testFileUrl(fileName: "propertyVar.12.qml" ); |
5102 | QTest::newRow(dataTag: "declarative binding assignment" ) << testFileUrl(fileName: "propertyVar.13.qml" ); |
5103 | QTest::newRow(dataTag: "imperative binding assignment" ) << testFileUrl(fileName: "propertyVar.14.qml" ); |
5104 | QTest::newRow(dataTag: "stored binding assignment" ) << testFileUrl(fileName: "propertyVar.15.qml" ); |
5105 | QTest::newRow(dataTag: "function expression binding assignment" ) << testFileUrl(fileName: "propertyVar.16.qml" ); |
5106 | } |
5107 | |
5108 | void tst_qqmlecmascript::propertyVar() |
5109 | { |
5110 | QFETCH(QUrl, qmlFile); |
5111 | |
5112 | QQmlEngine engine; |
5113 | QQmlComponent component(&engine, qmlFile); |
5114 | QObject *object = component.create(); |
5115 | QVERIFY(object != nullptr); |
5116 | |
5117 | QCOMPARE(object->property("test" ).toBool(), true); |
5118 | |
5119 | delete object; |
5120 | } |
5121 | |
5122 | void tst_qqmlecmascript::propertyQJSValue_data() |
5123 | { |
5124 | QTest::addColumn<QUrl>(name: "qmlFile" ); |
5125 | |
5126 | // valid |
5127 | QTest::newRow(dataTag: "non-bindable object subproperty changed" ) << testFileUrl(fileName: "propertyQJSValue.1.qml" ); |
5128 | QTest::newRow(dataTag: "non-bindable object changed" ) << testFileUrl(fileName: "propertyQJSValue.2.qml" ); |
5129 | QTest::newRow(dataTag: "primitive changed" ) << testFileUrl(fileName: "propertyQJSValue.3.qml" ); |
5130 | QTest::newRow(dataTag: "javascript array modification" ) << testFileUrl(fileName: "propertyQJSValue.4.qml" ); |
5131 | QTest::newRow(dataTag: "javascript map modification" ) << testFileUrl(fileName: "propertyQJSValue.5.qml" ); |
5132 | QTest::newRow(dataTag: "javascript array assignment" ) << testFileUrl(fileName: "propertyQJSValue.6.qml" ); |
5133 | QTest::newRow(dataTag: "javascript map assignment" ) << testFileUrl(fileName: "propertyQJSValue.7.qml" ); |
5134 | QTest::newRow(dataTag: "literal property assignment" ) << testFileUrl(fileName: "propertyQJSValue.8.qml" ); |
5135 | QTest::newRow(dataTag: "qobject property assignment" ) << testFileUrl(fileName: "propertyQJSValue.9.qml" ); |
5136 | QTest::newRow(dataTag: "base class var property assignment" ) << testFileUrl(fileName: "propertyQJSValue.10.qml" ); |
5137 | QTest::newRow(dataTag: "javascript function assignment" ) << testFileUrl(fileName: "propertyQJSValue.11.qml" ); |
5138 | QTest::newRow(dataTag: "javascript special assignment" ) << testFileUrl(fileName: "propertyQJSValue.12.qml" ); |
5139 | QTest::newRow(dataTag: "declarative binding assignment" ) << testFileUrl(fileName: "propertyQJSValue.13.qml" ); |
5140 | QTest::newRow(dataTag: "imperative binding assignment" ) << testFileUrl(fileName: "propertyQJSValue.14.qml" ); |
5141 | QTest::newRow(dataTag: "stored binding assignment" ) << testFileUrl(fileName: "propertyQJSValue.15.qml" ); |
5142 | QTest::newRow(dataTag: "javascript function binding" ) << testFileUrl(fileName: "propertyQJSValue.16.qml" ); |
5143 | |
5144 | QTest::newRow(dataTag: "reset property" ) << testFileUrl(fileName: "propertyQJSValue.reset.qml" ); |
5145 | QTest::newRow(dataTag: "reset property in binding" ) << testFileUrl(fileName: "propertyQJSValue.bindingreset.qml" ); |
5146 | } |
5147 | |
5148 | void tst_qqmlecmascript::propertyQJSValue() |
5149 | { |
5150 | QFETCH(QUrl, qmlFile); |
5151 | |
5152 | QQmlEngine engine; |
5153 | QQmlComponent component(&engine, qmlFile); |
5154 | QObject *object = component.create(); |
5155 | QVERIFY(object != nullptr); |
5156 | |
5157 | QCOMPARE(object->property("test" ).toBool(), true); |
5158 | |
5159 | delete object; |
5160 | } |
5161 | |
5162 | // Tests that we can write QVariant values to var properties from C++ |
5163 | void tst_qqmlecmascript::propertyVarCpp() |
5164 | { |
5165 | QObject *object = nullptr; |
5166 | |
5167 | // ensure that writing to and reading from a var property from cpp works as required. |
5168 | // Literal values stored in var properties can be read and written as QVariants |
5169 | // of a specific type, whereas object values are read as QVariantMaps. |
5170 | QQmlEngine engine; |
5171 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarCpp.qml" )); |
5172 | object = component.create(); |
5173 | QVERIFY(object != nullptr); |
5174 | // assign int to property var that currently has int assigned |
5175 | QVERIFY(object->setProperty("varProperty" , QVariant::fromValue(10))); |
5176 | QCOMPARE(object->property("varBound" ), QVariant(15)); |
5177 | QCOMPARE(object->property("intBound" ), QVariant(15)); |
5178 | QVERIFY(isJSNumberType(object->property("varProperty" ).userType())); |
5179 | QVERIFY(isJSNumberType(object->property("varBound" ).userType())); |
5180 | // assign string to property var that current has bool assigned |
5181 | QCOMPARE(object->property("varProperty2" ).userType(), (int)QVariant::Bool); |
5182 | QVERIFY(object->setProperty("varProperty2" , QVariant(QLatin1String("randomString" )))); |
5183 | QCOMPARE(object->property("varProperty2" ), QVariant(QLatin1String("randomString" ))); |
5184 | QCOMPARE(object->property("varProperty2" ).userType(), (int)QVariant::String); |
5185 | // now enforce behaviour when accessing JavaScript objects from cpp. |
5186 | QCOMPARE(object->property("jsobject" ).userType(), qMetaTypeId<QJSValue>()); |
5187 | delete object; |
5188 | } |
5189 | |
5190 | void tst_qqmlecmascript::propertyVarOwnership() |
5191 | { |
5192 | QQmlEngine engine; |
5193 | |
5194 | // Referenced JS objects are not collected |
5195 | { |
5196 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarOwnership.qml" )); |
5197 | QObject *object = component.create(); |
5198 | QVERIFY(object != nullptr); |
5199 | QCOMPARE(object->property("test" ).toBool(), false); |
5200 | QMetaObject::invokeMethod(obj: object, member: "runTest" ); |
5201 | QCOMPARE(object->property("test" ).toBool(), true); |
5202 | delete object; |
5203 | } |
5204 | // Referenced JS objects are not collected |
5205 | { |
5206 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarOwnership.2.qml" )); |
5207 | QObject *object = component.create(); |
5208 | QVERIFY(object != nullptr); |
5209 | QCOMPARE(object->property("test" ).toBool(), false); |
5210 | QMetaObject::invokeMethod(obj: object, member: "runTest" ); |
5211 | QCOMPARE(object->property("test" ).toBool(), true); |
5212 | delete object; |
5213 | } |
5214 | // Qt objects are not collected until they've been dereferenced |
5215 | { |
5216 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarOwnership.3.qml" )); |
5217 | QObject *object = component.create(); |
5218 | QVERIFY(object != nullptr); |
5219 | |
5220 | QCOMPARE(object->property("test2" ).toBool(), false); |
5221 | QCOMPARE(object->property("test2" ).toBool(), false); |
5222 | |
5223 | QMetaObject::invokeMethod(obj: object, member: "runTest" ); |
5224 | QCOMPARE(object->property("test1" ).toBool(), true); |
5225 | |
5226 | QPointer<QObject> referencedObject = object->property(name: "object" ).value<QObject*>(); |
5227 | QVERIFY(!referencedObject.isNull()); |
5228 | gc(engine); |
5229 | QVERIFY(!referencedObject.isNull()); |
5230 | |
5231 | QMetaObject::invokeMethod(obj: object, member: "runTest2" ); |
5232 | QCOMPARE(object->property("test2" ).toBool(), true); |
5233 | gc(engine); |
5234 | QVERIFY(referencedObject.isNull()); |
5235 | |
5236 | delete object; |
5237 | } |
5238 | // Self reference does not prevent Qt object collection |
5239 | { |
5240 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarOwnership.4.qml" )); |
5241 | QObject *object = component.create(); |
5242 | QVERIFY(object != nullptr); |
5243 | |
5244 | QCOMPARE(object->property("test" ).toBool(), true); |
5245 | |
5246 | QPointer<QObject> referencedObject = object->property(name: "object" ).value<QObject*>(); |
5247 | QVERIFY(!referencedObject.isNull()); |
5248 | gc(engine); |
5249 | QVERIFY(!referencedObject.isNull()); |
5250 | |
5251 | QMetaObject::invokeMethod(obj: object, member: "runTest" ); |
5252 | gc(engine); |
5253 | QVERIFY(referencedObject.isNull()); |
5254 | |
5255 | delete object; |
5256 | } |
5257 | // Garbage collection cannot result in attempted dereference of empty handle |
5258 | { |
5259 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarOwnership.5.qml" )); |
5260 | QObject *object = component.create(); |
5261 | QVERIFY(object != nullptr); |
5262 | QMetaObject::invokeMethod(obj: object, member: "createComponent" ); |
5263 | engine.collectGarbage(); |
5264 | QMetaObject::invokeMethod(obj: object, member: "runTest" ); |
5265 | QCOMPARE(object->property("test" ).toBool(), true); |
5266 | delete object; |
5267 | } |
5268 | } |
5269 | |
5270 | void tst_qqmlecmascript::propertyVarImplicitOwnership() |
5271 | { |
5272 | // The childObject has a reference to a different QObject. We want to ensure |
5273 | // that the different item will not be cleaned up until required. IE, the childObject |
5274 | // has implicit ownership of the constructed QObject. |
5275 | QQmlEngine engine; |
5276 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVarImplicitOwnership.qml" )); |
5277 | QObject *object = component.create(); |
5278 | QVERIFY(object != nullptr); |
5279 | QMetaObject::invokeMethod(obj: object, member: "assignCircular" ); |
5280 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5281 | QCoreApplication::processEvents(); |
5282 | QObject *rootObject = object->property(name: "vp" ).value<QObject*>(); |
5283 | QVERIFY(rootObject != nullptr); |
5284 | QObject *childObject = rootObject->findChild<QObject*>(aName: "text" ); |
5285 | QVERIFY(childObject != nullptr); |
5286 | QCOMPARE(rootObject->property("rectCanary" ).toInt(), 5); |
5287 | QCOMPARE(childObject->property("textCanary" ).toInt(), 10); |
5288 | QMetaObject::invokeMethod(obj: childObject, member: "constructQObject" ); // creates a reference to a constructed QObject. |
5289 | QPointer<QObject> qobjectGuard(childObject->property(name: "vp" ).value<QObject*>()); // get the pointer prior to processing deleteLater events. |
5290 | QVERIFY(!qobjectGuard.isNull()); |
5291 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5292 | QCoreApplication::processEvents(); |
5293 | QVERIFY(!qobjectGuard.isNull()); |
5294 | QMetaObject::invokeMethod(obj: object, member: "deassignCircular" ); |
5295 | gc(engine); |
5296 | QVERIFY(qobjectGuard.isNull()); // should have been collected now. |
5297 | delete object; |
5298 | } |
5299 | |
5300 | void tst_qqmlecmascript::propertyVarReparent() |
5301 | { |
5302 | // ensure that nothing breaks if we re-parent objects |
5303 | QQmlEngine engine; |
5304 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.reparent.qml" )); |
5305 | QObject *object = component.create(); |
5306 | QVERIFY(object != nullptr); |
5307 | QMetaObject::invokeMethod(obj: object, member: "assignVarProp" ); |
5308 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5309 | QCoreApplication::processEvents(); |
5310 | QObject *rect = object->property(name: "vp" ).value<QObject*>(); |
5311 | QObject *text = rect->findChild<QObject*>(aName: "textOne" ); |
5312 | QObject *text2 = rect->findChild<QObject*>(aName: "textTwo" ); |
5313 | QPointer<QObject> rectGuard(rect); |
5314 | QPointer<QObject> textGuard(text); |
5315 | QPointer<QObject> text2Guard(text2); |
5316 | QVERIFY(!rectGuard.isNull()); |
5317 | QVERIFY(!textGuard.isNull()); |
5318 | QVERIFY(!text2Guard.isNull()); |
5319 | QCOMPARE(text->property("textCanary" ).toInt(), 11); |
5320 | QCOMPARE(text2->property("textCanary" ).toInt(), 12); |
5321 | // now construct an image which we will reparent. |
5322 | QMetaObject::invokeMethod(obj: text2, member: "constructQObject" ); |
5323 | QObject *image = text2->property(name: "vp" ).value<QObject*>(); |
5324 | QPointer<QObject> imageGuard(image); |
5325 | QVERIFY(!imageGuard.isNull()); |
5326 | QCOMPARE(image->property("imageCanary" ).toInt(), 13); |
5327 | // now reparent the "Image" object (currently, it has JS ownership) |
5328 | image->setParent(text); // shouldn't be collected after deassignVp now, since has a parent. |
5329 | QMetaObject::invokeMethod(obj: text2, member: "deassignVp" ); |
5330 | gc(engine); |
5331 | QCOMPARE(text->property("textCanary" ).toInt(), 11); |
5332 | QCOMPARE(text2->property("textCanary" ).toInt(), 22); |
5333 | QVERIFY(!imageGuard.isNull()); // should still be alive. |
5334 | QCOMPARE(image->property("imageCanary" ).toInt(), 13); // still able to access var properties |
5335 | QMetaObject::invokeMethod(obj: object, member: "deassignVarProp" ); // now deassign the root-object's vp, causing gc of rect+text+text2 |
5336 | gc(engine); |
5337 | QVERIFY(imageGuard.isNull()); // should now have been deleted, due to parent being deleted. |
5338 | delete object; |
5339 | } |
5340 | |
5341 | void tst_qqmlecmascript::propertyVarReparentNullContext() |
5342 | { |
5343 | // sometimes reparenting can cause problems |
5344 | // (eg, if the ctxt is collected, varproperties are no longer available) |
5345 | // this test ensures that no crash occurs in that situation. |
5346 | QQmlEngine engine; |
5347 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.reparent.qml" )); |
5348 | QObject *object = component.create(); |
5349 | QVERIFY(object != nullptr); |
5350 | QMetaObject::invokeMethod(obj: object, member: "assignVarProp" ); |
5351 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5352 | QCoreApplication::processEvents(); |
5353 | QObject *rect = object->property(name: "vp" ).value<QObject*>(); |
5354 | QObject *text = rect->findChild<QObject*>(aName: "textOne" ); |
5355 | QObject *text2 = rect->findChild<QObject*>(aName: "textTwo" ); |
5356 | QPointer<QObject> rectGuard(rect); |
5357 | QPointer<QObject> textGuard(text); |
5358 | QPointer<QObject> text2Guard(text2); |
5359 | QVERIFY(!rectGuard.isNull()); |
5360 | QVERIFY(!textGuard.isNull()); |
5361 | QVERIFY(!text2Guard.isNull()); |
5362 | QCOMPARE(text->property("textCanary" ).toInt(), 11); |
5363 | QCOMPARE(text2->property("textCanary" ).toInt(), 12); |
5364 | // now construct an image which we will reparent. |
5365 | QMetaObject::invokeMethod(obj: text2, member: "constructQObject" ); |
5366 | QObject *image = text2->property(name: "vp" ).value<QObject*>(); |
5367 | QPointer<QObject> imageGuard(image); |
5368 | QVERIFY(!imageGuard.isNull()); |
5369 | QCOMPARE(image->property("imageCanary" ).toInt(), 13); |
5370 | // now reparent the "Image" object (currently, it has JS ownership) |
5371 | image->setParent(object); // reparented to base object. after deassignVarProp, the ctxt will be invalid. |
5372 | QMetaObject::invokeMethod(obj: object, member: "deassignVarProp" ); // now deassign the root-object's vp, causing gc of rect+text+text2 |
5373 | gc(engine); |
5374 | QVERIFY(!imageGuard.isNull()); // should still be alive. |
5375 | QVERIFY(!image->property("imageCanary" ).isValid()); // but varProperties won't be available (null context). |
5376 | delete object; |
5377 | QVERIFY(imageGuard.isNull()); // should now be dead. |
5378 | } |
5379 | |
5380 | void tst_qqmlecmascript::propertyVarCircular() |
5381 | { |
5382 | // enforce behaviour regarding circular references - ensure qdvmemo deletion. |
5383 | QQmlEngine engine; |
5384 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.circular.qml" )); |
5385 | QObject *object = component.create(); |
5386 | QVERIFY(object != nullptr); |
5387 | QMetaObject::invokeMethod(obj: object, member: "assignCircular" ); // cause assignment and gc |
5388 | { |
5389 | QCOMPARE(object->property("canaryInt" ), QVariant(5)); |
5390 | QVariant canaryResourceVariant = object->property(name: "canaryResource" ); |
5391 | QVERIFY(canaryResourceVariant.isValid()); |
5392 | } |
5393 | |
5394 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5395 | QCoreApplication::processEvents(); |
5396 | QCOMPARE(object->property("canaryInt" ), QVariant(5)); |
5397 | QVariant canaryResourceVariant = object->property(name: "canaryResource" ); |
5398 | QVERIFY(canaryResourceVariant.isValid()); |
5399 | QPixmap canaryResourcePixmap = canaryResourceVariant.value<QPixmap>(); |
5400 | canaryResourceVariant = QVariant(); // invalidate it to remove one copy of the pixmap from memory. |
5401 | QMetaObject::invokeMethod(obj: object, member: "deassignCanaryResource" ); // remove one copy of the pixmap from memory |
5402 | gc(engine); |
5403 | QVERIFY(!canaryResourcePixmap.isDetached()); // two copies extant - this and the propertyVar.vp.vp.vp.vp.memoryHog. |
5404 | QMetaObject::invokeMethod(obj: object, member: "deassignCircular" ); // cause deassignment and gc |
5405 | gc(engine); |
5406 | QCOMPARE(object->property("canaryInt" ), QVariant(2)); |
5407 | QCOMPARE(object->property("canaryResource" ), QVariant(1)); |
5408 | QVERIFY(canaryResourcePixmap.isDetached()); // now detached, since orig copy was member of qdvmemo which was deleted. |
5409 | delete object; |
5410 | } |
5411 | |
5412 | void tst_qqmlecmascript::propertyVarCircular2() |
5413 | { |
5414 | // track deletion of JS-owned parent item with Cpp-owned child |
5415 | // where the child has a var property referencing its parent. |
5416 | QQmlEngine engine; |
5417 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.circular.2.qml" )); |
5418 | QObject *object = component.create(); |
5419 | QVERIFY(object != nullptr); |
5420 | QMetaObject::invokeMethod(obj: object, member: "assignCircular" ); |
5421 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5422 | QCoreApplication::processEvents(); |
5423 | QObject *rootObject = object->property(name: "vp" ).value<QObject*>(); |
5424 | QVERIFY(rootObject != nullptr); |
5425 | QObject *childObject = rootObject->findChild<QObject*>(aName: "text" ); |
5426 | QVERIFY(childObject != nullptr); |
5427 | QPointer<QObject> rootObjectTracker(rootObject); |
5428 | QVERIFY(!rootObjectTracker.isNull()); |
5429 | QPointer<QObject> childObjectTracker(childObject); |
5430 | QVERIFY(!childObjectTracker.isNull()); |
5431 | gc(engine); |
5432 | QCOMPARE(rootObject->property("rectCanary" ).toInt(), 5); |
5433 | QCOMPARE(childObject->property("textCanary" ).toInt(), 10); |
5434 | QMetaObject::invokeMethod(obj: object, member: "deassignCircular" ); |
5435 | gc(engine); |
5436 | QVERIFY(rootObjectTracker.isNull()); // should have been collected |
5437 | QVERIFY(childObjectTracker.isNull()); // should have been collected |
5438 | delete object; |
5439 | } |
5440 | |
5441 | void tst_qqmlecmascript::propertyVarInheritance() |
5442 | { |
5443 | // enforce behaviour regarding element inheritance - ensure handle disposal. |
5444 | // The particular component under test here has a chain of references. |
5445 | QQmlEngine engine; |
5446 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.inherit.qml" )); |
5447 | QObject *object = component.create(); |
5448 | QVERIFY(object != nullptr); |
5449 | QMetaObject::invokeMethod(obj: object, member: "assignCircular" ); // cause assignment and gc |
5450 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5451 | QCoreApplication::processEvents(); |
5452 | // we want to be able to track when the varProperties array of the last metaobject is disposed |
5453 | QObject *cco5 = object->property(name: "varProperty" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>(); |
5454 | QObject *ico5 = object->property(name: "varProperty" ).value<QObject*>()->property(name: "inheritanceVarProperty" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>()->property(name: "vp" ).value<QObject*>(); |
5455 | QVERIFY(cco5); |
5456 | QVERIFY(ico5); |
5457 | QQmlVMEMetaObject *icovmemo = QQmlVMEMetaObject::get(obj: ico5); |
5458 | QQmlVMEMetaObject *ccovmemo = QQmlVMEMetaObject::get(obj: cco5); |
5459 | QV4::WeakValue icoCanaryHandle; |
5460 | QV4::WeakValue ccoCanaryHandle; |
5461 | { |
5462 | // XXX NOTE: this is very implementation dependent. QDVMEMO->vmeProperty() is the only |
5463 | // public function which can return us a handle to something in the varProperties array. |
5464 | QV4::ReturnedValue tmp = icovmemo->vmeProperty(index: ico5->metaObject()->indexOfProperty(name: "circ" )); |
5465 | icoCanaryHandle.set(engine: engine.handle(), value: tmp); |
5466 | tmp = ccovmemo->vmeProperty(index: cco5->metaObject()->indexOfProperty(name: "circ" )); |
5467 | ccoCanaryHandle.set(engine: engine.handle(), value: tmp); |
5468 | tmp = QV4::Encode::null(); |
5469 | QVERIFY(!icoCanaryHandle.isUndefined()); |
5470 | QVERIFY(!ccoCanaryHandle.isUndefined()); |
5471 | gc(engine); |
5472 | QVERIFY(!icoCanaryHandle.isUndefined()); |
5473 | QVERIFY(!ccoCanaryHandle.isUndefined()); |
5474 | } |
5475 | // now we deassign the var prop, which should trigger collection of item subtrees. |
5476 | QMetaObject::invokeMethod(obj: object, member: "deassignCircular" ); // cause deassignment and gc |
5477 | // ensure that there are only weak handles to the underlying varProperties array remaining. |
5478 | gc(engine); |
5479 | // an equivalent for pragma GCC optimize is still work-in-progress for CLang, so this test will fail. |
5480 | QVERIFY(icoCanaryHandle.isUndefined()); |
5481 | QVERIFY(ccoCanaryHandle.isUndefined()); |
5482 | delete object; |
5483 | // since there are no parent vmemo's to keep implicit references alive, and the only handles |
5484 | // to what remains are weak, all varProperties arrays must have been collected. |
5485 | } |
5486 | |
5487 | void tst_qqmlecmascript::propertyVarInheritance2() |
5488 | { |
5489 | // The particular component under test here does NOT have a chain of references; the |
5490 | // only link between rootObject and childObject is that rootObject is the parent of childObject. |
5491 | QQmlEngine engine; |
5492 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyVar.circular.2.qml" )); |
5493 | QObject *object = component.create(); |
5494 | QVERIFY(object != nullptr); |
5495 | QMetaObject::invokeMethod(obj: object, member: "assignCircular" ); |
5496 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. |
5497 | QCoreApplication::processEvents(); |
5498 | QObject *rootObject = object->property(name: "vp" ).value<QObject*>(); |
5499 | QVERIFY(rootObject != nullptr); |
5500 | QObject *childObject = rootObject->findChild<QObject*>(aName: "text" ); |
5501 | QVERIFY(childObject != nullptr); |
5502 | QCOMPARE(rootObject->property("rectCanary" ).toInt(), 5); |
5503 | QCOMPARE(childObject->property("textCanary" ).toInt(), 10); |
5504 | QV4::WeakValue childObjectVarArrayValueHandle; |
5505 | { |
5506 | childObjectVarArrayValueHandle.set(engine: engine.handle(), |
5507 | value: QQmlVMEMetaObject::get(obj: childObject)->vmeProperty(index: childObject->metaObject()->indexOfProperty(name: "vp" ))); |
5508 | QVERIFY(!childObjectVarArrayValueHandle.isUndefined()); |
5509 | gc(engine); |
5510 | QVERIFY(!childObjectVarArrayValueHandle.isUndefined()); // should not have been collected yet. |
5511 | QCOMPARE(childObject->property("vp" ).value<QObject*>(), rootObject); |
5512 | QCOMPARE(childObject->property("textCanary" ).toInt(), 10); |
5513 | } |
5514 | QMetaObject::invokeMethod(obj: object, member: "deassignCircular" ); |
5515 | gc(engine); |
5516 | // an equivalent for pragma GCC optimize is still work-in-progress for CLang, so this test will fail. |
5517 | QVERIFY(childObjectVarArrayValueHandle.isUndefined()); // should have been collected now. |
5518 | delete object; |
5519 | } |
5520 | |
5521 | |
5522 | // Ensure that QObject type conversion works on binding assignment |
5523 | void tst_qqmlecmascript::elementAssign() |
5524 | { |
5525 | QQmlEngine engine; |
5526 | QQmlComponent component(&engine, testFileUrl(fileName: "elementAssign.qml" )); |
5527 | |
5528 | QObject *object = component.create(); |
5529 | QVERIFY(object != nullptr); |
5530 | |
5531 | QCOMPARE(object->property("test" ).toBool(), true); |
5532 | |
5533 | delete object; |
5534 | } |
5535 | |
5536 | // QTBUG-12457 |
5537 | void tst_qqmlecmascript::objectPassThroughSignals() |
5538 | { |
5539 | QQmlEngine engine; |
5540 | QQmlComponent component(&engine, testFileUrl(fileName: "objectsPassThroughSignals.qml" )); |
5541 | |
5542 | QObject *object = component.create(); |
5543 | QVERIFY(object != nullptr); |
5544 | |
5545 | QCOMPARE(object->property("test" ).toBool(), true); |
5546 | |
5547 | delete object; |
5548 | } |
5549 | |
5550 | // QTBUG-21626 |
5551 | void tst_qqmlecmascript::objectConversion() |
5552 | { |
5553 | QQmlEngine engine; |
5554 | QQmlComponent component(&engine, testFileUrl(fileName: "objectConversion.qml" )); |
5555 | |
5556 | QObject *object = component.create(); |
5557 | QVERIFY(object != nullptr); |
5558 | QVariant retn; |
5559 | QMetaObject::invokeMethod(obj: object, member: "circularObject" , Q_RETURN_ARG(QVariant, retn)); |
5560 | QCOMPARE(retn.value<QJSValue>().property("test" ).toInt(), int(100)); |
5561 | |
5562 | delete object; |
5563 | } |
5564 | |
5565 | |
5566 | // QTBUG-20242 |
5567 | void tst_qqmlecmascript::booleanConversion() |
5568 | { |
5569 | QQmlEngine engine; |
5570 | QQmlComponent component(&engine, testFileUrl(fileName: "booleanConversion.qml" )); |
5571 | |
5572 | QObject *object = component.create(); |
5573 | QVERIFY(object != nullptr); |
5574 | |
5575 | QCOMPARE(object->property("test_true1" ).toBool(), true); |
5576 | QCOMPARE(object->property("test_true2" ).toBool(), true); |
5577 | QCOMPARE(object->property("test_true3" ).toBool(), true); |
5578 | QCOMPARE(object->property("test_true4" ).toBool(), true); |
5579 | QCOMPARE(object->property("test_true5" ).toBool(), true); |
5580 | |
5581 | QCOMPARE(object->property("test_false1" ).toBool(), false); |
5582 | QCOMPARE(object->property("test_false2" ).toBool(), false); |
5583 | QCOMPARE(object->property("test_false3" ).toBool(), false); |
5584 | |
5585 | delete object; |
5586 | } |
5587 | |
5588 | void tst_qqmlecmascript::handleReferenceManagement() |
5589 | { |
5590 | int dtorCount = 0; |
5591 | { |
5592 | // Linear QObject reference |
5593 | QQmlEngine hrmEngine; |
5594 | QQmlComponent component(&hrmEngine, testFileUrl(fileName: "handleReferenceManagement.object.1.qml" )); |
5595 | QObject *object = component.create(); |
5596 | QVERIFY(object != nullptr); |
5597 | CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>(aName: "cro" ); |
5598 | cro->setDtorCount(&dtorCount); |
5599 | QMetaObject::invokeMethod(obj: object, member: "createReference" ); |
5600 | gc(engine&: hrmEngine); |
5601 | QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference |
5602 | delete object; |
5603 | hrmEngine.collectGarbage(); |
5604 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
5605 | QCoreApplication::processEvents(); |
5606 | QCOMPARE(dtorCount, 3); |
5607 | } |
5608 | |
5609 | dtorCount = 0; |
5610 | { |
5611 | // Circular QObject reference |
5612 | QQmlEngine hrmEngine; |
5613 | QQmlComponent component(&hrmEngine, testFileUrl(fileName: "handleReferenceManagement.object.2.qml" )); |
5614 | QObject *object = component.create(); |
5615 | QVERIFY(object != nullptr); |
5616 | CircularReferenceObject *cro = object->findChild<CircularReferenceObject*>(aName: "cro" ); |
5617 | cro->setDtorCount(&dtorCount); |
5618 | QMetaObject::invokeMethod(obj: object, member: "circularReference" ); |
5619 | gc(engine&: hrmEngine); |
5620 | QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive. |
5621 | delete object; |
5622 | hrmEngine.collectGarbage(); |
5623 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
5624 | QCoreApplication::processEvents(); |
5625 | QCOMPARE(dtorCount, 3); |
5626 | } |
5627 | |
5628 | { |
5629 | // Dynamic variant property reference keeps target alive |
5630 | QQmlEngine hrmEngine; |
5631 | QQmlComponent component(&hrmEngine, testFileUrl(fileName: "handleReferenceManagement.dynprop.qml" )); |
5632 | QObject *object = component.create(); |
5633 | QVERIFY(object != nullptr); |
5634 | QMetaObject::invokeMethod(obj: object, member: "createReference" ); |
5635 | gc(engine&: hrmEngine); |
5636 | QMetaObject::invokeMethod(obj: object, member: "ensureReference" ); |
5637 | gc(engine&: hrmEngine); |
5638 | QMetaObject::invokeMethod(obj: object, member: "removeReference" ); |
5639 | gc(engine&: hrmEngine); |
5640 | QMetaObject::invokeMethod(obj: object, member: "ensureDeletion" ); |
5641 | QCOMPARE(object->property("success" ).toBool(), true); |
5642 | delete object; |
5643 | } |
5644 | |
5645 | { |
5646 | // Dynamic Item property reference keeps target alive |
5647 | QQmlEngine hrmEngine; |
5648 | QQmlComponent component(&hrmEngine, testFileUrl(fileName: "handleReferenceManagement.dynprop.2.qml" )); |
5649 | QObject *object = component.create(); |
5650 | QVERIFY(object != nullptr); |
5651 | QMetaObject::invokeMethod(obj: object, member: "createReference" ); |
5652 | gc(engine&: hrmEngine); |
5653 | QMetaObject::invokeMethod(obj: object, member: "ensureReference" ); |
5654 | gc(engine&: hrmEngine); |
5655 | QMetaObject::invokeMethod(obj: object, member: "removeReference" ); |
5656 | gc(engine&: hrmEngine); |
5657 | QMetaObject::invokeMethod(obj: object, member: "ensureDeletion" ); |
5658 | QCOMPARE(object->property("success" ).toBool(), true); |
5659 | delete object; |
5660 | } |
5661 | |
5662 | { |
5663 | // Item property reference to deleted item doesn't crash |
5664 | QQmlEngine hrmEngine; |
5665 | QQmlComponent component(&hrmEngine, testFileUrl(fileName: "handleReferenceManagement.dynprop.3.qml" )); |
5666 | QObject *object = component.create(); |
5667 | QVERIFY(object != nullptr); |
5668 | QMetaObject::invokeMethod(obj: object, member: "createReference" ); |
5669 | gc(engine&: hrmEngine); |
5670 | QMetaObject::invokeMethod(obj: object, member: "ensureReference" ); |
5671 | gc(engine&: hrmEngine); |
5672 | QMetaObject::invokeMethod(obj: object, member: "manuallyDelete" ); |
5673 | gc(engine&: hrmEngine); |
5674 | QMetaObject::invokeMethod(obj: object, member: "ensureDeleted" ); |
5675 | QCOMPARE(object->property("success" ).toBool(), true); |
5676 | delete object; |
5677 | } |
5678 | } |
5679 | |
5680 | void tst_qqmlecmascript::stringArg() |
5681 | { |
5682 | QQmlEngine engine; |
5683 | QQmlComponent component(&engine, testFileUrl(fileName: "stringArg.qml" )); |
5684 | QObject *object = component.create(); |
5685 | QVERIFY(object != nullptr); |
5686 | QMetaObject::invokeMethod(obj: object, member: "success" ); |
5687 | QVERIFY(object->property("returnValue" ).toBool()); |
5688 | |
5689 | QString w1 = testFileUrl(fileName: "stringArg.qml" ).toString() + QLatin1String(":45: Error: String.arg(): Invalid arguments" ); |
5690 | QTest::ignoreMessage(type: QtWarningMsg, message: w1.toLatin1().constData()); |
5691 | QMetaObject::invokeMethod(obj: object, member: "failure" ); |
5692 | QVERIFY(object->property("returnValue" ).toBool()); |
5693 | |
5694 | delete object; |
5695 | } |
5696 | |
5697 | void tst_qqmlecmascript::readonlyDeclaration() |
5698 | { |
5699 | QQmlEngine engine; |
5700 | QQmlComponent component(&engine, testFileUrl(fileName: "readonlyDeclaration.qml" )); |
5701 | |
5702 | QObject *object = component.create(); |
5703 | QVERIFY(object != nullptr); |
5704 | |
5705 | QCOMPARE(object->property("test" ).toBool(), true); |
5706 | |
5707 | delete object; |
5708 | } |
5709 | |
5710 | Q_DECLARE_METATYPE(QList<int>) |
5711 | Q_DECLARE_METATYPE(QList<qreal>) |
5712 | Q_DECLARE_METATYPE(QList<bool>) |
5713 | Q_DECLARE_METATYPE(QList<QString>) |
5714 | Q_DECLARE_METATYPE(QList<QUrl>) |
5715 | void tst_qqmlecmascript::sequenceConversionRead() |
5716 | { |
5717 | QQmlEngine engine; |
5718 | |
5719 | { |
5720 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.read.qml" ); |
5721 | QQmlComponent component(&engine, qmlFile); |
5722 | QObject *object = component.create(); |
5723 | QVERIFY(object != nullptr); |
5724 | MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>(aName: "msco" ); |
5725 | QVERIFY(seq != nullptr); |
5726 | |
5727 | QMetaObject::invokeMethod(obj: object, member: "readSequences" ); |
5728 | QList<int> intList; intList << 1 << 2 << 3 << 4; |
5729 | QCOMPARE(object->property("intListLength" ).toInt(), intList.length()); |
5730 | QCOMPARE(object->property("intList" ).value<QList<int> >(), intList); |
5731 | QList<qreal> qrealList; qrealList << 1.1 << 2.2 << 3.3 << 4.4; |
5732 | QCOMPARE(object->property("qrealListLength" ).toInt(), qrealList.length()); |
5733 | QCOMPARE(object->property("qrealList" ).value<QList<qreal> >(), qrealList); |
5734 | QList<bool> boolList; boolList << true << false << true << false; |
5735 | QCOMPARE(object->property("boolListLength" ).toInt(), boolList.length()); |
5736 | QCOMPARE(object->property("boolList" ).value<QList<bool> >(), boolList); |
5737 | QList<QString> stringList; stringList << QLatin1String("first" ) << QLatin1String("second" ) << QLatin1String("third" ) << QLatin1String("fourth" ); |
5738 | QCOMPARE(object->property("stringListLength" ).toInt(), stringList.length()); |
5739 | QCOMPARE(object->property("stringList" ).value<QList<QString> >(), stringList); |
5740 | QList<QUrl> urlList; urlList << QUrl("http://www.example1.com" ) << QUrl("http://www.example2.com" ) << QUrl("http://www.example3.com" ); |
5741 | QCOMPARE(object->property("urlListLength" ).toInt(), urlList.length()); |
5742 | QCOMPARE(object->property("urlList" ).value<QList<QUrl> >(), urlList); |
5743 | QStringList qstringList; qstringList << QLatin1String("first" ) << QLatin1String("second" ) << QLatin1String("third" ) << QLatin1String("fourth" ); |
5744 | QCOMPARE(object->property("qstringListLength" ).toInt(), qstringList.length()); |
5745 | QCOMPARE(object->property("qstringList" ).value<QStringList>(), qstringList); |
5746 | |
5747 | QMetaObject::invokeMethod(obj: object, member: "readSequenceElements" ); |
5748 | QCOMPARE(object->property("intVal" ).toInt(), 2); |
5749 | QCOMPARE(object->property("qrealVal" ).toReal(), 2.2); |
5750 | QCOMPARE(object->property("boolVal" ).toBool(), false); |
5751 | QCOMPARE(object->property("stringVal" ).toString(), QString(QLatin1String("second" ))); |
5752 | QCOMPARE(object->property("urlVal" ).toUrl(), QUrl("http://www.example2.com" )); |
5753 | QCOMPARE(object->property("qstringVal" ).toString(), QString(QLatin1String("second" ))); |
5754 | |
5755 | QMetaObject::invokeMethod(obj: object, member: "enumerateSequenceElements" ); |
5756 | QCOMPARE(object->property("enumerationMatches" ).toBool(), true); |
5757 | |
5758 | intList.clear(); intList << 1 << 2 << 3 << 4 << 5; // set by the enumerateSequenceElements test. |
5759 | QQmlProperty seqProp(seq, "intListProperty" ); |
5760 | QCOMPARE(seqProp.read().value<QList<int> >(), intList); |
5761 | QQmlProperty seqProp2(seq, "intListProperty" , &engine); |
5762 | QCOMPARE(seqProp2.read().value<QList<int> >(), intList); |
5763 | |
5764 | QMetaObject::invokeMethod(obj: object, member: "testReferenceDeletion" ); |
5765 | QCOMPARE(object->property("referenceDeletion" ).toBool(), true); |
5766 | |
5767 | delete object; |
5768 | } |
5769 | |
5770 | { |
5771 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.read.error.qml" ); |
5772 | QQmlComponent component(&engine, qmlFile); |
5773 | QObject *object = component.create(); |
5774 | QVERIFY(object != nullptr); |
5775 | MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>(aName: "msco" ); |
5776 | QVERIFY(seq != nullptr); |
5777 | |
5778 | // we haven't registered QList<NonRegisteredType> as a sequence type. |
5779 | QString warningOne = QLatin1String("QMetaProperty::read: Unable to handle unregistered datatype 'QList<NonRegisteredType>' for property 'MySequenceConversionObject::typeListProperty'" ); |
5780 | QString warningTwo = qmlFile.toString() + QLatin1String(":18: TypeError: Cannot read property 'length' of undefined" ); |
5781 | QTest::ignoreMessage(type: QtWarningMsg, message: warningOne.toLatin1().constData()); |
5782 | QTest::ignoreMessage(type: QtWarningMsg, message: warningTwo.toLatin1().constData()); |
5783 | |
5784 | QMetaObject::invokeMethod(obj: object, member: "performTest" ); |
5785 | |
5786 | // QList<NonRegisteredType> has not been registered as a sequence type. |
5787 | QCOMPARE(object->property("pointListLength" ).toInt(), 0); |
5788 | QVERIFY(!object->property("pointList" ).isValid()); |
5789 | QTest::ignoreMessage(type: QtWarningMsg, message: "QMetaProperty::read: Unable to handle unregistered datatype 'QList<NonRegisteredType>' for property 'MySequenceConversionObject::typeListProperty'" ); |
5790 | QQmlProperty seqProp(seq, "typeListProperty" , &engine); |
5791 | QVERIFY(!seqProp.read().isValid()); // not a valid/known sequence type |
5792 | |
5793 | delete object; |
5794 | } |
5795 | } |
5796 | |
5797 | void tst_qqmlecmascript::sequenceConversionWrite() |
5798 | { |
5799 | QQmlEngine engine; |
5800 | { |
5801 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.write.qml" ); |
5802 | QQmlComponent component(&engine, qmlFile); |
5803 | QObject *object = component.create(); |
5804 | QVERIFY(object != nullptr); |
5805 | MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>(aName: "msco" ); |
5806 | QVERIFY(seq != nullptr); |
5807 | |
5808 | QMetaObject::invokeMethod(obj: object, member: "writeSequences" ); |
5809 | QCOMPARE(object->property("success" ).toBool(), true); |
5810 | |
5811 | QMetaObject::invokeMethod(obj: object, member: "writeSequenceElements" ); |
5812 | QCOMPARE(object->property("success" ).toBool(), true); |
5813 | |
5814 | QMetaObject::invokeMethod(obj: object, member: "writeOtherElements" ); |
5815 | QCOMPARE(object->property("success" ).toBool(), true); |
5816 | |
5817 | QMetaObject::invokeMethod(obj: object, member: "testReferenceDeletion" ); |
5818 | QCOMPARE(object->property("referenceDeletion" ).toBool(), true); |
5819 | |
5820 | delete object; |
5821 | } |
5822 | |
5823 | { |
5824 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.write.error.qml" ); |
5825 | QQmlComponent component(&engine, qmlFile); |
5826 | QObject *object = component.create(); |
5827 | QVERIFY(object != nullptr); |
5828 | MySequenceConversionObject *seq = object->findChild<MySequenceConversionObject*>(aName: "msco" ); |
5829 | QVERIFY(seq != nullptr); |
5830 | |
5831 | // Behavior change in 5.14: due to added auto-magical conversions, it is possible to assign to |
5832 | // QList<QPoint>, even though it is not a registered sequence type |
5833 | QTest::ignoreMessage(type: QtMsgType::QtWarningMsg, messagePattern: QRegularExpression("Could not convert array value at position 1 from QString to QPoint" )); |
5834 | QMetaObject::invokeMethod(obj: object, member: "performTest" ); |
5835 | |
5836 | QList<QPoint> pointList; pointList << QPoint(7, 7) << QPoint(0,0) << QPoint(8, 8) << QPoint(9, 9); // original values, shouldn't have changed |
5837 | QCOMPARE(seq->pointListProperty(), pointList); |
5838 | |
5839 | delete object; |
5840 | } |
5841 | } |
5842 | |
5843 | void tst_qqmlecmascript::sequenceConversionArray() |
5844 | { |
5845 | // ensure that in JS the returned sequences act just like normal JS Arrays. |
5846 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.array.qml" ); |
5847 | QQmlEngine engine; |
5848 | QQmlComponent component(&engine, qmlFile); |
5849 | QScopedPointer<QObject> object(component.create()); |
5850 | QVERIFY(object != nullptr); |
5851 | QMetaObject::invokeMethod(obj: object.data(), member: "indexedAccess" ); |
5852 | QVERIFY(object->property("success" ).toBool()); |
5853 | QMetaObject::invokeMethod(obj: object.data(), member: "arrayOperations" ); |
5854 | QVERIFY(object->property("success" ).toBool()); |
5855 | QMetaObject::invokeMethod(obj: object.data(), member: "testEqualitySemantics" ); |
5856 | QVERIFY(object->property("success" ).toBool()); |
5857 | QMetaObject::invokeMethod(obj: object.data(), member: "testReferenceDeletion" ); |
5858 | QCOMPARE(object->property("referenceDeletion" ).toBool(), true); |
5859 | QMetaObject::invokeMethod(obj: object.data(), member: "jsonConversion" ); |
5860 | QVERIFY(object->property("success" ).toBool()); |
5861 | } |
5862 | |
5863 | |
5864 | void tst_qqmlecmascript::sequenceConversionIndexes() |
5865 | { |
5866 | // ensure that we gracefully fail if unsupported index values are specified. |
5867 | // Qt container classes only support non-negative, signed integer index values. |
5868 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.indexes.qml" ); |
5869 | QQmlEngine engine; |
5870 | QQmlComponent component(&engine, qmlFile); |
5871 | QObject *object = component.create(); |
5872 | QVERIFY(object != nullptr); |
5873 | QString w1 = qmlFile.toString() + QLatin1String(":34: Index out of range during length set" ); |
5874 | QString w2 = qmlFile.toString() + QLatin1String(":41: Index out of range during indexed set" ); |
5875 | QString w3 = qmlFile.toString() + QLatin1String(":48: Index out of range during indexed get" ); |
5876 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w1)); |
5877 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w2)); |
5878 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(w3)); |
5879 | QMetaObject::invokeMethod(obj: object, member: "indexedAccess" ); |
5880 | QVERIFY(object->property("success" ).toBool()); |
5881 | QMetaObject::invokeMethod(obj: object, member: "indexOf" ); |
5882 | QVERIFY(object->property("success" ).toBool()); |
5883 | delete object; |
5884 | } |
5885 | |
5886 | void tst_qqmlecmascript::sequenceConversionThreads() |
5887 | { |
5888 | // ensure that sequence conversion operations work correctly in a worker thread |
5889 | // and that serialisation between the main and worker thread succeeds. |
5890 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.threads.qml" ); |
5891 | QQmlEngine engine; |
5892 | QQmlComponent component(&engine, qmlFile); |
5893 | QObject *object = component.create(); |
5894 | QVERIFY(object != nullptr); |
5895 | |
5896 | QMetaObject::invokeMethod(obj: object, member: "testIntSequence" ); |
5897 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5898 | QVERIFY(object->property("success" ).toBool()); |
5899 | |
5900 | QMetaObject::invokeMethod(obj: object, member: "testQrealSequence" ); |
5901 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5902 | QVERIFY(object->property("success" ).toBool()); |
5903 | |
5904 | QMetaObject::invokeMethod(obj: object, member: "testBoolSequence" ); |
5905 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5906 | QVERIFY(object->property("success" ).toBool()); |
5907 | |
5908 | QMetaObject::invokeMethod(obj: object, member: "testStringSequence" ); |
5909 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5910 | QVERIFY(object->property("success" ).toBool()); |
5911 | |
5912 | QMetaObject::invokeMethod(obj: object, member: "testQStringSequence" ); |
5913 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5914 | QVERIFY(object->property("success" ).toBool()); |
5915 | |
5916 | QMetaObject::invokeMethod(obj: object, member: "testUrlSequence" ); |
5917 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5918 | QVERIFY(object->property("success" ).toBool()); |
5919 | |
5920 | QMetaObject::invokeMethod(obj: object, member: "testVariantSequence" ); |
5921 | QTRY_VERIFY(object->property("finished" ).toBool()); |
5922 | QVERIFY(object->property("success" ).toBool()); |
5923 | |
5924 | delete object; |
5925 | } |
5926 | |
5927 | void tst_qqmlecmascript::sequenceConversionBindings() |
5928 | { |
5929 | QQmlEngine engine; |
5930 | { |
5931 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.bindings.qml" ); |
5932 | QQmlComponent component(&engine, qmlFile); |
5933 | QObject *object = component.create(); |
5934 | QVERIFY(object != nullptr); |
5935 | QList<int> intList; intList << 1 << 2 << 3 << 12 << 7; |
5936 | QCOMPARE(object->property("boundSequence" ).value<QList<int> >(), intList); |
5937 | QCOMPARE(object->property("boundElement" ).toInt(), intList.at(3)); |
5938 | QList<int> intListTwo; intListTwo << 1 << 2 << 3 << 12 << 14; |
5939 | QCOMPARE(object->property("boundSequenceTwo" ).value<QList<int> >(), intListTwo); |
5940 | delete object; |
5941 | } |
5942 | |
5943 | { |
5944 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.bindings.error.qml" ); |
5945 | QString warning = QString(QLatin1String("%1:17:9: Unable to assign QList<int> to QList<bool>" )).arg(a: qmlFile.toString()); |
5946 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
5947 | QQmlComponent component(&engine, qmlFile); |
5948 | QObject *object = component.create(); |
5949 | QVERIFY(object != nullptr); |
5950 | delete object; |
5951 | } |
5952 | } |
5953 | |
5954 | void tst_qqmlecmascript::sequenceConversionCopy() |
5955 | { |
5956 | QUrl qmlFile = testFileUrl(fileName: "sequenceConversion.copy.qml" ); |
5957 | QQmlEngine engine; |
5958 | QQmlComponent component(&engine, qmlFile); |
5959 | QObject *object = component.create(); |
5960 | QVERIFY(object != nullptr); |
5961 | QMetaObject::invokeMethod(obj: object, member: "testCopySequences" ); |
5962 | QCOMPARE(object->property("success" ).toBool(), true); |
5963 | QMetaObject::invokeMethod(obj: object, member: "readSequenceCopyElements" ); |
5964 | QCOMPARE(object->property("success" ).toBool(), true); |
5965 | QMetaObject::invokeMethod(obj: object, member: "testEqualitySemantics" ); |
5966 | QCOMPARE(object->property("success" ).toBool(), true); |
5967 | QMetaObject::invokeMethod(obj: object, member: "testCopyParameters" ); |
5968 | QCOMPARE(object->property("success" ).toBool(), true); |
5969 | QMetaObject::invokeMethod(obj: object, member: "testReferenceParameters" ); |
5970 | QCOMPARE(object->property("success" ).toBool(), true); |
5971 | delete object; |
5972 | } |
5973 | |
5974 | void tst_qqmlecmascript::assignSequenceTypes() |
5975 | { |
5976 | QQmlEngine engine; |
5977 | |
5978 | // test binding array to sequence type property |
5979 | { |
5980 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.1.qml" )); |
5981 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
5982 | QVERIFY(object != nullptr); |
5983 | QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2)); |
5984 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2 << 3)); |
5985 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true)); |
5986 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com" ) << QUrl("http://www.example2.com" ))); |
5987 | QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one" ) << QLatin1String("two" ))); |
5988 | QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one" ) << QLatin1String("two" ))); |
5989 | delete object; |
5990 | } |
5991 | |
5992 | // test binding literal to sequence type property |
5993 | { |
5994 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.2.qml" )); |
5995 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
5996 | QVERIFY(object != nullptr); |
5997 | QCOMPARE(object->intListProperty(), (QList<int>() << 1)); |
5998 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1)); |
5999 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false)); |
6000 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com" ))); |
6001 | QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one" ))); |
6002 | QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two" ))); |
6003 | delete object; |
6004 | } |
6005 | |
6006 | // test binding single value to sequence type property |
6007 | { |
6008 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.3.qml" )); |
6009 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
6010 | QVERIFY(object != nullptr); |
6011 | QCOMPARE(object->intListProperty(), (QList<int>() << 1)); |
6012 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1)); |
6013 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false)); |
6014 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )))); |
6015 | delete object; |
6016 | } |
6017 | |
6018 | // test assigning array to sequence type property in js function |
6019 | { |
6020 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.4.qml" )); |
6021 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
6022 | QVERIFY(object != nullptr); |
6023 | QCOMPARE(object->intListProperty(), (QList<int>() << 1 << 2)); |
6024 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1 << 2.2 << 3)); |
6025 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false << true)); |
6026 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com" ) << QUrl("http://www.example2.com" ))); |
6027 | QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one" ) << QLatin1String("two" ))); |
6028 | QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("one" ) << QLatin1String("two" ))); |
6029 | delete object; |
6030 | } |
6031 | |
6032 | // test assigning literal to sequence type property in js function |
6033 | { |
6034 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.5.qml" )); |
6035 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
6036 | QVERIFY(object != nullptr); |
6037 | QCOMPARE(object->intListProperty(), (QList<int>() << 1)); |
6038 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1.1)); |
6039 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false)); |
6040 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl("http://www.example1.com" ))); |
6041 | QCOMPARE(object->stringListProperty(), (QList<QString>() << QLatin1String("one" ))); |
6042 | QCOMPARE(object->qstringListProperty(), (QStringList() << QLatin1String("two" ))); |
6043 | delete object; |
6044 | } |
6045 | |
6046 | // test assigning single value to sequence type property in js function |
6047 | { |
6048 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.6.qml" )); |
6049 | MySequenceConversionObject *object = qobject_cast<MySequenceConversionObject *>(object: component.create()); |
6050 | QVERIFY(object != nullptr); |
6051 | QCOMPARE(object->intListProperty(), (QList<int>() << 1)); |
6052 | QCOMPARE(object->qrealListProperty(), (QList<qreal>() << 1)); |
6053 | QCOMPARE(object->boolListProperty(), (QList<bool>() << false)); |
6054 | QCOMPARE(object->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )))); |
6055 | delete object; |
6056 | } |
6057 | |
6058 | // test QList<QUrl> literal assignment and binding assignment causes url resolution when required |
6059 | { |
6060 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.7.qml" )); |
6061 | QObject *object = component.create(); |
6062 | QVERIFY(object != nullptr); |
6063 | MySequenceConversionObject *msco1 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco1" )); |
6064 | MySequenceConversionObject *msco2 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco2" )); |
6065 | MySequenceConversionObject *msco3 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco3" )); |
6066 | MySequenceConversionObject *msco4 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco4" )); |
6067 | MySequenceConversionObject *msco5 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco5" )); |
6068 | QVERIFY(msco1 != nullptr && msco2 != nullptr && msco3 != nullptr && msco4 != nullptr && msco5 != nullptr); |
6069 | QCOMPARE(msco1->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )))); |
6070 | QCOMPARE(msco2->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )))); |
6071 | QCOMPARE(msco3->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )) << QUrl(testFileUrl("example2.html" )))); |
6072 | QCOMPARE(msco4->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )) << QUrl(testFileUrl("example2.html" )))); |
6073 | QCOMPARE(msco5->urlListProperty(), (QList<QUrl>() << QUrl(testFileUrl("example.html" )) << QUrl(testFileUrl("example2.html" )))); |
6074 | delete object; |
6075 | } |
6076 | |
6077 | { |
6078 | QQmlComponent component(&engine, testFileUrl(fileName: "assignSequenceTypes.8.qml" )); |
6079 | QScopedPointer<QObject> object(component.create()); |
6080 | QVERIFY(object != nullptr); |
6081 | QVariant result; |
6082 | QMetaObject::invokeMethod(obj: object.data(), member: "tryWritingReadOnlySequence" , Q_RETURN_ARG(QVariant, result)); |
6083 | QVERIFY(result.type() == QVariant::Bool); |
6084 | QVERIFY(result.toBool()); |
6085 | } |
6086 | } |
6087 | |
6088 | // Test that assigning a null object works |
6089 | // Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4 |
6090 | void tst_qqmlecmascript::nullObjectBinding() |
6091 | { |
6092 | QQmlEngine engine; |
6093 | QQmlComponent component(&engine, testFileUrl(fileName: "nullObjectBinding.qml" )); |
6094 | |
6095 | QObject *object = component.create(); |
6096 | QVERIFY(object != nullptr); |
6097 | |
6098 | QVERIFY(object->property("test" ) == QVariant::fromValue((QObject *)nullptr)); |
6099 | |
6100 | delete object; |
6101 | } |
6102 | |
6103 | void tst_qqmlecmascript::nullObjectInitializer() |
6104 | { |
6105 | QQmlEngine engine; |
6106 | { |
6107 | QQmlComponent component(&engine, testFileUrl(fileName: "nullObjectInitializer.qml" )); |
6108 | QScopedPointer<QObject> obj(component.create()); |
6109 | QVERIFY(!obj.isNull()); |
6110 | |
6111 | QQmlData *ddata = QQmlData::get(object: obj.data(), /*create*/false); |
6112 | QVERIFY(ddata); |
6113 | |
6114 | { |
6115 | const int propertyIndex = obj->metaObject()->indexOfProperty(name: "testProperty" ); |
6116 | QVERIFY(propertyIndex > 0); |
6117 | QVERIFY(!ddata->hasBindingBit(propertyIndex)); |
6118 | } |
6119 | |
6120 | QVariant value = obj->property(name: "testProperty" ); |
6121 | QVERIFY(value.userType() == qMetaTypeId<QObject*>()); |
6122 | QVERIFY(!value.value<QObject*>()); |
6123 | } |
6124 | |
6125 | { |
6126 | QQmlComponent component(&engine, testFileUrl(fileName: "nullObjectInitializer.2.qml" )); |
6127 | QScopedPointer<QObject> obj(component.create()); |
6128 | QVERIFY(!obj.isNull()); |
6129 | |
6130 | QQmlData *ddata = QQmlData::get(object: obj.data(), /*create*/false); |
6131 | QVERIFY(ddata); |
6132 | |
6133 | { |
6134 | const int propertyIndex = obj->metaObject()->indexOfProperty(name: "testProperty" ); |
6135 | QVERIFY(propertyIndex > 0); |
6136 | QVERIFY(!ddata->hasBindingBit(propertyIndex)); |
6137 | } |
6138 | |
6139 | QVERIFY(obj->property("success" ).toBool()); |
6140 | |
6141 | QVariant value = obj->property(name: "testProperty" ); |
6142 | QVERIFY(value.userType() == qMetaTypeId<QObject*>()); |
6143 | QVERIFY(!value.value<QObject*>()); |
6144 | } |
6145 | |
6146 | { |
6147 | QQmlComponent component(&engine, testFileUrl(fileName: "qmlVarNullBinding.qml" )); |
6148 | QScopedPointer<QObject> obj(component.create()); |
6149 | QVERIFY(!obj.isNull()); |
6150 | QVERIFY(!obj->property("signalSeen" ).toBool()); |
6151 | } |
6152 | } |
6153 | |
6154 | // Test that bindings don't evaluate once the engine has been destroyed |
6155 | void tst_qqmlecmascript::deletedEngine() |
6156 | { |
6157 | QQmlEngine *engine = new QQmlEngine; |
6158 | QQmlComponent component(engine, testFileUrl(fileName: "deletedEngine.qml" )); |
6159 | |
6160 | QObject *object = component.create(); |
6161 | QVERIFY(object != nullptr); |
6162 | |
6163 | QCOMPARE(object->property("a" ).toInt(), 39); |
6164 | object->setProperty(name: "b" , value: QVariant(9)); |
6165 | QCOMPARE(object->property("a" ).toInt(), 117); |
6166 | |
6167 | delete engine; |
6168 | |
6169 | QCOMPARE(object->property("a" ).toInt(), 0); |
6170 | object->setProperty(name: "b" , value: QVariant(10)); |
6171 | object->setProperty(name: "b" , value: QVariant()); |
6172 | QCOMPARE(object->property("a" ).toInt(), 0); |
6173 | |
6174 | delete object; |
6175 | } |
6176 | |
6177 | // Test the crashing part of QTBUG-9705 |
6178 | void tst_qqmlecmascript::libraryScriptAssert() |
6179 | { |
6180 | QQmlEngine engine; |
6181 | QQmlComponent component(&engine, testFileUrl(fileName: "libraryScriptAssert.qml" )); |
6182 | |
6183 | QObject *object = component.create(); |
6184 | QVERIFY(object != nullptr); |
6185 | |
6186 | delete object; |
6187 | } |
6188 | |
6189 | void tst_qqmlecmascript::variantsAssignedUndefined() |
6190 | { |
6191 | QQmlEngine engine; |
6192 | QQmlComponent component(&engine, testFileUrl(fileName: "variantsAssignedUndefined.qml" )); |
6193 | |
6194 | QObject *object = component.create(); |
6195 | QVERIFY(object != nullptr); |
6196 | |
6197 | QCOMPARE(object->property("test1" ).toInt(), 10); |
6198 | QCOMPARE(object->property("test2" ).toInt(), 11); |
6199 | |
6200 | object->setProperty(name: "runTest" , value: true); |
6201 | |
6202 | QCOMPARE(object->property("test1" ), QVariant()); |
6203 | QCOMPARE(object->property("test2" ), QVariant()); |
6204 | |
6205 | |
6206 | delete object; |
6207 | } |
6208 | |
6209 | void tst_qqmlecmascript::variants() |
6210 | { |
6211 | QQmlEngine engine; |
6212 | QQmlComponent component(&engine, testFileUrl(fileName: "variants.qml" )); |
6213 | |
6214 | QScopedPointer<QObject> object(component.create()); |
6215 | QVERIFY(object != nullptr); |
6216 | |
6217 | QCOMPARE(object->property("undefinedVariant" ).type(), QVariant::Invalid); |
6218 | QCOMPARE(int(object->property("nullVariant" ).type()), int(QMetaType::Nullptr)); |
6219 | QCOMPARE(object->property("intVariant" ).type(), QVariant::Int); |
6220 | QCOMPARE(object->property("doubleVariant" ).type(), QVariant::Double); |
6221 | |
6222 | QVariant result; |
6223 | QMetaObject::invokeMethod(obj: object.get(), member: "checkNull" , Q_RETURN_ARG(QVariant, result)); |
6224 | QCOMPARE(result.toBool(), true); |
6225 | |
6226 | QMetaObject::invokeMethod(obj: object.get(), member: "checkUndefined" , Q_RETURN_ARG(QVariant, result)); |
6227 | QCOMPARE(result.toBool(), true); |
6228 | |
6229 | QMetaObject::invokeMethod(obj: object.get(), member: "checkNumber" , Q_RETURN_ARG(QVariant, result)); |
6230 | QCOMPARE(result.toBool(), true); |
6231 | } |
6232 | |
6233 | void tst_qqmlecmascript::qtbug_9792() |
6234 | { |
6235 | QQmlEngine engine; |
6236 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_9792.qml" )); |
6237 | |
6238 | QQmlContext *context = new QQmlContext(engine.rootContext()); |
6239 | |
6240 | MyQmlObject *object = qobject_cast<MyQmlObject*>(object: component.create(context)); |
6241 | QVERIFY(object != nullptr); |
6242 | |
6243 | QTest::ignoreMessage(type: QtDebugMsg, message: "Hello world!" ); |
6244 | object->basicSignal(); |
6245 | |
6246 | delete context; |
6247 | |
6248 | QQmlTestMessageHandler messageHandler; |
6249 | |
6250 | object->basicSignal(); |
6251 | |
6252 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
6253 | |
6254 | delete object; |
6255 | } |
6256 | |
6257 | // Verifies that QPointer<>s used in the vmemetaobject are cleaned correctly |
6258 | void tst_qqmlecmascript::qtcreatorbug_1289() |
6259 | { |
6260 | QQmlEngine engine; |
6261 | QQmlComponent component(&engine, testFileUrl(fileName: "qtcreatorbug_1289.qml" )); |
6262 | |
6263 | QObject *o = component.create(); |
6264 | QVERIFY(o != nullptr); |
6265 | |
6266 | QObject *nested = qvariant_cast<QObject *>(v: o->property(name: "object" )); |
6267 | QVERIFY(nested != nullptr); |
6268 | |
6269 | QVERIFY(qvariant_cast<QObject *>(nested->property("nestedObject" )) == o); |
6270 | |
6271 | delete nested; |
6272 | nested = qvariant_cast<QObject *>(v: o->property(name: "object" )); |
6273 | QVERIFY(!nested); |
6274 | |
6275 | // If the bug is present, the next line will crash |
6276 | delete o; |
6277 | } |
6278 | |
6279 | // Test that we shut down without stupid warnings |
6280 | void tst_qqmlecmascript::noSpuriousWarningsAtShutdown() |
6281 | { |
6282 | QQmlEngine engine; |
6283 | { |
6284 | QQmlComponent component(&engine, testFileUrl(fileName: "noSpuriousWarningsAtShutdown.qml" )); |
6285 | |
6286 | QObject *o = component.create(); |
6287 | |
6288 | QQmlTestMessageHandler messageHandler; |
6289 | |
6290 | delete o; |
6291 | |
6292 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
6293 | } |
6294 | |
6295 | |
6296 | { |
6297 | QQmlComponent component(&engine, testFileUrl(fileName: "noSpuriousWarningsAtShutdown.2.qml" )); |
6298 | |
6299 | QObject *o = component.create(); |
6300 | |
6301 | QQmlTestMessageHandler messageHandler; |
6302 | |
6303 | delete o; |
6304 | |
6305 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
6306 | } |
6307 | } |
6308 | |
6309 | void tst_qqmlecmascript::canAssignNullToQObject() |
6310 | { |
6311 | QQmlEngine engine; |
6312 | { |
6313 | QQmlComponent component(&engine, testFileUrl(fileName: "canAssignNullToQObject.1.qml" )); |
6314 | |
6315 | MyQmlObject *o = qobject_cast<MyQmlObject *>(object: component.create()); |
6316 | QVERIFY(o != nullptr); |
6317 | |
6318 | QVERIFY(o->objectProperty() != nullptr); |
6319 | |
6320 | o->setProperty(name: "runTest" , value: true); |
6321 | |
6322 | QVERIFY(!o->objectProperty()); |
6323 | |
6324 | delete o; |
6325 | } |
6326 | |
6327 | { |
6328 | QQmlComponent component(&engine, testFileUrl(fileName: "canAssignNullToQObject.2.qml" )); |
6329 | |
6330 | MyQmlObject *o = qobject_cast<MyQmlObject *>(object: component.create()); |
6331 | QVERIFY(o != nullptr); |
6332 | |
6333 | QVERIFY(!o->objectProperty()); |
6334 | |
6335 | delete o; |
6336 | } |
6337 | } |
6338 | |
6339 | void tst_qqmlecmascript::functionAssignment_fromBinding() |
6340 | { |
6341 | QQmlEngine engine; |
6342 | QQmlComponent component(&engine, testFileUrl(fileName: "functionAssignment.1.qml" )); |
6343 | |
6344 | QString url = component.url().toString(); |
6345 | QString w1 = url + ":4:5: Unable to assign a function to a property of any type other than var." ; |
6346 | QString w2 = url + ":5:5: Invalid use of Qt.binding() in a binding declaration." ; |
6347 | QString w3 = url + ":6:5: Invalid use of Qt.binding() in a binding declaration." ; |
6348 | QString w4 = url + ":7:5: Invalid use of Qt.binding() in a binding declaration." ; |
6349 | QTest::ignoreMessage(type: QtWarningMsg, message: w1.toLatin1().constData()); |
6350 | QTest::ignoreMessage(type: QtWarningMsg, message: w2.toLatin1().constData()); |
6351 | QTest::ignoreMessage(type: QtWarningMsg, message: w3.toLatin1().constData()); |
6352 | QTest::ignoreMessage(type: QtWarningMsg, message: w4.toLatin1().constData()); |
6353 | |
6354 | MyQmlObject *o = qobject_cast<MyQmlObject *>(object: component.create()); |
6355 | QVERIFY(o != nullptr); |
6356 | |
6357 | QVERIFY(!o->property("a" ).isValid()); |
6358 | |
6359 | delete o; |
6360 | } |
6361 | |
6362 | void tst_qqmlecmascript::functionAssignment_fromJS() |
6363 | { |
6364 | QFETCH(QString, triggerProperty); |
6365 | |
6366 | QQmlEngine engine; |
6367 | QQmlComponent component(&engine, testFileUrl(fileName: "functionAssignment.2.qml" )); |
6368 | QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); |
6369 | |
6370 | MyQmlObject *o = qobject_cast<MyQmlObject *>(object: component.create()); |
6371 | QVERIFY(o != nullptr); |
6372 | QVERIFY(!o->property("a" ).isValid()); |
6373 | |
6374 | o->setProperty(name: "aNumber" , value: QVariant(5)); |
6375 | o->setProperty(name: triggerProperty.toUtf8().constData(), value: true); |
6376 | QCOMPARE(o->property("a" ), QVariant(50)); |
6377 | |
6378 | o->setProperty(name: "aNumber" , value: QVariant(10)); |
6379 | QCOMPARE(o->property("a" ), QVariant(100)); |
6380 | |
6381 | delete o; |
6382 | } |
6383 | |
6384 | void tst_qqmlecmascript::functionAssignment_fromJS_data() |
6385 | { |
6386 | QTest::addColumn<QString>(name: "triggerProperty" ); |
6387 | |
6388 | QTest::newRow(dataTag: "assign to property" ) << "assignToProperty" ; |
6389 | QTest::newRow(dataTag: "assign to property, from JS file" ) << "assignToPropertyFromJsFile" ; |
6390 | |
6391 | QTest::newRow(dataTag: "assign to value type" ) << "assignToValueType" ; |
6392 | |
6393 | QTest::newRow(dataTag: "use 'this'" ) << "assignWithThis" ; |
6394 | QTest::newRow(dataTag: "use 'this' from JS file" ) << "assignWithThisFromJsFile" ; |
6395 | } |
6396 | |
6397 | void tst_qqmlecmascript::functionAssignmentfromJS_invalid() |
6398 | { |
6399 | QQmlEngine engine; |
6400 | QQmlComponent component(&engine, testFileUrl(fileName: "functionAssignment.2.qml" )); |
6401 | QVERIFY2(component.errorString().isEmpty(), qPrintable(component.errorString())); |
6402 | |
6403 | MyQmlObject *o = qobject_cast<MyQmlObject *>(object: component.create()); |
6404 | QVERIFY(o != nullptr); |
6405 | QVERIFY(!o->property("a" ).isValid()); |
6406 | |
6407 | o->setProperty(name: "assignFuncWithoutReturn" , value: true); |
6408 | QVERIFY(!o->property("a" ).isValid()); |
6409 | |
6410 | QString url = component.url().toString(); |
6411 | QString warning = url + ":67: Unable to assign QString to int" ; |
6412 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
6413 | o->setProperty(name: "assignWrongType" , value: true); |
6414 | |
6415 | warning = url + ":71: Unable to assign QString to int" ; |
6416 | QTest::ignoreMessage(type: QtWarningMsg, message: warning.toLatin1().constData()); |
6417 | o->setProperty(name: "assignWrongTypeToValueType" , value: true); |
6418 | |
6419 | delete o; |
6420 | } |
6421 | |
6422 | void tst_qqmlecmascript::functionAssignment_afterBinding() |
6423 | { |
6424 | QQmlEngine engine; |
6425 | QQmlComponent component(&engine, testFileUrl(fileName: "functionAssignment.3.qml" )); |
6426 | |
6427 | QString url = component.url().toString(); |
6428 | QString w1 = url + ":16: Error: Cannot assign JavaScript function to int" ; |
6429 | QTest::ignoreMessage(type: QtWarningMsg, message: w1.toLatin1().constData()); |
6430 | |
6431 | QObject *o = component.create(); |
6432 | QVERIFY(o != nullptr); |
6433 | QCOMPARE(o->property("t1" ), QVariant::fromValue<int>(4)); // should have bound |
6434 | QCOMPARE(o->property("t2" ), QVariant::fromValue<int>(2)); // should not have changed |
6435 | |
6436 | delete o; |
6437 | } |
6438 | |
6439 | void tst_qqmlecmascript::eval() |
6440 | { |
6441 | QQmlEngine engine; |
6442 | QQmlComponent component(&engine, testFileUrl(fileName: "eval.qml" )); |
6443 | |
6444 | QObject *o = component.create(); |
6445 | QVERIFY(o != nullptr); |
6446 | |
6447 | QCOMPARE(o->property("test1" ).toBool(), true); |
6448 | QCOMPARE(o->property("test2" ).toBool(), true); |
6449 | QCOMPARE(o->property("test3" ).toBool(), true); |
6450 | QCOMPARE(o->property("test4" ).toBool(), true); |
6451 | QCOMPARE(o->property("test5" ).toBool(), true); |
6452 | |
6453 | delete o; |
6454 | } |
6455 | |
6456 | void tst_qqmlecmascript::function() |
6457 | { |
6458 | QQmlEngine engine; |
6459 | QQmlComponent component(&engine, testFileUrl(fileName: "function.qml" )); |
6460 | |
6461 | QObject *o = component.create(); |
6462 | QVERIFY(o != nullptr); |
6463 | |
6464 | QCOMPARE(o->property("test1" ).toBool(), true); |
6465 | QCOMPARE(o->property("test2" ).toBool(), true); |
6466 | QCOMPARE(o->property("test3" ).toBool(), true); |
6467 | |
6468 | delete o; |
6469 | } |
6470 | |
6471 | // QTBUG-77096 |
6472 | void tst_qqmlecmascript::topLevelGeneratorFunction() |
6473 | { |
6474 | QQmlEngine engine; |
6475 | QQmlComponent component(&engine, testFileUrl(fileName: "generatorFunction.qml" )); |
6476 | |
6477 | QScopedPointer<QObject> o {component.create()}; |
6478 | if (!o) |
6479 | qDebug() << component.errorString(); |
6480 | QVERIFY(o != nullptr); |
6481 | |
6482 | // check that generator works correctly in QML |
6483 | QCOMPARE(o->property("test1" ).toBool(), true); |
6484 | QCOMPARE(o->property("test2" ).toBool(), true); |
6485 | QCOMPARE(o->property("test3" ).toBool(), true); |
6486 | QCOMPARE(o->property("done" ).toBool(), true); |
6487 | |
6488 | // check that generator is accessible from C++ |
6489 | QVariant returnedValue; |
6490 | QMetaObject::invokeMethod(obj: o.get(), member: "gen" , Q_RETURN_ARG(QVariant, returnedValue)); |
6491 | auto it = returnedValue.value<QJSValue>(); |
6492 | QCOMPARE(it.property("next" ).callWithInstance(it).property("value" ).toInt(), 1); |
6493 | } |
6494 | |
6495 | // QTBUG-91491 |
6496 | void tst_qqmlecmascript::generatorCrashNewProperty() |
6497 | { |
6498 | QQmlEngine engine; |
6499 | QQmlComponent component(&engine, testFileUrl(fileName: "generatorCrashNewProperty.qml" )); |
6500 | |
6501 | QScopedPointer<QObject> o(component.create()); |
6502 | |
6503 | QVERIFY2(o != nullptr, qPrintable(component.errorString())); |
6504 | |
6505 | QCOMPARE(o->property("a" ).toInt(), 42); |
6506 | QCOMPARE(o->property("b" ).toInt(), 12); |
6507 | QCOMPARE(o->property("c" ).toInt(), 42); |
6508 | } |
6509 | |
6510 | void tst_qqmlecmascript::generatorCallsGC() |
6511 | { |
6512 | QQmlEngine engine; |
6513 | QQmlComponent component(&engine, testFileUrl(fileName: "generatorCallsGC.qml" )); |
6514 | |
6515 | QScopedPointer<QObject> o(component.create()); // should not crash |
6516 | QVERIFY2(o != nullptr, qPrintable(component.errorString())); |
6517 | } |
6518 | |
6519 | // Test the "Qt.include" method |
6520 | void tst_qqmlecmascript::include() |
6521 | { |
6522 | QQmlEngine engine; |
6523 | // Non-library relative include |
6524 | { |
6525 | QQmlComponent component(&engine, testFileUrl(fileName: "include.qml" )); |
6526 | QObject *o = component.create(); |
6527 | QVERIFY(o != nullptr); |
6528 | |
6529 | QCOMPARE(o->property("test0" ).toInt(), 99); |
6530 | QCOMPARE(o->property("test1" ).toBool(), true); |
6531 | QCOMPARE(o->property("test2" ).toBool(), true); |
6532 | QCOMPARE(o->property("test2_1" ).toBool(), true); |
6533 | QCOMPARE(o->property("test3" ).toBool(), true); |
6534 | QCOMPARE(o->property("test3_1" ).toBool(), true); |
6535 | |
6536 | delete o; |
6537 | } |
6538 | |
6539 | // Library relative include |
6540 | { |
6541 | QQmlComponent component(&engine, testFileUrl(fileName: "include_shared.qml" )); |
6542 | QObject *o = component.create(); |
6543 | QVERIFY(o != nullptr); |
6544 | |
6545 | QCOMPARE(o->property("test0" ).toInt(), 99); |
6546 | QCOMPARE(o->property("test1" ).toBool(), true); |
6547 | QCOMPARE(o->property("test2" ).toBool(), true); |
6548 | QCOMPARE(o->property("test2_1" ).toBool(), true); |
6549 | QCOMPARE(o->property("test3" ).toBool(), true); |
6550 | QCOMPARE(o->property("test3_1" ).toBool(), true); |
6551 | |
6552 | delete o; |
6553 | } |
6554 | |
6555 | // Callback |
6556 | { |
6557 | QQmlComponent component(&engine, testFileUrl(fileName: "include_callback.qml" )); |
6558 | QObject *o = component.create(); |
6559 | QVERIFY(o != nullptr); |
6560 | |
6561 | QCOMPARE(o->property("test1" ).toBool(), true); |
6562 | QCOMPARE(o->property("test2" ).toBool(), true); |
6563 | QCOMPARE(o->property("test3" ).toBool(), true); |
6564 | QCOMPARE(o->property("test4" ).toBool(), true); |
6565 | QCOMPARE(o->property("test5" ).toBool(), true); |
6566 | QCOMPARE(o->property("test6" ).toBool(), true); |
6567 | |
6568 | delete o; |
6569 | } |
6570 | |
6571 | // Including file with ".pragma library" |
6572 | { |
6573 | QQmlComponent component(&engine, testFileUrl(fileName: "include_pragma.qml" )); |
6574 | QObject *o = component.create(); |
6575 | QVERIFY(o != nullptr); |
6576 | QCOMPARE(o->property("test1" ).toInt(), 100); |
6577 | |
6578 | delete o; |
6579 | } |
6580 | |
6581 | // Including file with ".pragma library", shadowing a global var |
6582 | { |
6583 | QQmlComponent component(&engine, testFileUrl(fileName: "include_pragma_shadow.qml" )); |
6584 | QScopedPointer<QObject> o(component.create()); |
6585 | QVERIFY(!o.isNull()); |
6586 | QCOMPARE(o->property("result" ).toBool(), true); |
6587 | } |
6588 | |
6589 | // Remote - error |
6590 | { |
6591 | TestHTTPServer server; |
6592 | QVERIFY2(server.listen(), qPrintable(server.errorString())); |
6593 | server.serveDirectory(dataDirectory()); |
6594 | |
6595 | QQmlComponent component(&engine, testFileUrl(fileName: "include_remote_missing.qml" )); |
6596 | QObject *o = component.beginCreate(engine.rootContext()); |
6597 | QVERIFY(o != nullptr); |
6598 | o->setProperty(name: "serverBaseUrl" , value: server.baseUrl().toString()); |
6599 | component.completeCreate(); |
6600 | |
6601 | QTRY_VERIFY(o->property("done" ).toBool()); |
6602 | |
6603 | QCOMPARE(o->property("test1" ).toBool(), true); |
6604 | QCOMPARE(o->property("test2" ).toBool(), true); |
6605 | QCOMPARE(o->property("test3" ).toBool(), true); |
6606 | |
6607 | delete o; |
6608 | } |
6609 | |
6610 | // include from resources |
6611 | { |
6612 | QQmlComponent component(&engine, QUrl("qrc:///data/include.qml" )); |
6613 | QObject *o = component.create(); |
6614 | QVERIFY(o != nullptr); |
6615 | |
6616 | QCOMPARE(o->property("test0" ).toInt(), 99); |
6617 | QCOMPARE(o->property("test1" ).toBool(), true); |
6618 | QCOMPARE(o->property("test2" ).toBool(), true); |
6619 | QCOMPARE(o->property("test2_1" ).toBool(), true); |
6620 | QCOMPARE(o->property("test3" ).toBool(), true); |
6621 | QCOMPARE(o->property("test3_1" ).toBool(), true); |
6622 | |
6623 | delete o; |
6624 | } |
6625 | |
6626 | } |
6627 | |
6628 | void tst_qqmlecmascript::includeRemoteSuccess() |
6629 | { |
6630 | // Remote - success |
6631 | TestHTTPServer server; |
6632 | QVERIFY2(server.listen(), qPrintable(server.errorString())); |
6633 | server.serveDirectory(dataDirectory()); |
6634 | |
6635 | QQmlEngine engine; |
6636 | QQmlComponent component(&engine, testFileUrl(fileName: "include_remote.qml" )); |
6637 | QObject *o = component.beginCreate(engine.rootContext()); |
6638 | QVERIFY(o != nullptr); |
6639 | o->setProperty(name: "serverBaseUrl" , value: server.baseUrl().toString()); |
6640 | component.completeCreate(); |
6641 | |
6642 | QTRY_VERIFY(o->property("done" ).toBool()); |
6643 | QTRY_VERIFY(o->property("done2" ).toBool()); |
6644 | |
6645 | QCOMPARE(o->property("test1" ).toBool(), true); |
6646 | QCOMPARE(o->property("test2" ).toBool(), true); |
6647 | QCOMPARE(o->property("test3" ).toBool(), true); |
6648 | QCOMPARE(o->property("test4" ).toBool(), true); |
6649 | QCOMPARE(o->property("test5" ).toBool(), true); |
6650 | |
6651 | QCOMPARE(o->property("test6" ).toBool(), true); |
6652 | QCOMPARE(o->property("test7" ).toBool(), true); |
6653 | QCOMPARE(o->property("test8" ).toBool(), true); |
6654 | QCOMPARE(o->property("test9" ).toBool(), true); |
6655 | QCOMPARE(o->property("test10" ).toBool(), true); |
6656 | |
6657 | delete o; |
6658 | } |
6659 | |
6660 | void tst_qqmlecmascript::signalHandlers() |
6661 | { |
6662 | QQmlEngine engine; |
6663 | QQmlComponent component(&engine, testFileUrl(fileName: "signalHandlers.qml" )); |
6664 | QScopedPointer<QObject> o(component.create()); |
6665 | QVERIFY(o != nullptr); |
6666 | QCOMPARE(o->property("count" ).toInt(), 0); |
6667 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalCall" ); |
6668 | QCOMPARE(o->property("count" ).toInt(), 1); |
6669 | |
6670 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalHandlerCall" ); |
6671 | QCOMPARE(o->property("count" ).toInt(), 1); |
6672 | QString scopeObjectAsString = o->property(name: "scopeObjectAsString" ).toString(); |
6673 | QCOMPARE(o->property("errorString" ).toString(), QString("TypeError: Property 'onTestSignal' of object %1 is not a function" ).arg(scopeObjectAsString)); |
6674 | |
6675 | QCOMPARE(o->property("funcCount" ).toInt(), 0); |
6676 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalConnection" ); |
6677 | QCOMPARE(o->property("funcCount" ).toInt(), 1); |
6678 | |
6679 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalHandlerConnection" ); |
6680 | QCOMPARE(o->property("funcCount" ).toInt(), 2); |
6681 | |
6682 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalDefined" ); |
6683 | QCOMPARE(o->property("definedResult" ).toBool(), true); |
6684 | |
6685 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalHandlerDefined" ); |
6686 | QCOMPARE(o->property("definedHandlerResult" ).toBool(), true); |
6687 | |
6688 | QVariant result; |
6689 | QMetaObject::invokeMethod(obj: o.data(), member: "testConnectionOnAlias" , Q_RETURN_ARG(QVariant, result)); |
6690 | QCOMPARE(result.toBool(), true); |
6691 | |
6692 | QMetaObject::invokeMethod(obj: o.data(), member: "testAliasSignalHandler" , Q_RETURN_ARG(QVariant, result)); |
6693 | QCOMPARE(result.toBool(), true); |
6694 | |
6695 | QMetaObject::invokeMethod(obj: o.data(), member: "testSignalWithClosureArgument" , Q_RETURN_ARG(QVariant, result)); |
6696 | QCOMPARE(result.toBool(), true); |
6697 | QMetaObject::invokeMethod(obj: o.data(), member: "testThisInSignalHandler" , Q_RETURN_ARG(QVariant, result)); |
6698 | QCOMPARE(result.toBool(), true); |
6699 | } |
6700 | |
6701 | void tst_qqmlecmascript::qtbug_37351() |
6702 | { |
6703 | MyTypeObject signalEmitter; |
6704 | { |
6705 | QQmlEngine engine; |
6706 | QQmlComponent component(&engine); |
6707 | component.setData("import Qt.test 1.0; import QtQml 2.0;\nQtObject {\n" |
6708 | " Component.onCompleted: {\n" |
6709 | " testObject.action.connect(function() { print('dont crash'); });" |
6710 | " }\n" |
6711 | "}" , baseUrl: QUrl()); |
6712 | |
6713 | engine.rootContext()->setContextProperty("testObject" , &signalEmitter); |
6714 | |
6715 | QScopedPointer<QObject> object(component.create()); |
6716 | QVERIFY(!object.isNull()); |
6717 | } |
6718 | signalEmitter.doAction(); |
6719 | // Don't crash |
6720 | } |
6721 | |
6722 | void tst_qqmlecmascript::qtbug_10696() |
6723 | { |
6724 | QQmlEngine engine; |
6725 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_10696.qml" )); |
6726 | QObject *o = component.create(); |
6727 | QVERIFY(o != nullptr); |
6728 | delete o; |
6729 | } |
6730 | |
6731 | void tst_qqmlecmascript::qtbug_11606() |
6732 | { |
6733 | QQmlEngine engine; |
6734 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_11606.qml" )); |
6735 | QObject *o = component.create(); |
6736 | QVERIFY(o != nullptr); |
6737 | QCOMPARE(o->property("test" ).toBool(), true); |
6738 | delete o; |
6739 | } |
6740 | |
6741 | void tst_qqmlecmascript::qtbug_11600() |
6742 | { |
6743 | QQmlEngine engine; |
6744 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_11600.qml" )); |
6745 | QObject *o = component.create(); |
6746 | QVERIFY(o != nullptr); |
6747 | QCOMPARE(o->property("test" ).toBool(), true); |
6748 | delete o; |
6749 | } |
6750 | |
6751 | void tst_qqmlecmascript::qtbug_21864() |
6752 | { |
6753 | QQmlEngine engine; |
6754 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_21864.qml" )); |
6755 | QObject *o = component.create(); |
6756 | QVERIFY(o != nullptr); |
6757 | QCOMPARE(o->property("test" ).toBool(), true); |
6758 | delete o; |
6759 | } |
6760 | |
6761 | void tst_qqmlecmascript::rewriteMultiLineStrings() |
6762 | { |
6763 | QQmlEngine engine; |
6764 | { |
6765 | // QTBUG-23387 |
6766 | QQmlComponent component(&engine, testFileUrl(fileName: "rewriteMultiLineStrings.qml" )); |
6767 | QObject *o = component.create(); |
6768 | QVERIFY(o != nullptr); |
6769 | QTRY_COMPARE(o->property("test" ).toBool(), true); |
6770 | delete o; |
6771 | } |
6772 | |
6773 | { |
6774 | QQmlComponent component(&engine, testFileUrl(fileName: "rewriteMultiLineStrings_crlf.1.qml" )); |
6775 | QObject *o = component.create(); |
6776 | QVERIFY(o != nullptr); |
6777 | delete o; |
6778 | } |
6779 | } |
6780 | |
6781 | void tst_qqmlecmascript::qobjectConnectionListExceptionHandling() |
6782 | { |
6783 | // QTBUG-23375 |
6784 | QQmlEngine engine; |
6785 | QQmlComponent component(&engine, testFileUrl(fileName: "qobjectConnectionListExceptionHandling.qml" )); |
6786 | QString warning = component.url().toString() + QLatin1String(":13: TypeError: Cannot read property 'undefined' of undefined" ); |
6787 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
6788 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
6789 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
6790 | QObject *o = component.create(); |
6791 | QVERIFY(o != nullptr); |
6792 | QCOMPARE(o->property("test" ).toBool(), true); |
6793 | delete o; |
6794 | } |
6795 | |
6796 | // Reading and writing non-scriptable properties should fail |
6797 | void tst_qqmlecmascript::nonscriptable() |
6798 | { |
6799 | QQmlEngine engine; |
6800 | QQmlComponent component(&engine, testFileUrl(fileName: "nonscriptable.qml" )); |
6801 | QObject *o = component.create(); |
6802 | QVERIFY(o != nullptr); |
6803 | QCOMPARE(o->property("readOk" ).toBool(), true); |
6804 | QCOMPARE(o->property("writeOk" ).toBool(), true); |
6805 | delete o; |
6806 | } |
6807 | |
6808 | // deleteLater() should not be callable from QML |
6809 | void tst_qqmlecmascript::deleteLater() |
6810 | { |
6811 | QQmlEngine engine; |
6812 | QQmlComponent component(&engine, testFileUrl(fileName: "deleteLater.qml" )); |
6813 | QObject *o = component.create(); |
6814 | QVERIFY(o != nullptr); |
6815 | QCOMPARE(o->property("test" ).toBool(), true); |
6816 | delete o; |
6817 | } |
6818 | |
6819 | // objectNameChanged() should be usable from QML |
6820 | void tst_qqmlecmascript::objectNameChangedSignal() |
6821 | { |
6822 | QQmlEngine engine; |
6823 | QQmlComponent component(&engine, testFileUrl(fileName: "objectNameChangedSignal.qml" )); |
6824 | QObject *o = component.create(); |
6825 | QVERIFY(o != nullptr); |
6826 | QCOMPARE(o->property("test" ).toBool(), false); |
6827 | o->setObjectName("obj" ); |
6828 | QCOMPARE(o->property("test" ).toBool(), true); |
6829 | delete o; |
6830 | } |
6831 | |
6832 | // destroyed() should not be usable from QML |
6833 | void tst_qqmlecmascript::destroyedSignal() |
6834 | { |
6835 | QQmlEngine engine; |
6836 | QQmlComponent component(&engine, testFileUrl(fileName: "destroyedSignal.qml" )); |
6837 | QVERIFY(component.isError()); |
6838 | |
6839 | QString expectedErrorString = component.url().toString() + QLatin1String(":5:5: Cannot assign to non-existent property \"onDestroyed\"" ); |
6840 | QCOMPARE(component.errors().at(0).toString(), expectedErrorString); |
6841 | } |
6842 | |
6843 | void tst_qqmlecmascript::in() |
6844 | { |
6845 | QQmlEngine engine; |
6846 | QQmlComponent component(&engine, testFileUrl(fileName: "in.qml" )); |
6847 | QObject *o = component.create(); |
6848 | QVERIFY(o != nullptr); |
6849 | QCOMPARE(o->property("test1" ).toBool(), true); |
6850 | QCOMPARE(o->property("test2" ).toBool(), true); |
6851 | delete o; |
6852 | } |
6853 | |
6854 | void tst_qqmlecmascript::typeOf() |
6855 | { |
6856 | QQmlEngine engine; |
6857 | QQmlComponent component(&engine, testFileUrl(fileName: "typeOf.qml" )); |
6858 | |
6859 | QObject *o = component.create(); |
6860 | QVERIFY(o != nullptr); |
6861 | |
6862 | QCOMPARE(o->property("test1" ).toString(), QLatin1String("undefined" )); |
6863 | QCOMPARE(o->property("test2" ).toString(), QLatin1String("object" )); |
6864 | QCOMPARE(o->property("test3" ).toString(), QLatin1String("number" )); |
6865 | QCOMPARE(o->property("test4" ).toString(), QLatin1String("string" )); |
6866 | QCOMPARE(o->property("test5" ).toString(), QLatin1String("function" )); |
6867 | QCOMPARE(o->property("test6" ).toString(), QLatin1String("object" )); |
6868 | QCOMPARE(o->property("test7" ).toString(), QLatin1String("undefined" )); |
6869 | QCOMPARE(o->property("test8" ).toString(), QLatin1String("boolean" )); |
6870 | QCOMPARE(o->property("test9" ).toString(), QLatin1String("object" )); |
6871 | |
6872 | delete o; |
6873 | } |
6874 | |
6875 | void tst_qqmlecmascript::qtbug_24448() |
6876 | { |
6877 | QQmlEngine engine; |
6878 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_24448.qml" )); |
6879 | QScopedPointer<QObject> o(component.create()); |
6880 | QVERIFY(o != nullptr); |
6881 | QVERIFY(o->property("test" ).toBool()); |
6882 | } |
6883 | |
6884 | void tst_qqmlecmascript::sharedAttachedObject() |
6885 | { |
6886 | QQmlEngine engine; |
6887 | QQmlComponent component(&engine, testFileUrl(fileName: "sharedAttachedObject.qml" )); |
6888 | QObject *o = component.create(); |
6889 | QVERIFY(o != nullptr); |
6890 | QCOMPARE(o->property("test1" ).toBool(), true); |
6891 | QCOMPARE(o->property("test2" ).toBool(), true); |
6892 | delete o; |
6893 | } |
6894 | |
6895 | // QTBUG-13999 |
6896 | void tst_qqmlecmascript::objectName() |
6897 | { |
6898 | QQmlEngine engine; |
6899 | QQmlComponent component(&engine, testFileUrl(fileName: "objectName.qml" )); |
6900 | QObject *o = component.create(); |
6901 | QVERIFY(o != nullptr); |
6902 | |
6903 | QCOMPARE(o->property("test1" ).toString(), QString("hello" )); |
6904 | QCOMPARE(o->property("test2" ).toString(), QString("ell" )); |
6905 | |
6906 | o->setObjectName("world" ); |
6907 | |
6908 | QCOMPARE(o->property("test1" ).toString(), QString("world" )); |
6909 | QCOMPARE(o->property("test2" ).toString(), QString("orl" )); |
6910 | |
6911 | delete o; |
6912 | } |
6913 | |
6914 | void tst_qqmlecmascript::writeRemovesBinding() |
6915 | { |
6916 | QQmlEngine engine; |
6917 | QQmlComponent component(&engine, testFileUrl(fileName: "writeRemovesBinding.qml" )); |
6918 | QObject *o = component.create(); |
6919 | QVERIFY(o != nullptr); |
6920 | |
6921 | QCOMPARE(o->property("test" ).toBool(), true); |
6922 | |
6923 | delete o; |
6924 | } |
6925 | |
6926 | // Test bindings assigned to alias properties actually assign to the alias' target |
6927 | void tst_qqmlecmascript::aliasBindingsAssignCorrectly() |
6928 | { |
6929 | QQmlEngine engine; |
6930 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasBindingsAssignCorrectly.qml" )); |
6931 | QObject *o = component.create(); |
6932 | QVERIFY(o != nullptr); |
6933 | |
6934 | QCOMPARE(o->property("test" ).toBool(), true); |
6935 | |
6936 | delete o; |
6937 | } |
6938 | |
6939 | // Test bindings assigned to alias properties override a binding on the target (QTBUG-13719) |
6940 | void tst_qqmlecmascript::aliasBindingsOverrideTarget() |
6941 | { |
6942 | QQmlEngine engine; |
6943 | { |
6944 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasBindingsOverrideTarget.qml" )); |
6945 | QObject *o = component.create(); |
6946 | QVERIFY(o != nullptr); |
6947 | |
6948 | QCOMPARE(o->property("test" ).toBool(), true); |
6949 | |
6950 | delete o; |
6951 | } |
6952 | |
6953 | { |
6954 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasBindingsOverrideTarget.2.qml" )); |
6955 | QObject *o = component.create(); |
6956 | QVERIFY(o != nullptr); |
6957 | |
6958 | QCOMPARE(o->property("test" ).toBool(), true); |
6959 | |
6960 | delete o; |
6961 | } |
6962 | |
6963 | { |
6964 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasBindingsOverrideTarget.3.qml" )); |
6965 | QObject *o = component.create(); |
6966 | QVERIFY(o != nullptr); |
6967 | |
6968 | QCOMPARE(o->property("test" ).toBool(), true); |
6969 | |
6970 | delete o; |
6971 | } |
6972 | } |
6973 | |
6974 | // Test that writes to alias properties override bindings on the alias target (QTBUG-13719) |
6975 | void tst_qqmlecmascript::aliasWritesOverrideBindings() |
6976 | { |
6977 | QQmlEngine engine; |
6978 | { |
6979 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasWritesOverrideBindings.qml" )); |
6980 | QObject *o = component.create(); |
6981 | QVERIFY(o != nullptr); |
6982 | |
6983 | QCOMPARE(o->property("test" ).toBool(), true); |
6984 | |
6985 | delete o; |
6986 | } |
6987 | |
6988 | { |
6989 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasWritesOverrideBindings.2.qml" )); |
6990 | QObject *o = component.create(); |
6991 | QVERIFY(o != nullptr); |
6992 | |
6993 | QCOMPARE(o->property("test" ).toBool(), true); |
6994 | |
6995 | delete o; |
6996 | } |
6997 | |
6998 | { |
6999 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasWritesOverrideBindings.3.qml" )); |
7000 | QObject *o = component.create(); |
7001 | QVERIFY(o != nullptr); |
7002 | |
7003 | QCOMPARE(o->property("test" ).toBool(), true); |
7004 | |
7005 | delete o; |
7006 | } |
7007 | } |
7008 | |
7009 | // Allow an alais to a composite element |
7010 | // QTBUG-20200 |
7011 | void tst_qqmlecmascript::aliasToCompositeElement() |
7012 | { |
7013 | QQmlEngine engine; |
7014 | QQmlComponent component(&engine, testFileUrl(fileName: "aliasToCompositeElement.qml" )); |
7015 | |
7016 | QObject *object = component.create(); |
7017 | QVERIFY(object != nullptr); |
7018 | |
7019 | delete object; |
7020 | } |
7021 | |
7022 | void tst_qqmlecmascript::qtbug_20344() |
7023 | { |
7024 | QQmlEngine engine; |
7025 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_20344.qml" )); |
7026 | |
7027 | QString warning = component.url().toString() + ":5: Error: Exception thrown from within QObject slot" ; |
7028 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
7029 | |
7030 | QObject *object = component.create(); |
7031 | QVERIFY(object != nullptr); |
7032 | |
7033 | delete object; |
7034 | } |
7035 | |
7036 | void tst_qqmlecmascript::revisionErrors() |
7037 | { |
7038 | QQmlEngine engine; |
7039 | { |
7040 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevisionErrors.qml" )); |
7041 | QString url = component.url().toString(); |
7042 | |
7043 | QString warning1 = url + ":8: ReferenceError: prop2 is not defined" ; |
7044 | QString warning2 = url + ":11: ReferenceError: prop2 is not defined" ; |
7045 | QString warning3 = url + ":13: ReferenceError: method2 is not defined" ; |
7046 | |
7047 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
7048 | QTest::ignoreMessage(type: QtWarningMsg, message: warning2.toLatin1().constData()); |
7049 | QTest::ignoreMessage(type: QtWarningMsg, message: warning3.toLatin1().constData()); |
7050 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7051 | QVERIFY(object != nullptr); |
7052 | delete object; |
7053 | } |
7054 | { |
7055 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevisionErrors2.qml" )); |
7056 | QString url = component.url().toString(); |
7057 | |
7058 | // MyRevisionedSubclass 1.0 uses MyRevisionedClass revision 0 |
7059 | // method2, prop2 from MyRevisionedClass not available |
7060 | // method4, prop4 from MyRevisionedSubclass not available |
7061 | QString warning1 = url + ":8: ReferenceError: prop2 is not defined" ; |
7062 | QString warning2 = url + ":14: ReferenceError: prop2 is not defined" ; |
7063 | QString warning3 = url + ":10: ReferenceError: prop4 is not defined" ; |
7064 | QString warning4 = url + ":16: ReferenceError: prop4 is not defined" ; |
7065 | QString warning5 = url + ":20: ReferenceError: method2 is not defined" ; |
7066 | |
7067 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
7068 | QTest::ignoreMessage(type: QtWarningMsg, message: warning2.toLatin1().constData()); |
7069 | QTest::ignoreMessage(type: QtWarningMsg, message: warning3.toLatin1().constData()); |
7070 | QTest::ignoreMessage(type: QtWarningMsg, message: warning4.toLatin1().constData()); |
7071 | QTest::ignoreMessage(type: QtWarningMsg, message: warning5.toLatin1().constData()); |
7072 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7073 | QVERIFY(object != nullptr); |
7074 | delete object; |
7075 | } |
7076 | { |
7077 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevisionErrors3.qml" )); |
7078 | QString url = component.url().toString(); |
7079 | |
7080 | // MyRevisionedSubclass 1.1 uses MyRevisionedClass revision 1 |
7081 | // All properties/methods available, except MyRevisionedBaseClassUnregistered rev 1 |
7082 | QString warning1 = url + ":30: ReferenceError: methodD is not defined" ; |
7083 | QString warning2 = url + ":10: ReferenceError: propD is not defined" ; |
7084 | QString warning3 = url + ":20: ReferenceError: propD is not defined" ; |
7085 | QTest::ignoreMessage(type: QtWarningMsg, message: warning1.toLatin1().constData()); |
7086 | QTest::ignoreMessage(type: QtWarningMsg, message: warning2.toLatin1().constData()); |
7087 | QTest::ignoreMessage(type: QtWarningMsg, message: warning3.toLatin1().constData()); |
7088 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7089 | QVERIFY(object != nullptr); |
7090 | delete object; |
7091 | } |
7092 | } |
7093 | |
7094 | void tst_qqmlecmascript::revision() |
7095 | { |
7096 | QQmlEngine engine; |
7097 | { |
7098 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevision.qml" )); |
7099 | QString url = component.url().toString(); |
7100 | |
7101 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7102 | QVERIFY(object != nullptr); |
7103 | delete object; |
7104 | } |
7105 | { |
7106 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevision2.qml" )); |
7107 | QString url = component.url().toString(); |
7108 | |
7109 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7110 | QVERIFY(object != nullptr); |
7111 | delete object; |
7112 | } |
7113 | { |
7114 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevision3.qml" )); |
7115 | QString url = component.url().toString(); |
7116 | |
7117 | MyRevisionedClass *object = qobject_cast<MyRevisionedClass *>(object: component.create()); |
7118 | QVERIFY(object != nullptr); |
7119 | delete object; |
7120 | } |
7121 | // Test that non-root classes can resolve revisioned methods |
7122 | { |
7123 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevision4.qml" )); |
7124 | |
7125 | QObject *object = component.create(); |
7126 | QVERIFY(object != nullptr); |
7127 | QCOMPARE(object->property("test" ).toReal(), 11.); |
7128 | delete object; |
7129 | } |
7130 | |
7131 | { |
7132 | QQmlComponent component(&engine, testFileUrl(fileName: "metaobjectRevision5.qml" )); |
7133 | |
7134 | QObject *object = component.create(); |
7135 | QVERIFY(object != nullptr); |
7136 | QCOMPARE(object->property("test" ).toReal(), 11.); |
7137 | delete object; |
7138 | } |
7139 | } |
7140 | |
7141 | void tst_qqmlecmascript::realToInt() |
7142 | { |
7143 | QQmlEngine engine; |
7144 | QQmlComponent component(&engine, testFileUrl(fileName: "realToInt.qml" )); |
7145 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7146 | QVERIFY(object != nullptr); |
7147 | |
7148 | QMetaObject::invokeMethod(obj: object.get(), member: "test1" ); |
7149 | QCOMPARE(object->value(), int(4)); |
7150 | QMetaObject::invokeMethod(obj: object.get(), member: "test2" ); |
7151 | QCOMPARE(object->value(), int(7)); |
7152 | } |
7153 | |
7154 | void tst_qqmlecmascript::urlProperty() |
7155 | { |
7156 | QQmlEngine engine; |
7157 | { |
7158 | QQmlComponent component(&engine, testFileUrl(fileName: "urlProperty.1.qml" )); |
7159 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7160 | QVERIFY(object != nullptr); |
7161 | object->setStringProperty("http://qt-project.org" ); |
7162 | QCOMPARE(object->urlProperty(), QUrl("http://qt-project.org/index.html" )); |
7163 | QCOMPARE(object->intProperty(), 123); |
7164 | QCOMPARE(object->value(), 1); |
7165 | QCOMPARE(object->property("result" ).toBool(), true); |
7166 | } |
7167 | } |
7168 | |
7169 | void tst_qqmlecmascript::urlPropertyWithEncoding() |
7170 | { |
7171 | QQmlEngine engine; |
7172 | { |
7173 | QQmlComponent component(&engine, testFileUrl(fileName: "urlProperty.2.qml" )); |
7174 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7175 | QVERIFY(object != nullptr); |
7176 | object->setStringProperty("http://qt-project.org" ); |
7177 | const QUrl encoded = QUrl::fromEncoded(url: "http://qt-project.org/?get%3cDATA%3e" , mode: QUrl::TolerantMode); |
7178 | QCOMPARE(object->urlProperty(), encoded); |
7179 | QCOMPARE(object->value(), 0); // Interpreting URL as string yields canonicalised version |
7180 | QCOMPARE(object->property("result" ).toBool(), true); |
7181 | } |
7182 | } |
7183 | |
7184 | void tst_qqmlecmascript::urlListPropertyWithEncoding() |
7185 | { |
7186 | QQmlEngine engine; |
7187 | { |
7188 | QQmlComponent component(&engine, testFileUrl(fileName: "urlListProperty.qml" )); |
7189 | QObject *object = component.create(); |
7190 | QVERIFY(object != nullptr); |
7191 | MySequenceConversionObject *msco1 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco1" )); |
7192 | MySequenceConversionObject *msco2 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco2" )); |
7193 | MySequenceConversionObject *msco3 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco3" )); |
7194 | MySequenceConversionObject *msco4 = object->findChild<MySequenceConversionObject *>(aName: QLatin1String("msco4" )); |
7195 | QVERIFY(msco1 != nullptr && msco2 != nullptr && msco3 != nullptr && msco4 != nullptr); |
7196 | const QUrl encoded = QUrl::fromEncoded(url: "http://qt-project.org/?get%3cDATA%3e" , mode: QUrl::TolerantMode); |
7197 | QCOMPARE(msco1->urlListProperty(), (QList<QUrl>() << encoded)); |
7198 | QCOMPARE(msco2->urlListProperty(), (QList<QUrl>() << encoded)); |
7199 | QCOMPARE(msco3->urlListProperty(), (QList<QUrl>() << encoded << encoded)); |
7200 | QCOMPARE(msco4->urlListProperty(), (QList<QUrl>() << encoded << encoded)); |
7201 | delete object; |
7202 | } |
7203 | } |
7204 | |
7205 | void tst_qqmlecmascript::dynamicString() |
7206 | { |
7207 | QQmlEngine engine; |
7208 | QQmlComponent component(&engine, testFileUrl(fileName: "dynamicString.qml" )); |
7209 | QScopedPointer<QObject> object(component.create()); |
7210 | QVERIFY(object != nullptr); |
7211 | QCOMPARE(object->property("stringProperty" ).toString(), |
7212 | QString::fromLatin1("string:Hello World false:0 true:1 uint32:100 int32:-100 double:3.14159 date:2011-02-11 05::30:50!" )); |
7213 | } |
7214 | |
7215 | void tst_qqmlecmascript::deleteLaterObjectMethodCall() |
7216 | { |
7217 | QQmlEngine engine; |
7218 | QQmlComponent component(&engine, testFileUrl(fileName: "deleteLaterObjectMethodCall.qml" )); |
7219 | QScopedPointer<QObject> object(component.create()); |
7220 | QVERIFY(object != nullptr); |
7221 | } |
7222 | |
7223 | void tst_qqmlecmascript::automaticSemicolon() |
7224 | { |
7225 | QQmlEngine engine; |
7226 | QQmlComponent component(&engine, testFileUrl(fileName: "automaticSemicolon.qml" )); |
7227 | QScopedPointer<QObject> object(component.create()); |
7228 | QVERIFY(object != nullptr); |
7229 | } |
7230 | |
7231 | void tst_qqmlecmascript::compatibilitySemicolon() |
7232 | { |
7233 | QQmlEngine engine; |
7234 | QQmlComponent component(&engine, testFileUrl(fileName: "compatibilitySemicolon.qml" )); |
7235 | QScopedPointer<QObject> object(component.create()); |
7236 | QVERIFY(object != nullptr); |
7237 | } |
7238 | |
7239 | void tst_qqmlecmascript::incrDecrSemicolon1() |
7240 | { |
7241 | QQmlEngine engine; |
7242 | QQmlComponent component(&engine, testFileUrl(fileName: "incrDecrSemicolon1.qml" )); |
7243 | QScopedPointer<QObject> object(component.create()); |
7244 | QVERIFY(object != nullptr); |
7245 | } |
7246 | |
7247 | void tst_qqmlecmascript::incrDecrSemicolon2() |
7248 | { |
7249 | QQmlEngine engine; |
7250 | QQmlComponent component(&engine, testFileUrl(fileName: "incrDecrSemicolon2.qml" )); |
7251 | QScopedPointer<QObject> object(component.create()); |
7252 | QVERIFY(object != nullptr); |
7253 | } |
7254 | |
7255 | void tst_qqmlecmascript::incrDecrSemicolon_error1() |
7256 | { |
7257 | QQmlEngine engine; |
7258 | QQmlComponent component(&engine, testFileUrl(fileName: "incrDecrSemicolon_error1.qml" )); |
7259 | QObject *object = component.create(); |
7260 | QVERIFY(!object); |
7261 | } |
7262 | |
7263 | void tst_qqmlecmascript::unaryExpression() |
7264 | { |
7265 | QQmlEngine engine; |
7266 | QQmlComponent component(&engine, testFileUrl(fileName: "unaryExpression.qml" )); |
7267 | QScopedPointer<QObject> object(component.create()); |
7268 | QVERIFY(object != nullptr); |
7269 | } |
7270 | |
7271 | // Makes sure that a binding isn't double re-evaluated when it depends on the same variable twice |
7272 | void tst_qqmlecmascript::doubleEvaluate() |
7273 | { |
7274 | QQmlEngine engine; |
7275 | QQmlComponent component(&engine, testFileUrl(fileName: "doubleEvaluate.qml" )); |
7276 | QObject *object = component.create(); |
7277 | QVERIFY(object != nullptr); |
7278 | WriteCounter *wc = qobject_cast<WriteCounter *>(object); |
7279 | QVERIFY(wc != nullptr); |
7280 | QCOMPARE(wc->count(), 1); |
7281 | |
7282 | wc->setProperty(name: "x" , value: 9); |
7283 | |
7284 | QCOMPARE(wc->count(), 2); |
7285 | |
7286 | delete object; |
7287 | } |
7288 | |
7289 | void tst_qqmlecmascript::nonNotifyable() |
7290 | { |
7291 | QQmlEngine engine; |
7292 | QQmlComponent component(&engine, testFileUrl(fileName: "nonNotifyable.qml" )); |
7293 | |
7294 | QQmlTestMessageHandler messageHandler; |
7295 | |
7296 | QObject *object = component.create(); |
7297 | |
7298 | QVERIFY(object != nullptr); |
7299 | |
7300 | QString expected1 = QLatin1String("QQmlExpression: Expression " ) + |
7301 | component.url().toString() + |
7302 | QLatin1String(":5:5 depends on non-NOTIFYable properties:" ); |
7303 | QString expected2 = QLatin1String(" " ) + |
7304 | QLatin1String(object->metaObject()->className()) + |
7305 | QLatin1String("::value" ); |
7306 | |
7307 | QCOMPARE(messageHandler.messages().length(), 2); |
7308 | QCOMPARE(messageHandler.messages().at(0), expected1); |
7309 | QCOMPARE(messageHandler.messages().at(1), expected2); |
7310 | |
7311 | delete object; |
7312 | } |
7313 | |
7314 | void tst_qqmlecmascript::nonNotifyableConstant() |
7315 | { |
7316 | QQmlEngine engine; |
7317 | QQmlComponent component(&engine, testFileUrl(fileName: "nonNotifyableConstant.qml" )); |
7318 | QQmlTestMessageHandler messageHandler; |
7319 | |
7320 | // Shouldn't produce an error message about non-NOTIFYable properties, |
7321 | // as the property has the CONSTANT attribute. |
7322 | QScopedPointer<QObject> object(component.create()); |
7323 | QVERIFY(object); |
7324 | QCOMPARE(messageHandler.messages().size(), 0); |
7325 | } |
7326 | |
7327 | void tst_qqmlecmascript::forInLoop() |
7328 | { |
7329 | QQmlEngine engine; |
7330 | QQmlComponent component(&engine, testFileUrl(fileName: "forInLoop.qml" )); |
7331 | QObject *object = component.create(); |
7332 | QVERIFY(object != nullptr); |
7333 | |
7334 | QMetaObject::invokeMethod(obj: object, member: "listProperty" ); |
7335 | |
7336 | QStringList r = object->property(name: "listResult" ).toString().split(sep: "|" , behavior: Qt::SkipEmptyParts); |
7337 | QCOMPARE(r.size(), 3); |
7338 | QCOMPARE(r[0],QLatin1String("0=obj1" )); |
7339 | QCOMPARE(r[1],QLatin1String("1=obj2" )); |
7340 | QCOMPARE(r[2],QLatin1String("2=obj3" )); |
7341 | |
7342 | //TODO: should test for in loop for other objects (such as QObjects) as well. |
7343 | |
7344 | delete object; |
7345 | } |
7346 | |
7347 | // An object the binding depends on is deleted while the binding is still running |
7348 | void tst_qqmlecmascript::deleteWhileBindingRunning() |
7349 | { |
7350 | QQmlEngine engine; |
7351 | QQmlComponent component(&engine, testFileUrl(fileName: "deleteWhileBindingRunning.qml" )); |
7352 | QObject *object = component.create(); |
7353 | QVERIFY(object != nullptr); |
7354 | delete object; |
7355 | } |
7356 | |
7357 | void tst_qqmlecmascript::qtbug_22679() |
7358 | { |
7359 | MyQmlObject object; |
7360 | object.setStringProperty(QLatin1String("Please work correctly" )); |
7361 | QQmlEngine engine; |
7362 | engine.rootContext()->setContextProperty("contextProp" , &object); |
7363 | |
7364 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_22679.qml" )); |
7365 | qRegisterMetaType<QList<QQmlError> >(typeName: "QList<QQmlError>" ); |
7366 | QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); |
7367 | |
7368 | QObject *o = component.create(); |
7369 | QVERIFY(o != nullptr); |
7370 | QCOMPARE(warningsSpy.count(), 0); |
7371 | delete o; |
7372 | } |
7373 | |
7374 | void tst_qqmlecmascript::qtbug_22843_data() |
7375 | { |
7376 | QTest::addColumn<bool>(name: "library" ); |
7377 | |
7378 | QTest::newRow(dataTag: "without .pragma library" ) << false; |
7379 | QTest::newRow(dataTag: "with .pragma library" ) << true; |
7380 | } |
7381 | |
7382 | void tst_qqmlecmascript::qtbug_22843() |
7383 | { |
7384 | QFETCH(bool, library); |
7385 | |
7386 | QString fileName("qtbug_22843" ); |
7387 | if (library) |
7388 | fileName += QLatin1String(".library" ); |
7389 | fileName += QLatin1String(".qml" ); |
7390 | |
7391 | QQmlEngine engine; |
7392 | QQmlComponent component(&engine, testFileUrl(fileName)); |
7393 | |
7394 | QString url = component.url().toString(); |
7395 | QString expectedError = url.left(n: url.length()-3) + QLatin1String("js:4:16: Expected token `;'" ); |
7396 | |
7397 | QVERIFY(component.isError()); |
7398 | QCOMPARE(component.errors().value(1).toString(), expectedError); |
7399 | } |
7400 | |
7401 | |
7402 | void tst_qqmlecmascript::switchStatement() |
7403 | { |
7404 | QQmlEngine engine; |
7405 | { |
7406 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.1.qml" )); |
7407 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7408 | QVERIFY(object != nullptr); |
7409 | |
7410 | // `object->value()' is the number of executed statements |
7411 | |
7412 | object->setStringProperty("A" ); |
7413 | QCOMPARE(object->value(), 5); |
7414 | |
7415 | object->setStringProperty("S" ); |
7416 | QCOMPARE(object->value(), 3); |
7417 | |
7418 | object->setStringProperty("D" ); |
7419 | QCOMPARE(object->value(), 3); |
7420 | |
7421 | object->setStringProperty("F" ); |
7422 | QCOMPARE(object->value(), 4); |
7423 | |
7424 | object->setStringProperty("something else" ); |
7425 | QCOMPARE(object->value(), 1); |
7426 | } |
7427 | |
7428 | { |
7429 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.2.qml" )); |
7430 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7431 | QVERIFY(object != nullptr); |
7432 | |
7433 | // `object->value()' is the number of executed statements |
7434 | |
7435 | object->setStringProperty("A" ); |
7436 | QCOMPARE(object->value(), 5); |
7437 | |
7438 | object->setStringProperty("S" ); |
7439 | QCOMPARE(object->value(), 3); |
7440 | |
7441 | object->setStringProperty("D" ); |
7442 | QCOMPARE(object->value(), 3); |
7443 | |
7444 | object->setStringProperty("F" ); |
7445 | QCOMPARE(object->value(), 3); |
7446 | |
7447 | object->setStringProperty("something else" ); |
7448 | QCOMPARE(object->value(), 4); |
7449 | } |
7450 | |
7451 | { |
7452 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.3.qml" )); |
7453 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7454 | QVERIFY(object != nullptr); |
7455 | |
7456 | // `object->value()' is the number of executed statements |
7457 | |
7458 | object->setStringProperty("A" ); |
7459 | QCOMPARE(object->value(), 5); |
7460 | |
7461 | object->setStringProperty("S" ); |
7462 | QCOMPARE(object->value(), 3); |
7463 | |
7464 | object->setStringProperty("D" ); |
7465 | QCOMPARE(object->value(), 3); |
7466 | |
7467 | object->setStringProperty("F" ); |
7468 | QCOMPARE(object->value(), 3); |
7469 | |
7470 | object->setStringProperty("something else" ); |
7471 | QCOMPARE(object->value(), 6); |
7472 | } |
7473 | |
7474 | { |
7475 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.4.qml" )); |
7476 | |
7477 | QString warning = component.url().toString() + ":4:5: Unable to assign [undefined] to int" ; |
7478 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
7479 | |
7480 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7481 | QVERIFY(object != nullptr); |
7482 | |
7483 | // `object->value()' is the number of executed statements |
7484 | |
7485 | object->setStringProperty("A" ); |
7486 | QCOMPARE(object->value(), 5); |
7487 | |
7488 | object->setStringProperty("S" ); |
7489 | QCOMPARE(object->value(), 3); |
7490 | |
7491 | object->setStringProperty("D" ); |
7492 | QCOMPARE(object->value(), 3); |
7493 | |
7494 | object->setStringProperty("F" ); |
7495 | QCOMPARE(object->value(), 3); |
7496 | |
7497 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
7498 | |
7499 | object->setStringProperty("something else" ); |
7500 | } |
7501 | |
7502 | { |
7503 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.5.qml" )); |
7504 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7505 | QVERIFY(object != nullptr); |
7506 | |
7507 | // `object->value()' is the number of executed statements |
7508 | |
7509 | object->setStringProperty("A" ); |
7510 | QCOMPARE(object->value(), 1); |
7511 | |
7512 | object->setStringProperty("S" ); |
7513 | QCOMPARE(object->value(), 1); |
7514 | |
7515 | object->setStringProperty("D" ); |
7516 | QCOMPARE(object->value(), 1); |
7517 | |
7518 | object->setStringProperty("F" ); |
7519 | QCOMPARE(object->value(), 1); |
7520 | |
7521 | object->setStringProperty("something else" ); |
7522 | QCOMPARE(object->value(), 1); |
7523 | } |
7524 | |
7525 | { |
7526 | QQmlComponent component(&engine, testFileUrl(fileName: "switchStatement.6.qml" )); |
7527 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7528 | QVERIFY(object != nullptr); |
7529 | |
7530 | // `object->value()' is the number of executed statements |
7531 | |
7532 | object->setStringProperty("A" ); |
7533 | QCOMPARE(object->value(), 123); |
7534 | |
7535 | object->setStringProperty("S" ); |
7536 | QCOMPARE(object->value(), 123); |
7537 | |
7538 | object->setStringProperty("D" ); |
7539 | QCOMPARE(object->value(), 321); |
7540 | |
7541 | object->setStringProperty("F" ); |
7542 | QCOMPARE(object->value(), 321); |
7543 | |
7544 | object->setStringProperty("something else" ); |
7545 | QCOMPARE(object->value(), 0); |
7546 | } |
7547 | } |
7548 | |
7549 | void tst_qqmlecmascript::withStatement() |
7550 | { |
7551 | QQmlEngine engine; |
7552 | { |
7553 | QUrl url = testFileUrl(fileName: "withStatement.1.qml" ); |
7554 | QQmlComponent component(&engine, url); |
7555 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7556 | QVERIFY(object != nullptr); |
7557 | |
7558 | QCOMPARE(object->value(), 123); |
7559 | } |
7560 | } |
7561 | |
7562 | void tst_qqmlecmascript::tryStatement() |
7563 | { |
7564 | QQmlEngine engine; |
7565 | { |
7566 | QQmlComponent component(&engine, testFileUrl(fileName: "tryStatement.1.qml" )); |
7567 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7568 | QVERIFY(object != nullptr); |
7569 | |
7570 | QCOMPARE(object->value(), 123); |
7571 | } |
7572 | |
7573 | { |
7574 | QQmlComponent component(&engine, testFileUrl(fileName: "tryStatement.2.qml" )); |
7575 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7576 | QVERIFY(object != nullptr); |
7577 | |
7578 | QCOMPARE(object->value(), 321); |
7579 | } |
7580 | |
7581 | { |
7582 | QQmlComponent component(&engine, testFileUrl(fileName: "tryStatement.3.qml" )); |
7583 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7584 | QVERIFY(object != nullptr); |
7585 | |
7586 | QVERIFY(object->qjsvalue().isUndefined()); |
7587 | } |
7588 | |
7589 | { |
7590 | QQmlComponent component(&engine, testFileUrl(fileName: "tryStatement.4.qml" )); |
7591 | QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(object: component.create())); |
7592 | QVERIFY(object != nullptr); |
7593 | |
7594 | QVERIFY(object->qjsvalue().isUndefined()); |
7595 | } |
7596 | } |
7597 | |
7598 | class CppInvokableWithQObjectDerived : public QObject |
7599 | { |
7600 | Q_OBJECT |
7601 | public: |
7602 | CppInvokableWithQObjectDerived() {} |
7603 | ~CppInvokableWithQObjectDerived() {} |
7604 | |
7605 | Q_INVOKABLE MyQmlObject *createMyQmlObject(QString data) |
7606 | { |
7607 | MyQmlObject *obj = new MyQmlObject(); |
7608 | obj->setStringProperty(data); |
7609 | return obj; |
7610 | } |
7611 | |
7612 | Q_INVOKABLE QString getStringProperty(MyQmlObject *obj) |
7613 | { |
7614 | return obj->stringProperty(); |
7615 | } |
7616 | }; |
7617 | |
7618 | void tst_qqmlecmascript::invokableWithQObjectDerived() |
7619 | { |
7620 | CppInvokableWithQObjectDerived invokable; |
7621 | |
7622 | { |
7623 | QQmlEngine engine; |
7624 | engine.rootContext()->setContextProperty("invokable" , &invokable); |
7625 | |
7626 | QQmlComponent component(&engine, testFileUrl(fileName: "qobjectDerivedArgument.qml" )); |
7627 | |
7628 | QObject *object = component.create(); |
7629 | |
7630 | QVERIFY(object != nullptr); |
7631 | QVERIFY(object->property("result" ).value<bool>()); |
7632 | |
7633 | delete object; |
7634 | } |
7635 | } |
7636 | |
7637 | void tst_qqmlecmascript::realTypePrecision() |
7638 | { |
7639 | // Properties and signal parameters of type real should have double precision. |
7640 | QQmlEngine engine; |
7641 | QQmlComponent component(&engine, testFileUrl(fileName: "realTypePrecision.qml" )); |
7642 | QScopedPointer<QObject> object(component.create()); |
7643 | QVERIFY(object != nullptr); |
7644 | QCOMPARE(object->property("test" ).toDouble(), 1234567890.); |
7645 | QCOMPARE(object->property("test2" ).toDouble(), 1234567890.); |
7646 | QCOMPARE(object->property("test3" ).toDouble(), 1234567890.); |
7647 | QCOMPARE(object->property("test4" ).toDouble(), 1234567890.); |
7648 | QCOMPARE(object->property("test5" ).toDouble(), 1234567890.); |
7649 | QCOMPARE(object->property("test6" ).toDouble(), 1234567890.*2); |
7650 | } |
7651 | |
7652 | void tst_qqmlecmascript::registeredFlagMethod() |
7653 | { |
7654 | QQmlEngine engine; |
7655 | QQmlComponent component(&engine, testFileUrl(fileName: "registeredFlagMethod.qml" )); |
7656 | MyQmlObject *object = qobject_cast<MyQmlObject *>(object: component.create()); |
7657 | QVERIFY(object != nullptr); |
7658 | |
7659 | QCOMPARE(object->buttons(), 0); |
7660 | emit object->basicSignal(); |
7661 | QCOMPARE(object->buttons(), Qt::RightButton); |
7662 | |
7663 | delete object; |
7664 | } |
7665 | |
7666 | // QTBUG-23138 |
7667 | void tst_qqmlecmascript::replaceBinding() |
7668 | { |
7669 | QQmlEngine engine; |
7670 | QQmlComponent c(&engine, testFileUrl(fileName: "replaceBinding.qml" )); |
7671 | QObject *obj = c.create(); |
7672 | QVERIFY(obj != nullptr); |
7673 | |
7674 | QVERIFY(obj->property("success" ).toBool()); |
7675 | delete obj; |
7676 | } |
7677 | |
7678 | void tst_qqmlecmascript::bindingBoundFunctions() |
7679 | { |
7680 | QQmlEngine engine; |
7681 | QQmlComponent c(&engine, testFileUrl(fileName: "bindingBoundFunctions.qml" )); |
7682 | QObject *obj = c.create(); |
7683 | QVERIFY(obj != nullptr); |
7684 | |
7685 | QVERIFY(obj->property("success" ).toBool()); |
7686 | delete obj; |
7687 | } |
7688 | |
7689 | void tst_qqmlecmascript::deleteRootObjectInCreation() |
7690 | { |
7691 | QQmlEngine engine; |
7692 | { |
7693 | QQmlComponent c(&engine, testFileUrl(fileName: "deleteRootObjectInCreation.qml" )); |
7694 | QObject *obj = c.create(); |
7695 | QVERIFY(obj != nullptr); |
7696 | QVERIFY(obj->property("rootIndestructible" ).toBool()); |
7697 | QVERIFY(!obj->property("childDestructible" ).toBool()); |
7698 | QTest::qWait(ms: 1); |
7699 | QVERIFY(obj->property("childDestructible" ).toBool()); |
7700 | delete obj; |
7701 | } |
7702 | |
7703 | { |
7704 | QQmlComponent c(&engine, testFileUrl(fileName: "deleteRootObjectInCreation.2.qml" )); |
7705 | QObject *object = c.create(); |
7706 | QVERIFY(object != nullptr); |
7707 | QVERIFY(object->property("testConditionsMet" ).toBool()); |
7708 | delete object; |
7709 | } |
7710 | } |
7711 | |
7712 | void tst_qqmlecmascript::onDestruction() |
7713 | { |
7714 | { |
7715 | // Delete object manually to invoke the associated handlers, |
7716 | // prior to engine destruction. |
7717 | QQmlEngine engine; |
7718 | QQmlComponent c(&engine, testFileUrl(fileName: "onDestruction.qml" )); |
7719 | QObject *obj = c.create(); |
7720 | QVERIFY(obj != nullptr); |
7721 | delete obj; |
7722 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
7723 | } |
7724 | |
7725 | { |
7726 | // In this case, the teardown of the engine causes deletion |
7727 | // of contexts and child items. This triggers the |
7728 | // onDestruction handler of a (previously .destroy()ed) |
7729 | // component instance. This shouldn't crash. |
7730 | QQmlEngine engine; |
7731 | QQmlComponent c(&engine, testFileUrl(fileName: "onDestruction.qml" )); |
7732 | QScopedPointer<QObject> obj(c.create()); |
7733 | QVERIFY(obj != nullptr); |
7734 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
7735 | } |
7736 | } |
7737 | |
7738 | class WeakReferenceMutator : public QObject |
7739 | { |
7740 | Q_OBJECT |
7741 | public: |
7742 | WeakReferenceMutator() |
7743 | : resultPtr(nullptr) |
7744 | , weakRef(nullptr) |
7745 | {} |
7746 | |
7747 | void init(QV4::ExecutionEngine *v4, QV4::WeakValue *weakRef, bool *resultPtr) |
7748 | { |
7749 | QV4::QObjectWrapper::wrap(engine: v4, object: this); |
7750 | QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); |
7751 | |
7752 | this->resultPtr = resultPtr; |
7753 | this->weakRef = weakRef; |
7754 | |
7755 | QObject::connect(sender: QQmlComponent::qmlAttachedProperties(this), signal: &QQmlComponentAttached::destruction, receiver: this, slot: &WeakReferenceMutator::reviveFirstWeakReference); |
7756 | } |
7757 | |
7758 | private slots: |
7759 | void reviveFirstWeakReference() { |
7760 | // weakRef is not required to be undefined here. The gc can clear it later. |
7761 | *resultPtr = weakRef->valueRef(); |
7762 | if (!*resultPtr) |
7763 | return; |
7764 | QV4::ExecutionEngine *v4 = qmlEngine(this)->handle(); |
7765 | weakRef->set(engine: v4, obj: v4->newObject()); |
7766 | *resultPtr = weakRef->valueRef() && !weakRef->isNullOrUndefined(); |
7767 | } |
7768 | |
7769 | public: |
7770 | bool *resultPtr; |
7771 | |
7772 | QV4::WeakValue *weakRef; |
7773 | }; |
7774 | |
7775 | QT_BEGIN_NAMESPACE |
7776 | |
7777 | namespace QV4 { |
7778 | |
7779 | namespace Heap { |
7780 | struct WeakReferenceSentinel : public Object { |
7781 | void init(WeakValue *weakRef, bool *resultPtr) |
7782 | { |
7783 | Object::init(); |
7784 | this->weakRef = weakRef; |
7785 | this->resultPtr = resultPtr; |
7786 | } |
7787 | |
7788 | void destroy() { |
7789 | *resultPtr = weakRef->isNullOrUndefined(); |
7790 | Object::destroy(); |
7791 | } |
7792 | |
7793 | WeakValue *weakRef; |
7794 | bool *resultPtr; |
7795 | }; |
7796 | } // namespace Heap |
7797 | |
7798 | struct WeakReferenceSentinel : public Object { |
7799 | V4_OBJECT2(WeakReferenceSentinel, Object) |
7800 | V4_NEEDS_DESTROY |
7801 | }; |
7802 | |
7803 | } // namespace QV4 |
7804 | |
7805 | QT_END_NAMESPACE |
7806 | |
7807 | DEFINE_OBJECT_VTABLE(QV4::WeakReferenceSentinel); |
7808 | |
7809 | void tst_qqmlecmascript::onDestructionViaGC() |
7810 | { |
7811 | qmlRegisterType<WeakReferenceMutator>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "WeakReferenceMutator" ); |
7812 | |
7813 | QQmlEngine engine; |
7814 | QV4::ExecutionEngine *v4 =engine.handle(); |
7815 | |
7816 | QQmlComponent component(&engine, testFileUrl(fileName: "DestructionHelper.qml" )); |
7817 | |
7818 | QScopedPointer<QV4::WeakValue> weakRef; |
7819 | |
7820 | bool mutatorResult = false; |
7821 | bool sentinelResult = false; |
7822 | |
7823 | { |
7824 | weakRef.reset(other: new QV4::WeakValue); |
7825 | weakRef->set(engine: v4, obj: v4->newObject()); |
7826 | QVERIFY(!weakRef->isNullOrUndefined()); |
7827 | |
7828 | QPointer<WeakReferenceMutator> weakReferenceMutator = qobject_cast<WeakReferenceMutator *>(object: component.create()); |
7829 | QVERIFY2(!weakReferenceMutator.isNull(), qPrintable(component.errorString())); |
7830 | weakReferenceMutator->init(v4, weakRef: weakRef.data(), resultPtr: &mutatorResult); |
7831 | |
7832 | v4->memoryManager->allocate<QV4::WeakReferenceSentinel>(args: weakRef.data(), args: &sentinelResult); |
7833 | } |
7834 | gc(engine); |
7835 | |
7836 | QVERIFY2(mutatorResult, "We failed to re-assign the weak reference a new value during GC" ); |
7837 | QVERIFY2(sentinelResult, "The weak reference was not cleared properly" ); |
7838 | } |
7839 | |
7840 | struct EventProcessor : public QObject |
7841 | { |
7842 | Q_OBJECT |
7843 | public: |
7844 | Q_INVOKABLE void process() |
7845 | { |
7846 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
7847 | QCoreApplication::processEvents(); |
7848 | } |
7849 | }; |
7850 | |
7851 | void tst_qqmlecmascript::bindingSuppression() |
7852 | { |
7853 | QQmlEngine engine; |
7854 | EventProcessor processor; |
7855 | engine.rootContext()->setContextProperty("pendingEvents" , &processor); |
7856 | |
7857 | QQmlTestMessageHandler messageHandler; |
7858 | |
7859 | QQmlComponent c(&engine, testFileUrl(fileName: "bindingSuppression.qml" )); |
7860 | QObject *obj = c.create(); |
7861 | QVERIFY(obj != nullptr); |
7862 | delete obj; |
7863 | |
7864 | QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); |
7865 | } |
7866 | |
7867 | void tst_qqmlecmascript::signalEmitted() |
7868 | { |
7869 | { |
7870 | // calling destroy on the sibling. |
7871 | QQmlEngine engine; |
7872 | QQmlComponent c(&engine, testFileUrl(fileName: "signalEmitted.2.qml" )); |
7873 | QObject *obj = c.create(); |
7874 | QVERIFY(obj != nullptr); |
7875 | QTRY_VERIFY(obj->property("success" ).toBool()); |
7876 | delete obj; |
7877 | } |
7878 | |
7879 | { |
7880 | // allowing gc to clean up the sibling. |
7881 | QQmlEngine engine; |
7882 | QQmlComponent c(&engine, testFileUrl(fileName: "signalEmitted.3.qml" )); |
7883 | QObject *obj = c.create(); |
7884 | QVERIFY(obj != nullptr); |
7885 | gc(engine); // should collect c1. |
7886 | QTRY_VERIFY(obj->property("success" ).toBool()); |
7887 | delete obj; |
7888 | } |
7889 | |
7890 | { |
7891 | // allowing gc to clean up the sibling after manually destroying target. |
7892 | QQmlEngine engine; |
7893 | QQmlComponent c(&engine, testFileUrl(fileName: "signalEmitted.4.qml" )); |
7894 | QObject *obj = c.create(); |
7895 | QVERIFY(obj != nullptr); |
7896 | gc(engine); // should collect c1. |
7897 | QMetaObject::invokeMethod(obj, member: "destroyC2" ); |
7898 | QTRY_VERIFY(obj->property("success" ).toBool()); // handles events (incl. delete later). |
7899 | delete obj; |
7900 | } |
7901 | } |
7902 | |
7903 | // QTBUG-25647 |
7904 | void tst_qqmlecmascript::threadSignal() |
7905 | { |
7906 | QQmlEngine engine; |
7907 | { |
7908 | QQmlComponent c(&engine, testFileUrl(fileName: "threadSignal.qml" )); |
7909 | QScopedPointer<QObject> object(c.create()); |
7910 | QVERIFY(!object.isNull()); |
7911 | QTRY_VERIFY(object->property("passed" ).toBool()); |
7912 | } |
7913 | { |
7914 | QQmlComponent c(&engine, testFileUrl(fileName: "threadSignal.2.qml" )); |
7915 | QScopedPointer<QObject> object(c.create()); |
7916 | QVERIFY(!object.isNull()); |
7917 | QMetaObject::invokeMethod(obj: object.data(), member: "doIt" ); |
7918 | QTRY_VERIFY(object->property("passed" ).toBool()); |
7919 | } |
7920 | } |
7921 | |
7922 | // ensure that the qqmldata::destroyed() handler doesn't cause problems |
7923 | void tst_qqmlecmascript::qqmldataDestroyed() |
7924 | { |
7925 | QQmlEngine engine; |
7926 | // gc cleans up a qobject, later the qqmldata destroyed handler will run. |
7927 | { |
7928 | QQmlComponent c(&engine, testFileUrl(fileName: "qqmldataDestroyed.qml" )); |
7929 | QObject *object = c.create(); |
7930 | QVERIFY(object != nullptr); |
7931 | // now gc causing the collection of the dynamically constructed object. |
7932 | engine.collectGarbage(); |
7933 | engine.collectGarbage(); |
7934 | // now process events to allow deletion (calling qqmldata::destroyed()) |
7935 | QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete); |
7936 | QCoreApplication::processEvents(); |
7937 | // shouldn't crash. |
7938 | delete object; |
7939 | } |
7940 | |
7941 | // in this case, the object has CPP ownership, and the gc will |
7942 | // be triggered during its beginCreate stage. |
7943 | { |
7944 | QQmlComponent c(&engine, testFileUrl(fileName: "qqmldataDestroyed.2.qml" )); |
7945 | QObject *object = c.create(); |
7946 | QVERIFY(object != nullptr); |
7947 | QVERIFY(object->property("testConditionsMet" ).toBool()); |
7948 | // the gc() within the handler will have triggered the weak |
7949 | // qobject reference callback. If that incorrectly disposes |
7950 | // the handle, when the qqmldata::destroyed() handler is |
7951 | // called due to object deletion we will see a crash. |
7952 | delete object; |
7953 | // shouldn't have crashed. |
7954 | } |
7955 | } |
7956 | |
7957 | void tst_qqmlecmascript::secondAlias() |
7958 | { |
7959 | QQmlEngine engine; |
7960 | QQmlComponent c(&engine, testFileUrl(fileName: "secondAlias.qml" )); |
7961 | QObject *object = c.create(); |
7962 | QVERIFY(object != nullptr); |
7963 | QCOMPARE(object->property("test" ).toInt(), 200); |
7964 | delete object; |
7965 | } |
7966 | |
7967 | // An alias to a var property works |
7968 | void tst_qqmlecmascript::varAlias() |
7969 | { |
7970 | QQmlEngine engine; |
7971 | QQmlComponent c(&engine, testFileUrl(fileName: "varAlias.qml" )); |
7972 | QObject *object = c.create(); |
7973 | QVERIFY(object != nullptr); |
7974 | QCOMPARE(object->property("test" ).toInt(), 192); |
7975 | delete object; |
7976 | } |
7977 | |
7978 | // Used to trigger an assert in the lazy meta object creation stage |
7979 | void tst_qqmlecmascript::overrideDataAssert() |
7980 | { |
7981 | QQmlEngine engine; |
7982 | QQmlComponent c(&engine, testFileUrl(fileName: "overrideDataAssert.qml" )); |
7983 | QObject *object = c.create(); |
7984 | QVERIFY(object != nullptr); |
7985 | object->metaObject(); |
7986 | delete object; |
7987 | } |
7988 | |
7989 | void tst_qqmlecmascript::fallbackBindings_data() |
7990 | { |
7991 | QTest::addColumn<QString>(name: "source" ); |
7992 | |
7993 | QTest::newRow(dataTag: "Property without fallback" ) << "fallbackBindings.1.qml" ; |
7994 | QTest::newRow(dataTag: "Property fallback" ) << "fallbackBindings.2.qml" ; |
7995 | QTest::newRow(dataTag: "SingletonType without fallback" ) << "fallbackBindings.3.qml" ; |
7996 | QTest::newRow(dataTag: "SingletonType fallback" ) << "fallbackBindings.4.qml" ; |
7997 | QTest::newRow(dataTag: "Attached without fallback" ) << "fallbackBindings.5.qml" ; |
7998 | QTest::newRow(dataTag: "Attached fallback" ) << "fallbackBindings.6.qml" ; |
7999 | QTest::newRow(dataTag: "Subproperty without fallback" ) << "fallbackBindings.7.qml" ; |
8000 | QTest::newRow(dataTag: "Subproperty fallback" ) << "fallbackBindings.8.qml" ; |
8001 | } |
8002 | |
8003 | void tst_qqmlecmascript::fallbackBindings() |
8004 | { |
8005 | QFETCH(QString, source); |
8006 | |
8007 | QQmlEngine engine; |
8008 | QQmlComponent component(&engine, testFileUrl(fileName: source)); |
8009 | QScopedPointer<QObject> object(component.create()); |
8010 | QVERIFY(object != nullptr); |
8011 | |
8012 | QCOMPARE(object->property("success" ).toBool(), true); |
8013 | } |
8014 | |
8015 | void tst_qqmlecmascript::propertyOverride() |
8016 | { |
8017 | QQmlEngine engine; |
8018 | QQmlComponent component(&engine, testFileUrl(fileName: "propertyOverride.qml" )); |
8019 | QScopedPointer<QObject> object(component.create()); |
8020 | QVERIFY(object != nullptr); |
8021 | |
8022 | QCOMPARE(object->property("success" ).toBool(), true); |
8023 | } |
8024 | |
8025 | void tst_qqmlecmascript::sequenceSort_data() |
8026 | { |
8027 | QTest::addColumn<QString>(name: "function" ); |
8028 | QTest::addColumn<bool>(name: "useComparer" ); |
8029 | |
8030 | QTest::newRow(dataTag: "qtbug_25269" ) << "test_qtbug_25269" << false; |
8031 | |
8032 | const char *types[] = { "alphabet" , "numbers" , "reals" , "number_vector" , "real_vector" }; |
8033 | const char *sort[] = { "insertionSort" , "quickSort" }; |
8034 | |
8035 | for (size_t t=0 ; t < sizeof(types)/sizeof(types[0]) ; ++t) { |
8036 | for (size_t s=0 ; s < sizeof(sort)/sizeof(sort[0]) ; ++s) { |
8037 | for (int c=0 ; c < 2 ; ++c) { |
8038 | QString testName = QLatin1String(types[t]) + QLatin1Char('_') + QLatin1String(sort[s]); |
8039 | QString fnName = QLatin1String("test_" ) + testName; |
8040 | bool useComparer = c != 0; |
8041 | testName += useComparer ? QLatin1String("[custom]" ) : QLatin1String("[default]" ); |
8042 | QTest::newRow(dataTag: testName.toLatin1().constData()) << fnName << useComparer; |
8043 | } |
8044 | } |
8045 | } |
8046 | } |
8047 | |
8048 | void tst_qqmlecmascript::sequenceSort() |
8049 | { |
8050 | QFETCH(QString, function); |
8051 | QFETCH(bool, useComparer); |
8052 | |
8053 | QQmlEngine engine; |
8054 | QQmlComponent component(&engine, testFileUrl(fileName: "sequenceSort.qml" )); |
8055 | |
8056 | QObject *object = component.create(); |
8057 | if (object == nullptr) |
8058 | qDebug() << component.errorString(); |
8059 | QVERIFY(object != nullptr); |
8060 | |
8061 | QVariant q; |
8062 | QMetaObject::invokeMethod(obj: object, member: function.toLatin1().constData(), Q_RETURN_ARG(QVariant, q), Q_ARG(QVariant, useComparer)); |
8063 | QVERIFY(q.toBool()); |
8064 | |
8065 | delete object; |
8066 | } |
8067 | |
8068 | void tst_qqmlecmascript::dateParse() |
8069 | { |
8070 | QQmlEngine engine; |
8071 | QQmlComponent component(&engine, testFileUrl(fileName: "date.qml" )); |
8072 | |
8073 | QScopedPointer<QObject> object(component.create()); |
8074 | if (object == nullptr) |
8075 | qDebug() << component.errorString(); |
8076 | QVERIFY(object != nullptr); |
8077 | |
8078 | QVariant q; |
8079 | QMetaObject::invokeMethod(obj: object.get(), member: "test_is_invalid_jsDateTime" , Q_RETURN_ARG(QVariant, q)); |
8080 | QVERIFY(q.toBool()); |
8081 | |
8082 | QMetaObject::invokeMethod(obj: object.get(), member: "test_is_invalid_qtDateTime" , Q_RETURN_ARG(QVariant, q)); |
8083 | QVERIFY(q.toBool()); |
8084 | |
8085 | QMetaObject::invokeMethod(obj: object.get(), member: "test_rfc2822_date" , Q_RETURN_ARG(QVariant, q)); |
8086 | QCOMPARE(q.toLongLong(), 1379512851000LL); |
8087 | } |
8088 | |
8089 | void tst_qqmlecmascript::utcDate() |
8090 | { |
8091 | QQmlEngine engine; |
8092 | QQmlComponent component(&engine, testFileUrl(fileName: "utcdate.qml" )); |
8093 | |
8094 | QScopedPointer<QObject> object(component.create()); |
8095 | if (object == nullptr) |
8096 | qDebug() << component.errorString(); |
8097 | QVERIFY(object != nullptr); |
8098 | |
8099 | QVariant q; |
8100 | QVariant val = QString::fromLatin1(str: "2014-07-16T23:30:31" ); |
8101 | QMetaObject::invokeMethod(obj: object.get(), member: "check_utc" , Q_RETURN_ARG(QVariant, q), Q_ARG(QVariant, val)); |
8102 | QVERIFY(q.toBool()); |
8103 | } |
8104 | |
8105 | void tst_qqmlecmascript::negativeYear() |
8106 | { |
8107 | QQmlEngine engine; |
8108 | QQmlComponent component(&engine, testFileUrl(fileName: "negativeyear.qml" )); |
8109 | |
8110 | QScopedPointer<QObject> object(component.create()); |
8111 | if (object == nullptr) |
8112 | qDebug() << component.errorString(); |
8113 | QVERIFY(object != nullptr); |
8114 | |
8115 | QVariant q; |
8116 | QMetaObject::invokeMethod(obj: object.get(), member: "check_negative_tostring" , Q_RETURN_ARG(QVariant, q)); |
8117 | |
8118 | // Only check for the year. We hope that every language writes the year in arabic numerals and |
8119 | // in relation to a specific dude's date of birth. We also hope that no language adds a "-2001" |
8120 | // junk string somewhere in the middle. |
8121 | QVERIFY(q.toString().indexOf(QStringLiteral("-2001" )) != -1); |
8122 | |
8123 | QMetaObject::invokeMethod(obj: object.get(), member: "check_negative_toisostring" , Q_RETURN_ARG(QVariant, q)); |
8124 | QCOMPARE(q.toString().left(16), QStringLiteral("result: -002000-" )); |
8125 | } |
8126 | |
8127 | void tst_qqmlecmascript::concatenatedStringPropertyAccess() |
8128 | { |
8129 | QQmlEngine engine; |
8130 | QQmlComponent component(&engine, testFileUrl(fileName: "concatenatedStringPropertyAccess.qml" )); |
8131 | QObject *object = component.create(); |
8132 | QVERIFY(object); |
8133 | QVERIFY(object->property("success" ).toBool()); |
8134 | delete object; |
8135 | } |
8136 | |
8137 | void tst_qqmlecmascript::jsOwnedObjectsDeletedOnEngineDestroy() |
8138 | { |
8139 | QQmlEngine *myEngine = new QQmlEngine; |
8140 | |
8141 | MyDeleteObject deleteObject; |
8142 | deleteObject.setObjectName("deleteObject" ); |
8143 | QObject * const object1 = new QObject; |
8144 | QObject * const object2 = new QObject; |
8145 | object1->setObjectName("object1" ); |
8146 | object2->setObjectName("object2" ); |
8147 | deleteObject.setObject1(object1); |
8148 | deleteObject.setObject2(object2); |
8149 | |
8150 | // Objects returned by function calls get marked as destructible, but objects returned by |
8151 | // property getters do not - therefore we explicitly set the object as destructible. |
8152 | QQmlEngine::setObjectOwnership(object2, QQmlEngine::JavaScriptOwnership); |
8153 | |
8154 | myEngine->rootContext()->setContextProperty("deleteObject" , &deleteObject); |
8155 | QQmlComponent component(myEngine, testFileUrl(fileName: "jsOwnedObjectsDeletedOnEngineDestroy.qml" )); |
8156 | QObject *object = component.create(); |
8157 | QVERIFY(object); |
8158 | |
8159 | // Destroying the engine should delete all JS owned QObjects |
8160 | QSignalSpy spy1(object1, SIGNAL(destroyed())); |
8161 | QSignalSpy spy2(object2, SIGNAL(destroyed())); |
8162 | delete myEngine; |
8163 | QCOMPARE(spy1.count(), 1); |
8164 | QCOMPARE(spy2.count(), 1); |
8165 | |
8166 | deleteObject.deleteNestedObject(); |
8167 | delete object; |
8168 | } |
8169 | |
8170 | void tst_qqmlecmascript::updateCall() |
8171 | { |
8172 | // update is a slot on QQuickItem. Even though it's not |
8173 | // documented it can be called from within QML. Make sure |
8174 | // we don't crash when calling it. |
8175 | QString file("updateCall.qml" ); |
8176 | QQmlEngine engine; |
8177 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
8178 | QScopedPointer<QObject> object(component.create()); |
8179 | QVERIFY(object != nullptr); |
8180 | } |
8181 | |
8182 | void tst_qqmlecmascript::numberParsing() |
8183 | { |
8184 | QQmlEngine engine; |
8185 | for (int i = 1; i < 8; ++i) { |
8186 | QString file("numberParsing.%1.qml" ); |
8187 | file = file.arg(a: i); |
8188 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
8189 | QScopedPointer<QObject> object(component.create()); |
8190 | QVERIFY(object != nullptr); |
8191 | } |
8192 | for (int i = 1; i < 3; ++i) { |
8193 | QString file("numberParsing_error.%1.qml" ); |
8194 | file = file.arg(a: i); |
8195 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
8196 | QVERIFY(!component.errors().isEmpty()); |
8197 | } |
8198 | } |
8199 | |
8200 | void tst_qqmlecmascript::stringParsing() |
8201 | { |
8202 | QQmlEngine engine; |
8203 | for (int i = 1; i < 7; ++i) { |
8204 | QString file("stringParsing_error.%1.qml" ); |
8205 | file = file.arg(a: i); |
8206 | QQmlComponent component(&engine, testFileUrl(fileName: file)); |
8207 | QObject *object = component.create(); |
8208 | QVERIFY(!object); |
8209 | } |
8210 | } |
8211 | |
8212 | void tst_qqmlecmascript::push_and_shift() |
8213 | { |
8214 | QJSEngine e; |
8215 | const QString program = |
8216 | "var array = []; " |
8217 | "for (var i = 0; i < 10000; i++) {" |
8218 | " array.push(5); array.unshift(5); array.push(5);" |
8219 | "}" |
8220 | "array.length;" ; |
8221 | QCOMPARE(e.evaluate(program).toNumber(), double(30000)); |
8222 | } |
8223 | |
8224 | void tst_qqmlecmascript::qtbug_32801() |
8225 | { |
8226 | QQmlEngine engine; |
8227 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_32801.qml" )); |
8228 | |
8229 | QScopedPointer<QObject> obj(component.create()); |
8230 | QVERIFY(obj != nullptr); |
8231 | |
8232 | // do not crash when a QML signal is connected to a non-void slot |
8233 | connect(sender: obj.data(), SIGNAL(testSignal(QString)), receiver: obj.data(), SLOT(slotWithReturnValue(QString))); |
8234 | QVERIFY(QMetaObject::invokeMethod(obj.data(), "emitTestSignal" )); |
8235 | } |
8236 | |
8237 | void tst_qqmlecmascript::thisObject() |
8238 | { |
8239 | QQmlEngine engine; |
8240 | QQmlComponent component(&engine, testFileUrl(fileName: "thisObject.qml" )); |
8241 | QObject *object = component.create(); |
8242 | QVERIFY(object); |
8243 | QCOMPARE(qvariant_cast<QObject*>(object->property("subObject" ))->property("test" ).toInt(), 2); |
8244 | delete object; |
8245 | } |
8246 | |
8247 | void tst_qqmlecmascript::qtbug_33754() |
8248 | { |
8249 | QQmlEngine engine; |
8250 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_33754.qml" )); |
8251 | |
8252 | QScopedPointer<QObject> obj(component.create()); |
8253 | QVERIFY(obj != nullptr); |
8254 | } |
8255 | |
8256 | void tst_qqmlecmascript::qtbug_34493() |
8257 | { |
8258 | QQmlEngine engine; |
8259 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_34493.qml" )); |
8260 | |
8261 | QScopedPointer<QObject> obj(component.create()); |
8262 | if (component.errors().size()) |
8263 | qDebug() << component.errors(); |
8264 | QVERIFY(component.errors().isEmpty()); |
8265 | QVERIFY(obj != nullptr); |
8266 | QVERIFY(QMetaObject::invokeMethod(obj.data(), "doIt" )); |
8267 | QTRY_VERIFY(obj->property("prop" ).toString() == QLatin1String("Hello World!" )); |
8268 | } |
8269 | |
8270 | // Check that a Singleton can be passed from QML to C++ |
8271 | // as its type*, it's parent type* and as QObject* |
8272 | void tst_qqmlecmascript::singletonFromQMLToCpp() |
8273 | { |
8274 | QQmlEngine engine; |
8275 | QQmlComponent component(&engine, testFile(fileName: "singletonTest.qml" )); |
8276 | QScopedPointer<QObject> obj(component.create()); |
8277 | if (component.errors().size()) |
8278 | qDebug() << component.errors(); |
8279 | QVERIFY(component.errors().isEmpty()); |
8280 | QVERIFY(obj != nullptr); |
8281 | |
8282 | QCOMPARE(obj->property("qobjectTest" ), QVariant(true)); |
8283 | QCOMPARE(obj->property("myQmlObjectTest" ), QVariant(true)); |
8284 | QCOMPARE(obj->property("myInheritedQmlObjectTest" ), QVariant(true)); |
8285 | } |
8286 | |
8287 | // Check that a Singleton can be passed from QML to C++ |
8288 | // as its type*, it's parent type* and as QObject* |
8289 | // and correctly compares to itself |
8290 | void tst_qqmlecmascript::singletonFromQMLAndBackAndCompare() |
8291 | { |
8292 | QQmlEngine engine; |
8293 | QQmlComponent component(&engine, testFile(fileName: "singletonTest2.qml" )); |
8294 | QScopedPointer<QObject> o(component.create()); |
8295 | if (component.errors().size()) |
8296 | qDebug() << component.errors(); |
8297 | QVERIFY(component.errors().isEmpty()); |
8298 | QVERIFY(o != nullptr); |
8299 | |
8300 | QCOMPARE(o->property("myInheritedQmlObjectTest1" ), QVariant(true)); |
8301 | QCOMPARE(o->property("myInheritedQmlObjectTest2" ), QVariant(true)); |
8302 | QCOMPARE(o->property("myInheritedQmlObjectTest3" ), QVariant(true)); |
8303 | |
8304 | QCOMPARE(o->property("myQmlObjectTest1" ), QVariant(true)); |
8305 | QCOMPARE(o->property("myQmlObjectTest2" ), QVariant(true)); |
8306 | QCOMPARE(o->property("myQmlObjectTest3" ), QVariant(true)); |
8307 | |
8308 | QCOMPARE(o->property("qobjectTest1" ), QVariant(true)); |
8309 | QCOMPARE(o->property("qobjectTest2" ), QVariant(true)); |
8310 | QCOMPARE(o->property("qobjectTest3" ), QVariant(true)); |
8311 | |
8312 | QCOMPARE(o->property("singletonEqualToItself" ), QVariant(true)); |
8313 | } |
8314 | |
8315 | void tst_qqmlecmascript::setPropertyOnInvalid() |
8316 | { |
8317 | QQmlEngine engine; |
8318 | { |
8319 | QQmlComponent component(&engine, testFileUrl(fileName: "setPropertyOnNull.qml" )); |
8320 | QString warning = component.url().toString() + ":4: TypeError: Value is null and could not be converted to an object" ; |
8321 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
8322 | QObject *object = component.create(); |
8323 | QVERIFY(object); |
8324 | delete object; |
8325 | } |
8326 | |
8327 | { |
8328 | QQmlComponent component(&engine, testFileUrl(fileName: "setPropertyOnUndefined.qml" )); |
8329 | QString warning = component.url().toString() + ":4: TypeError: Value is undefined and could not be converted to an object" ; |
8330 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning)); |
8331 | QObject *object = component.create(); |
8332 | QVERIFY(object); |
8333 | delete object; |
8334 | } |
8335 | } |
8336 | |
8337 | void tst_qqmlecmascript::miscTypeTest() |
8338 | { |
8339 | QQmlEngine engine; |
8340 | QQmlComponent component(&engine, testFileUrl(fileName: "misctypetest.qml" )); |
8341 | |
8342 | QObject *object = component.create(); |
8343 | if (object == nullptr) |
8344 | qDebug() << component.errorString(); |
8345 | QVERIFY(object != nullptr); |
8346 | |
8347 | QVariant q; |
8348 | QMetaObject::invokeMethod(obj: object, member: "test_invalid_url_equal" , Q_RETURN_ARG(QVariant, q)); |
8349 | QVERIFY(q.toBool()); |
8350 | QMetaObject::invokeMethod(obj: object, member: "test_invalid_url_strictequal" , Q_RETURN_ARG(QVariant, q)); |
8351 | QVERIFY(q.toBool()); |
8352 | QMetaObject::invokeMethod(obj: object, member: "test_valid_url_equal" , Q_RETURN_ARG(QVariant, q)); |
8353 | QVERIFY(q.toBool()); |
8354 | QMetaObject::invokeMethod(obj: object, member: "test_valid_url_strictequal" , Q_RETURN_ARG(QVariant, q)); |
8355 | QVERIFY(q.toBool()); |
8356 | |
8357 | delete object; |
8358 | } |
8359 | |
8360 | void tst_qqmlecmascript::stackLimits() |
8361 | { |
8362 | QJSEngine engine; |
8363 | engine.evaluate(QStringLiteral("function foo() {foo();} try {foo()} catch(e) { }" )); |
8364 | } |
8365 | |
8366 | void tst_qqmlecmascript::idsAsLValues() |
8367 | { |
8368 | QQmlEngine engine; |
8369 | QString err = QString(QLatin1String("%1:5: Error: left-hand side of assignment operator is not an lvalue" )).arg(a: testFileUrl(fileName: "idAsLValue.qml" ).toString()); |
8370 | QQmlComponent component(&engine, testFileUrl(fileName: "idAsLValue.qml" )); |
8371 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(err)); |
8372 | QScopedPointer<QObject> object(component.create()); |
8373 | QVERIFY(!qobject_cast<MyQmlObject*>(object.get())); |
8374 | } |
8375 | |
8376 | void tst_qqmlecmascript::qtbug_34792() |
8377 | { |
8378 | QQmlEngine engine; |
8379 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug34792.qml" )); |
8380 | |
8381 | QObject *object = component.create(); |
8382 | if (object == nullptr) |
8383 | qDebug() << component.errorString(); |
8384 | QVERIFY(object != nullptr); |
8385 | delete object; |
8386 | } |
8387 | |
8388 | void tst_qqmlecmascript::noCaptureWhenWritingProperty() |
8389 | { |
8390 | QQmlEngine engine; |
8391 | QQmlComponent component(&engine, testFileUrl(fileName: "noCaptureWhenWritingProperty.qml" )); |
8392 | QScopedPointer<QObject> obj(component.create()); |
8393 | QVERIFY(!obj.isNull()); |
8394 | QCOMPARE(obj->property("somePropertyEvaluated" ).toBool(), false); |
8395 | } |
8396 | |
8397 | void tst_qqmlecmascript::singletonWithEnum() |
8398 | { |
8399 | QQmlEngine engine; |
8400 | QQmlComponent component(&engine, testFileUrl(fileName: "singletontype/singletonWithEnum.qml" )); |
8401 | QScopedPointer<QObject> obj(component.create()); |
8402 | if (obj.isNull()) |
8403 | qDebug() << component.errors().first().toString(); |
8404 | QVERIFY(!obj.isNull()); |
8405 | QVariant prop = obj->property(name: "testValue" ); |
8406 | QCOMPARE(prop.type(), QVariant::Int); |
8407 | QCOMPARE(prop.toInt(), int(SingletonWithEnum::TestValue)); |
8408 | |
8409 | { |
8410 | QQmlExpression expr(qmlContext(obj.data()), obj.data(), "SingletonWithEnum.TestValue_MinusOne" ); |
8411 | bool valueUndefined = false; |
8412 | QVariant result = expr.evaluate(valueIsUndefined: &valueUndefined); |
8413 | QVERIFY2(!expr.hasError(), qPrintable(expr.error().toString())); |
8414 | QVERIFY(!valueUndefined); |
8415 | QCOMPARE(result.toInt(), -1); |
8416 | } |
8417 | } |
8418 | |
8419 | void tst_qqmlecmascript::lazyBindingEvaluation() |
8420 | { |
8421 | QQmlEngine engine; |
8422 | QQmlComponent component(&engine, testFileUrl(fileName: "lazyBindingEvaluation.qml" )); |
8423 | QScopedPointer<QObject> obj(component.create()); |
8424 | if (obj.isNull()) |
8425 | qDebug() << component.errors().first().toString(); |
8426 | QVERIFY(!obj.isNull()); |
8427 | QVariant prop = obj->property(name: "arrayLength" ); |
8428 | QCOMPARE(prop.type(), QVariant::Int); |
8429 | QCOMPARE(prop.toInt(), 2); |
8430 | } |
8431 | |
8432 | void tst_qqmlecmascript::varPropertyAccessOnObjectWithInvalidContext() |
8433 | { |
8434 | QQmlEngine engine; |
8435 | QQmlComponent component(&engine, testFileUrl(fileName: "varPropertyAccessOnObjectWithInvalidContext.qml" )); |
8436 | QScopedPointer<QObject> obj(component.create()); |
8437 | if (obj.isNull()) |
8438 | qDebug() << component.errors().first().toString(); |
8439 | QVERIFY(!obj.isNull()); |
8440 | QVERIFY(obj->property("success" ).toBool()); |
8441 | } |
8442 | |
8443 | void tst_qqmlecmascript::importedScriptsAccessOnObjectWithInvalidContext() |
8444 | { |
8445 | QQmlEngine engine; |
8446 | const QUrl url = testFileUrl(fileName: "importedScriptsAccessOnObjectWithInvalidContext.qml" ); |
8447 | QTest::ignoreMessage(type: QtWarningMsg, qPrintable(url.toString() + ":29: TypeError: Cannot read property 'Foo' of null" )); |
8448 | QQmlComponent component(&engine, url); |
8449 | QScopedPointer<QObject> obj(component.create()); |
8450 | if (obj.isNull()) |
8451 | qDebug() << component.errors().first().toString(); |
8452 | QVERIFY(!obj.isNull()); |
8453 | QTRY_VERIFY(obj->property("success" ).toBool()); |
8454 | } |
8455 | |
8456 | void tst_qqmlecmascript::importedScriptsWithoutQmlMode() |
8457 | { |
8458 | QQmlEngine engine; |
8459 | QQmlComponent component(&engine, testFileUrl(fileName: "importScriptsWithoutQmlMode.qml" )); |
8460 | QScopedPointer<QObject> obj(component.create()); |
8461 | if (obj.isNull()) |
8462 | qDebug() << component.errors().first().toString(); |
8463 | QVERIFY(!obj.isNull()); |
8464 | QTRY_VERIFY(obj->property("success" ).toBool()); |
8465 | } |
8466 | |
8467 | void tst_qqmlecmascript::contextObjectOnLazyBindings() |
8468 | { |
8469 | QQmlEngine engine; |
8470 | QQmlComponent component(&engine, testFileUrl(fileName: "contextObjectOnLazyBindings.qml" )); |
8471 | QScopedPointer<QObject> obj(component.create()); |
8472 | if (obj.isNull()) |
8473 | qDebug() << component.errors().first().toString(); |
8474 | QVERIFY(!obj.isNull()); |
8475 | QObject *subObject = qvariant_cast<QObject*>(v: obj->property(name: "subObject" )); |
8476 | QVERIFY(subObject); |
8477 | QCOMPARE(subObject->property("testValue" ).toInt(), int(42)); |
8478 | } |
8479 | |
8480 | void tst_qqmlecmascript::garbageCollectionDuringCreation() |
8481 | { |
8482 | QQmlEngine engine; |
8483 | QQmlComponent component(&engine); |
8484 | component.setData("import Qt.test 1.0\n" |
8485 | "QObjectContainerWithGCOnAppend {\n" |
8486 | " objectName: \"root\"\n" |
8487 | " FloatingQObject {\n" |
8488 | " objectName: \"parentLessChild\"\n" |
8489 | " property var blah;\n" // Ensure we have JS wrapper |
8490 | " }\n" |
8491 | "}\n" , |
8492 | baseUrl: QUrl()); |
8493 | |
8494 | QScopedPointer<QObject> object(component.create()); |
8495 | QVERIFY(!object.isNull()); |
8496 | |
8497 | QObjectContainer *container = qobject_cast<QObjectContainer*>(object: object.data()); |
8498 | QCOMPARE(container->dataChildren.count(), 1); |
8499 | |
8500 | QObject *child = container->dataChildren.first(); |
8501 | QQmlData *ddata = QQmlData::get(object: child); |
8502 | QVERIFY(!ddata->jsWrapper.isNullOrUndefined()); |
8503 | |
8504 | gc(engine); |
8505 | QCOMPARE(container->dataChildren.count(), 0); |
8506 | } |
8507 | |
8508 | void tst_qqmlecmascript::qtbug_39520() |
8509 | { |
8510 | QQmlEngine engine; |
8511 | QQmlComponent component(&engine); |
8512 | component.setData("import QtQuick 2.0\n" |
8513 | "Item {\n" |
8514 | " property string s\n" |
8515 | " Component.onCompleted: test()\n" |
8516 | " function test() {\n" |
8517 | " var count = 1 * 1000 * 1000\n" |
8518 | " var t = ''\n" |
8519 | " for (var i = 0; i < count; ++i)\n" |
8520 | " t += 'testtest ' + i + '\n'\n" |
8521 | " s = t\n" |
8522 | " }\n" |
8523 | "}\n" , |
8524 | baseUrl: QUrl()); |
8525 | |
8526 | QScopedPointer<QObject> object(component.create()); |
8527 | QVERIFY(!object.isNull()); |
8528 | |
8529 | QString s = object->property(name: "s" ).toString(); |
8530 | QCOMPARE(s.count('\n'), 1 * 1000 * 1000); |
8531 | } |
8532 | |
8533 | class ContainedObject1 : public QObject |
8534 | { |
8535 | Q_OBJECT |
8536 | }; |
8537 | |
8538 | class ContainedObject2 : public QObject |
8539 | { |
8540 | Q_OBJECT |
8541 | }; |
8542 | |
8543 | class ObjectContainer : public QObject |
8544 | { |
8545 | Q_OBJECT |
8546 | Q_PROPERTY(ContainedObject1 *object1 READ object1 WRITE setObject1) |
8547 | Q_PROPERTY(ContainedObject2 *object2 READ object2 WRITE setObject2) |
8548 | public: |
8549 | explicit ObjectContainer(QObject *parent = nullptr) : |
8550 | QObject(parent), |
8551 | mGetterCalled(false), |
8552 | mSetterCalled(false) |
8553 | { |
8554 | } |
8555 | |
8556 | ContainedObject1 *object1() |
8557 | { |
8558 | mGetterCalled = true; |
8559 | return nullptr; |
8560 | } |
8561 | |
8562 | void setObject1(ContainedObject1 *) |
8563 | { |
8564 | mSetterCalled = true; |
8565 | } |
8566 | |
8567 | ContainedObject2 *object2() |
8568 | { |
8569 | mGetterCalled = true; |
8570 | return nullptr; |
8571 | } |
8572 | |
8573 | void setObject2(ContainedObject2 *) |
8574 | { |
8575 | mSetterCalled = true; |
8576 | } |
8577 | |
8578 | public: |
8579 | bool mGetterCalled; |
8580 | bool mSetterCalled; |
8581 | }; |
8582 | |
8583 | void tst_qqmlecmascript::readUnregisteredQObjectProperty() |
8584 | { |
8585 | qmlRegisterType<ObjectContainer>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ObjectContainer" ); |
8586 | QQmlEngine engine; |
8587 | QQmlComponent component(&engine, testFileUrl(fileName: "accessUnregisteredQObjectProperty.qml" )); |
8588 | QScopedPointer<QObject> root(component.create()); |
8589 | QVERIFY(root); |
8590 | |
8591 | QMetaObject::invokeMethod(obj: root.get(), member: "readProperty" ); |
8592 | QCOMPARE(root->property("container" ).value<ObjectContainer*>()->mGetterCalled, true); |
8593 | } |
8594 | |
8595 | void tst_qqmlecmascript::writeUnregisteredQObjectProperty() |
8596 | { |
8597 | qmlRegisterType<ObjectContainer>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ObjectContainer" ); |
8598 | QQmlEngine engine; |
8599 | QQmlComponent component(&engine, testFileUrl(fileName: "accessUnregisteredQObjectProperty.qml" )); |
8600 | QScopedPointer<QObject> root(component.create()); |
8601 | QVERIFY(root); |
8602 | |
8603 | QMetaObject::invokeMethod(obj: root.get(), member: "writeProperty" ); |
8604 | QCOMPARE(root->property("container" ).value<ObjectContainer*>()->mSetterCalled, true); |
8605 | } |
8606 | |
8607 | void tst_qqmlecmascript::switchExpression() |
8608 | { |
8609 | // verify that we evaluate the expression inside switch() exactly once |
8610 | QJSEngine engine; |
8611 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
8612 | str: "var num = 0\n" |
8613 | "var x = 0\n" |
8614 | "function f() { ++num; return (Math.random() > 0.5) ? 0 : 1; }\n" |
8615 | "for (var i = 0; i < 1000; ++i) {\n" |
8616 | " switch (f()) {\n" |
8617 | " case 0:\n" |
8618 | " case 1:\n" |
8619 | " break;\n" |
8620 | " default:\n" |
8621 | " ++x;\n" |
8622 | " }\n" |
8623 | "}\n" |
8624 | "(x == 0 && num == 1000) ? true : false\n" |
8625 | )); |
8626 | QVERIFY(!v.isError()); |
8627 | QCOMPARE(v.toBool(), true); |
8628 | } |
8629 | |
8630 | void tst_qqmlecmascript::qtbug_46022() |
8631 | { |
8632 | QQmlEngine engine; |
8633 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_46022.qml" )); |
8634 | |
8635 | QScopedPointer<QObject> obj(component.create()); |
8636 | QVERIFY(obj != nullptr); |
8637 | QCOMPARE(obj->property("test1" ).toBool(), true); |
8638 | QCOMPARE(obj->property("test2" ).toBool(), true); |
8639 | } |
8640 | |
8641 | void tst_qqmlecmascript::qtbug_52340() |
8642 | { |
8643 | QQmlEngine engine; |
8644 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_52340.qml" )); |
8645 | QScopedPointer<QObject> object(component.create()); |
8646 | QVERIFY(!object.isNull()); |
8647 | QVariant returnValue; |
8648 | QVERIFY(QMetaObject::invokeMethod(object.data(), "testCall" , Q_RETURN_ARG(QVariant, returnValue))); |
8649 | QVERIFY(returnValue.isValid()); |
8650 | QVERIFY(returnValue.toBool()); |
8651 | } |
8652 | |
8653 | void tst_qqmlecmascript::qtbug_54589() |
8654 | { |
8655 | QQmlEngine engine; |
8656 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_54589.qml" )); |
8657 | |
8658 | QScopedPointer<QObject> obj(component.create()); |
8659 | QVERIFY(obj != nullptr); |
8660 | QCOMPARE(obj->property("result" ).toBool(), true); |
8661 | } |
8662 | |
8663 | void tst_qqmlecmascript::qtbug_54687() |
8664 | { |
8665 | QJSEngine e; |
8666 | // it's simple: this shouldn't crash. |
8667 | e.evaluate(program: "12\n----12" ); |
8668 | } |
8669 | |
8670 | void tst_qqmlecmascript::stringify_qtbug_50592() |
8671 | { |
8672 | QQmlEngine engine; |
8673 | QQmlComponent component(&engine, testFileUrl(fileName: "stringify_qtbug_50592.qml" )); |
8674 | |
8675 | QScopedPointer<QObject> obj(component.create()); |
8676 | QVERIFY(obj != nullptr); |
8677 | QCOMPARE(obj->property("source" ).toString(), |
8678 | QString::fromLatin1("\"http://example.org/some_nonexistant_image.png\"" )); |
8679 | } |
8680 | |
8681 | // Tests for the JS-only instanceof. Tests for the QML extensions for |
8682 | // instanceof belong in tst_qqmllanguage! |
8683 | void tst_qqmlecmascript::instanceof_data() |
8684 | { |
8685 | QTest::addColumn<QString>(name: "setupCode" ); |
8686 | QTest::addColumn<QVariant>(name: "expectedValue" ); |
8687 | |
8688 | // so the way this works is that the name of the test tag defines the test |
8689 | // to run. the code in setupCode defines code run before the actual test |
8690 | // (e.g. to create vars). |
8691 | // |
8692 | // the expectedValue is either a boolean true or false for whether the two |
8693 | // operands are indeed an instanceof each other, or a string for the |
8694 | // expected error message. |
8695 | QTest::newRow(dataTag: "String instanceof String" ) |
8696 | << "" |
8697 | << QVariant(false); |
8698 | QTest::newRow(dataTag: "s instanceof String" ) |
8699 | << "var s = \"hello\"" |
8700 | << QVariant(false); |
8701 | QTest::newRow(dataTag: "objectString instanceof String" ) |
8702 | << "var objectString = new String(\"hello\")" |
8703 | << QVariant(true); |
8704 | QTest::newRow(dataTag: "o instanceof Object" ) |
8705 | << "var o = new Object()" |
8706 | << QVariant(true); |
8707 | QTest::newRow(dataTag: "o instanceof String" ) |
8708 | << "var o = new Object()" |
8709 | << QVariant(false); |
8710 | QTest::newRow(dataTag: "true instanceof true" ) |
8711 | << "" |
8712 | << QVariant("TypeError: Type error" ); |
8713 | QTest::newRow(dataTag: "1 instanceof Math" ) |
8714 | << "" |
8715 | << QVariant("TypeError: Type error" ); |
8716 | QTest::newRow(dataTag: "date instanceof Date" ) |
8717 | << "var date = new Date" |
8718 | << QVariant(true); |
8719 | QTest::newRow(dataTag: "date instanceof Object" ) |
8720 | << "var date = new Date" |
8721 | << QVariant(true); |
8722 | QTest::newRow(dataTag: "date instanceof String" ) |
8723 | << "var date = new Date" |
8724 | << QVariant(false); |
8725 | } |
8726 | |
8727 | void tst_qqmlecmascript::instanceof() |
8728 | { |
8729 | QFETCH(QString, setupCode); |
8730 | QFETCH(QVariant, expectedValue); |
8731 | |
8732 | QJSEngine engine; |
8733 | QJSValue ret = engine.evaluate(program: setupCode + ";\n" + QTest::currentDataTag()); |
8734 | |
8735 | if (expectedValue.type() == QVariant::Bool) { |
8736 | bool returnValue = ret.toBool(); |
8737 | QVERIFY2(!ret.isError(), qPrintable(ret.toString())); |
8738 | QCOMPARE(returnValue, expectedValue.toBool()); |
8739 | } else { |
8740 | QVERIFY2(ret.isError(), qPrintable(ret.toString())); |
8741 | QCOMPARE(ret.toString(), expectedValue.toString()); |
8742 | } |
8743 | } |
8744 | |
8745 | void tst_qqmlecmascript::constkw_data() |
8746 | { |
8747 | QTest::addColumn<QString>(name: "sourceCode" ); |
8748 | QTest::addColumn<bool>(name: "exceptionExpected" ); |
8749 | QTest::addColumn<QVariant>(name: "expectedValue" ); |
8750 | |
8751 | QTest::newRow(dataTag: "simpleconst" ) |
8752 | << "const v = 5\n" |
8753 | "v\n" |
8754 | << false |
8755 | << QVariant(5); |
8756 | QTest::newRow(dataTag: "twoconst" ) |
8757 | << "const v = 5, i = 10\n" |
8758 | "v + i\n" |
8759 | << false |
8760 | << QVariant(15); |
8761 | QTest::newRow(dataTag: "constandvar" ) |
8762 | << "const v = 5\n" |
8763 | "var i = 20\n" |
8764 | "v + i\n" |
8765 | << false |
8766 | << QVariant(25); |
8767 | QTest::newRow(dataTag: "const-multiple-scopes-same-var" ) |
8768 | << "const v = 3\n" |
8769 | "function f() { const v = 1; return v; }\n" |
8770 | "v + f()\n" |
8771 | << false |
8772 | << QVariant(4); |
8773 | |
8774 | // error cases |
8775 | QTest::newRow(dataTag: "const-no-initializer" ) |
8776 | << "const v\n" |
8777 | << true |
8778 | << QVariant("SyntaxError: Missing initializer in const declaration" ); |
8779 | QTest::newRow(dataTag: "const-no-initializer-comma" ) |
8780 | << "const v = 1, i\n" |
8781 | << true |
8782 | << QVariant("SyntaxError: Missing initializer in const declaration" ); |
8783 | QTest::newRow(dataTag: "const-no-duplicate" ) |
8784 | << "const v = 1, v = 2\n" |
8785 | << true |
8786 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8787 | QTest::newRow(dataTag: "const-no-duplicate-2" ) |
8788 | << "const v = 1\n" |
8789 | "const v = 2\n" |
8790 | << true |
8791 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8792 | QTest::newRow(dataTag: "const-no-duplicate-var" ) |
8793 | << "const v = 1\n" |
8794 | "var v = 1\n" |
8795 | << true |
8796 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8797 | QTest::newRow(dataTag: "var-no-duplicate-const" ) |
8798 | << "var v = 1\n" |
8799 | "const v = 1\n" |
8800 | << true |
8801 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8802 | QTest::newRow(dataTag: "const-no-duplicate-let" ) |
8803 | << "const v = 1\n" |
8804 | "let v = 1\n" |
8805 | << true |
8806 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8807 | QTest::newRow(dataTag: "let-no-duplicate-const" ) |
8808 | << "let v = 1\n" |
8809 | "const v = 1\n" |
8810 | << true |
8811 | << QVariant("SyntaxError: Identifier v has already been declared" ); |
8812 | } |
8813 | |
8814 | void tst_qqmlecmascript::constkw() |
8815 | { |
8816 | QFETCH(QString, sourceCode); |
8817 | QFETCH(bool, exceptionExpected); |
8818 | QFETCH(QVariant, expectedValue); |
8819 | |
8820 | QJSEngine engine; |
8821 | QJSValue ret = engine.evaluate(program: sourceCode); |
8822 | |
8823 | if (!exceptionExpected) { |
8824 | QVERIFY2(!ret.isError(), qPrintable(ret.toString())); |
8825 | QCOMPARE(ret.toVariant(), expectedValue); |
8826 | } else { |
8827 | QVERIFY2(ret.isError(), qPrintable(ret.toString())); |
8828 | QCOMPARE(ret.toString(), expectedValue.toString()); |
8829 | } |
8830 | } |
8831 | |
8832 | // Redefine a property found on the global object. It shouldn't throw. |
8833 | void tst_qqmlecmascript::redefineGlobalProp() |
8834 | { |
8835 | { |
8836 | QJSEngine engine; |
8837 | QJSValue ret = engine.evaluate(program: "\"use strict\"\n var toString = 1;" ); |
8838 | QVERIFY2(!ret.isError(), qPrintable(ret.toString())); |
8839 | } |
8840 | { |
8841 | QJSEngine engine; |
8842 | QJSValue ret = engine.evaluate(program: "var toString = 1;" ); |
8843 | QVERIFY2(!ret.isError(), qPrintable(ret.toString())); |
8844 | } |
8845 | } |
8846 | |
8847 | void tst_qqmlecmascript::freeze_empty_object() |
8848 | { |
8849 | // this shouldn't crash |
8850 | QJSEngine engine; |
8851 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
8852 | str: "var obj = {};\n" |
8853 | "Object.freeze(obj);\n" |
8854 | )); |
8855 | QVERIFY(!v.isError()); |
8856 | QCOMPARE(v.toBool(), true); |
8857 | } |
8858 | |
8859 | void tst_qqmlecmascript::singleBlockLoops() |
8860 | { |
8861 | QQmlEngine engine; |
8862 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug_59012.qml" )); |
8863 | |
8864 | QScopedPointer<QObject> obj(component.create()); |
8865 | QVERIFY(obj != nullptr); |
8866 | QVERIFY(!component.isError()); |
8867 | } |
8868 | |
8869 | // 'counter' was incorrectly resolved as a type rather than a variable. |
8870 | // This fix ensures it looks up the right thing. |
8871 | void tst_qqmlecmascript::qtbug_60547() |
8872 | { |
8873 | QQmlEngine engine; |
8874 | QQmlComponent component(&engine, testFileUrl(fileName: "qtbug60547/main.qml" )); |
8875 | QScopedPointer<QObject> object(component.create()); |
8876 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); |
8877 | QCOMPARE(object->property("counter" ), QVariant(int(1))); |
8878 | } |
8879 | |
8880 | void tst_qqmlecmascript::anotherNaN() |
8881 | { |
8882 | QQmlEngine engine; |
8883 | QQmlComponent component(&engine, testFileUrl(fileName: "nans.qml" )); |
8884 | QScopedPointer<QObject> object(component.create()); |
8885 | QVERIFY2(!object.isNull(), qPrintable(component.errorString())); |
8886 | object->setProperty(name: "prop" , value: std::numeric_limits<double>::quiet_NaN()); // don't crash |
8887 | |
8888 | std::uint64_t anotherNaN = 0xFFFFFF01000000F7ul; |
8889 | double d; |
8890 | std::memcpy(dest: &d, src: &anotherNaN, n: sizeof(d)); |
8891 | QVERIFY(std::isnan(d)); |
8892 | object->setProperty(name: "prop" , value: d); // don't crash |
8893 | } |
8894 | |
8895 | void tst_qqmlecmascript::delayLoadingArgs() |
8896 | { |
8897 | QJSEngine engine; |
8898 | QJSValue ret = engine.evaluate(program: "(function(x){return x + (x+=2)})(20)" ); |
8899 | QCOMPARE(ret.toInt(), 42); // esp. not 44. |
8900 | } |
8901 | |
8902 | void tst_qqmlecmascript::manyArguments() |
8903 | { |
8904 | const char *testCase = |
8905 | "function x() { var sum; for (var i = 0; i < arguments.length; ++i) sum += arguments[i][0]; }" |
8906 | "x([0],[1],[2],[3],[4],[5],[6],[7],[8],[9], [0],[1],[2],[3],[4],[5],[6],[7],[8],[9], [0],[1],[2],[3],[4],[5],[6],[7],[8],[9])" ; |
8907 | |
8908 | QJSEngine engine; |
8909 | engine.evaluate(program: testCase); |
8910 | } |
8911 | |
8912 | void tst_qqmlecmascript::forInIterator() |
8913 | { |
8914 | auto testCase = |
8915 | "(function(){\n" |
8916 | "var x = 'yoyo'\n" |
8917 | "var i\n" |
8918 | "for (i in x) {\n" |
8919 | "}\n" |
8920 | "return i\n" |
8921 | "})()" ; |
8922 | QJSEngine engine; |
8923 | QJSValue ret = engine.evaluate(program: testCase); |
8924 | QVERIFY(ret.isString()); |
8925 | QCOMPARE(ret.toString(), QStringLiteral("3" )); |
8926 | } |
8927 | |
8928 | void tst_qqmlecmascript::localForInIterator() |
8929 | { |
8930 | auto testCase = |
8931 | "(function(){\n" |
8932 | "var x = 'yoyo'\n" |
8933 | "for (var i in x) {\n" |
8934 | "}\n" |
8935 | "return i\n" |
8936 | "})()" ; |
8937 | QJSEngine engine; |
8938 | QJSValue ret = engine.evaluate(program: testCase); |
8939 | QVERIFY(ret.isString()); |
8940 | QCOMPARE(ret.toString(), QStringLiteral("3" )); |
8941 | } |
8942 | |
8943 | void tst_qqmlecmascript::shadowedFunctionName() |
8944 | { |
8945 | // verify that arguments shadow the function name |
8946 | QJSEngine engine; |
8947 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
8948 | str: "function f(f) { return f; }\n" |
8949 | "f(true)\n" |
8950 | )); |
8951 | QVERIFY(!v.isError()); |
8952 | QVERIFY(v.isBool()); |
8953 | QCOMPARE(v.toBool(), true); |
8954 | } |
8955 | |
8956 | void tst_qqmlecmascript::callPropertyOnUndefined() |
8957 | { |
8958 | QJSEngine engine; |
8959 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
8960 | str: "function f() {\n" |
8961 | " var base;\n" |
8962 | " base.push(1);" |
8963 | "}\n" |
8964 | )); |
8965 | QVERIFY(!v.isError()); // well, more importantly: this shouldn't fail on an assert. |
8966 | } |
8967 | |
8968 | void tst_qqmlecmascript::jumpStrictNotEqualUndefined() |
8969 | { |
8970 | QJSEngine engine; |
8971 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
8972 | str: "var ok = 0\n" |
8973 | "var foo = 0\n" |
8974 | "if (foo !== void 1)\n" |
8975 | " ++ok;\n" |
8976 | "else\n" |
8977 | " --ok;\n" |
8978 | "if (foo === void 1)\n" |
8979 | " --ok;\n" |
8980 | "else\n" |
8981 | " ++ok;\n" |
8982 | "ok\n" |
8983 | )); |
8984 | QVERIFY(!v.isError()); |
8985 | QCOMPARE(v.toInt(), 2); |
8986 | } |
8987 | |
8988 | void tst_qqmlecmascript::removeBindingsWithNoDependencies() |
8989 | { |
8990 | QQmlEngine engine; |
8991 | QQmlComponent component(&engine, testFileUrl(fileName: "removeBindingsWithNoDependencies.qml" )); |
8992 | QScopedPointer<QObject> object(component.create()); |
8993 | QVERIFY(!object.isNull()); |
8994 | QVariant rect = object->property(name: "placement" ); |
8995 | QCOMPARE(rect.toRectF(), QRectF(0, 0, 100, 100)); |
8996 | const QMetaObject *metaObject = object->metaObject(); |
8997 | |
8998 | { |
8999 | const QMetaProperty prop = metaObject->property(index: metaObject->indexOfProperty(name: "placement" )); |
9000 | QVERIFY(prop.isValid()); |
9001 | QVERIFY(!QQmlPropertyPrivate::binding(object.data(), QQmlPropertyIndex(prop.propertyIndex()))); |
9002 | } |
9003 | |
9004 | { |
9005 | const QMetaProperty prop = metaObject->property(index: metaObject->indexOfProperty(name: "partialPlacement" )); |
9006 | QVERIFY(prop.isValid()); |
9007 | QQmlAbstractBinding *vtProxyBinding = QQmlPropertyPrivate::binding(object.data(), index: QQmlPropertyIndex(prop.propertyIndex())); |
9008 | QVERIFY(vtProxyBinding); |
9009 | QVERIFY(vtProxyBinding->isValueTypeProxy()); |
9010 | |
9011 | QQmlValueTypeProxyBinding *proxy = static_cast<QQmlValueTypeProxyBinding*>(vtProxyBinding); |
9012 | QVERIFY(!proxy->subBindings()); |
9013 | } |
9014 | |
9015 | } |
9016 | |
9017 | void tst_qqmlecmascript::preserveBindingWithUnresolvedNames() |
9018 | { |
9019 | QQmlEngine engine; |
9020 | QQmlComponent component(&engine, testFileUrl(fileName: "preserveBindingWithUnresolvedNames.qml" )); |
9021 | QScopedPointer<QObject> object(component.create()); |
9022 | QVERIFY(!object.isNull()); |
9023 | QCOMPARE(object->property("testTypeOf" ).toString(), QString("undefined" )); |
9024 | QObject obj; |
9025 | engine.rootContext()->setContextProperty("contextProp" , &obj); |
9026 | QCOMPARE(object->property("testTypeOf" ).toString(), QString("object" )); |
9027 | } |
9028 | |
9029 | void tst_qqmlecmascript::temporaryDeadZone() |
9030 | { |
9031 | QJSEngine engine; |
9032 | QJSValue v = engine.evaluate(program: QString::fromLatin1(str: "a; let a;" )); |
9033 | QVERIFY(v.isError()); |
9034 | v = engine.evaluate(program: QString::fromLatin1(str: "a.name; let a;" )); |
9035 | QVERIFY(v.isError()); |
9036 | v = engine.evaluate(program: QString::fromLatin1(str: "var a = {}; a[b]; let b;" )); |
9037 | QVERIFY(v.isError()); |
9038 | v = engine.evaluate(program: QString::fromLatin1(str: "class C { constructor() { super[x]; let x; } }; new C()" )); |
9039 | QVERIFY(v.isError()); |
9040 | } |
9041 | |
9042 | void tst_qqmlecmascript::importLexicalVariables_data() |
9043 | { |
9044 | QTest::addColumn<QUrl>(name: "testFile" ); |
9045 | QTest::addColumn<QString>(name: "expected" ); |
9046 | |
9047 | QTest::newRow(dataTag: "script" ) |
9048 | << testFileUrl(fileName: "importLexicalVariables_script.qml" ) |
9049 | << QStringLiteral("000 100 210" ); |
9050 | QTest::newRow(dataTag: "pragmaLibrary" ) |
9051 | << testFileUrl(fileName: "importLexicalVariables_pragmaLibrary.qml" ) |
9052 | << QStringLiteral("000 100 210" ); |
9053 | QTest::newRow(dataTag: "module" ) |
9054 | << testFileUrl(fileName: "importLexicalVariables_module.qml" ) |
9055 | << QStringLiteral("000 000 110" ); |
9056 | } |
9057 | |
9058 | void tst_qqmlecmascript::importLexicalVariables() |
9059 | { |
9060 | QFETCH(QUrl, testFile); |
9061 | QFETCH(QString, expected); |
9062 | |
9063 | QQmlEngine engine; |
9064 | QQmlComponent component(&engine, testFile); |
9065 | QScopedPointer<QObject> object(component.create()); |
9066 | QVERIFY(object != nullptr); |
9067 | QVERIFY(!component.isError()); |
9068 | |
9069 | QVariant result; |
9070 | QMetaObject::invokeMethod(obj: object.data(), member: "runTest" , Qt::DirectConnection, Q_RETURN_ARG(QVariant, result)); |
9071 | QCOMPARE(result, QVariant(expected)); |
9072 | } |
9073 | |
9074 | void tst_qqmlecmascript::hugeObject() |
9075 | { |
9076 | // mainly check that this doesn't crash |
9077 | QJSEngine engine; |
9078 | QJSValue v = engine.evaluate(program: QString::fromLatin1( |
9079 | str: "var known = {}, prefix = 'x'\n" |
9080 | "for (var i = 0; i < 150000; i++) known[prefix + i] = true;" |
9081 | )); |
9082 | QVERIFY(!v.isError()); |
9083 | } |
9084 | |
9085 | void tst_qqmlecmascript::templateStringTerminator() |
9086 | { |
9087 | QJSEngine engine; |
9088 | const QJSValue value = engine.evaluate(program: "let a = 123; let b = `x${a}\ny^`; b;" ); |
9089 | QVERIFY(!value.isError()); |
9090 | QCOMPARE(value.toString(), QLatin1String("x123\ny^" )); |
9091 | } |
9092 | |
9093 | void tst_qqmlecmascript::arrayAndException() |
9094 | { |
9095 | QJSEngine engine; |
9096 | const QJSValue value = engine.evaluate(program: "[...[],[,,$]]" ); |
9097 | // Should not crash |
9098 | QVERIFY(value.isError()); |
9099 | } |
9100 | |
9101 | void tst_qqmlecmascript::numberToStringWithRadix() |
9102 | { |
9103 | QJSEngine engine; |
9104 | { |
9105 | const QJSValue value = engine.evaluate(program: ".5.toString(5)" ); |
9106 | QVERIFY(!value.isError()); |
9107 | QVERIFY(value.toString().startsWith("0.2222222222" )); |
9108 | } |
9109 | { |
9110 | const QJSValue value = engine.evaluate(program: ".05.toString(5)" ); |
9111 | QVERIFY(!value.isError()); |
9112 | QVERIFY(value.toString().startsWith("0.01111111111" )); |
9113 | } |
9114 | } |
9115 | |
9116 | void tst_qqmlecmascript::tailCallWithArguments() |
9117 | { |
9118 | QJSEngine engine; |
9119 | const QJSValue value = engine.evaluate( |
9120 | program: "'use strict';\n" |
9121 | "[[1, 2]].map(function (a) {\n" |
9122 | " return (function() { return Math.min.apply(this, arguments); })(a[0], a[1]);\n" |
9123 | "})[0];" ); |
9124 | QVERIFY(!value.isError()); |
9125 | QCOMPARE(value.toInt(), 1); |
9126 | } |
9127 | |
9128 | void tst_qqmlecmascript::deleteSparseInIteration() |
9129 | { |
9130 | QJSEngine engine; |
9131 | const QJSValue value = engine.evaluate( |
9132 | program: "(function() {\n" |
9133 | " var obj = { 1: null, 2: null, 4096: null };\n" |
9134 | " var iterated = [];\n" |
9135 | " for (var t in obj) {\n" |
9136 | " if (t == 2)\n" |
9137 | " delete obj[t];\n" |
9138 | " iterated.push(t);\n" |
9139 | " }\n" |
9140 | " return iterated;" |
9141 | "})()" ); |
9142 | QVERIFY(value.isArray()); |
9143 | QCOMPARE(value.property("length" ).toInt(), 3); |
9144 | QCOMPARE(value.property("0" ).toInt(), 1); |
9145 | QCOMPARE(value.property("1" ).toInt(), 2); |
9146 | QCOMPARE(value.property("2" ).toInt(), 4096); |
9147 | } |
9148 | |
9149 | void tst_qqmlecmascript::saveAccumulatorBeforeToInt32() |
9150 | { |
9151 | QJSEngine engine; |
9152 | |
9153 | // Infinite recursion produces a range error, but should not crash. |
9154 | // Also, any GC runs in between should not trash the temporary results of "a+a". |
9155 | const QJSValue value = engine.evaluate(program: "function a(){a(a&a+a)}a()" ); |
9156 | QVERIFY(value.isError()); |
9157 | QCOMPARE(value.toString(), QLatin1String("RangeError: Maximum call stack size exceeded." )); |
9158 | } |
9159 | |
9160 | void tst_qqmlecmascript::intMinDividedByMinusOne() |
9161 | { |
9162 | QQmlEngine engine; |
9163 | QQmlComponent component(&engine); |
9164 | component.setData(QByteArray("import QtQml 2.2\n" |
9165 | "QtObject {\n" |
9166 | " property int intMin: -2147483648\n" |
9167 | " property int minusOne: -1\n" |
9168 | " property double doesNotFitInInt: intMin / minusOne\n" |
9169 | "}" ), baseUrl: QUrl()); |
9170 | QVERIFY(component.isReady()); |
9171 | QScopedPointer<QObject> object(component.create()); |
9172 | QVERIFY(!object.isNull()); |
9173 | QCOMPARE(object->property("doesNotFitInInt" ).toUInt(), 2147483648u); |
9174 | } |
9175 | |
9176 | void tst_qqmlecmascript::undefinedPropertiesInObjectWrapper() |
9177 | { |
9178 | QQmlEngine engine; |
9179 | QQmlComponent component(&engine, testFile(fileName: "undefinedPropertiesInObjectWrapper.qml" )); |
9180 | QVERIFY(component.isReady()); |
9181 | QScopedPointer<QObject> object(component.create()); |
9182 | QVERIFY(!object.isNull()); |
9183 | } |
9184 | |
9185 | void tst_qqmlecmascript::hugeRegexpQuantifiers() |
9186 | { |
9187 | QJSEngine engine; |
9188 | QJSValue value = engine.evaluate(program: "/({3072140529})?{3072140529}/" ); |
9189 | |
9190 | // It's a regular expression, but it won't match anything. |
9191 | // The RegExp compiler also shouldn't crash. |
9192 | QVERIFY(value.isRegExp()); |
9193 | } |
9194 | |
9195 | struct CppSingleton1 : public QObject |
9196 | { |
9197 | Q_OBJECT |
9198 | Q_PROPERTY(int testVar MEMBER testVar CONSTANT) |
9199 | public: |
9200 | const int testVar = 0; |
9201 | }; |
9202 | |
9203 | struct CppSingleton2 : public QObject |
9204 | { |
9205 | Q_OBJECT |
9206 | Q_PROPERTY(int testVar MEMBER testVar CONSTANT) |
9207 | public: |
9208 | const int testVar = 1; |
9209 | }; |
9210 | |
9211 | void tst_qqmlecmascript::singletonTypeWrapperLookup() |
9212 | { |
9213 | QQmlEngine engine; |
9214 | |
9215 | auto singletonTypeId1 = qmlRegisterSingletonType<CppSingleton1>(uri: "Test.Singletons" , versionMajor: 1, versionMinor: 0, typeName: "CppSingleton1" , |
9216 | callback: [](QQmlEngine *, QJSEngine *) -> QObject * { |
9217 | return new CppSingleton1; |
9218 | }); |
9219 | |
9220 | auto singletonTypeId2 = qmlRegisterSingletonType<CppSingleton2>(uri: "Test.Singletons" , versionMajor: 1, versionMinor: 0, typeName: "CppSingleton2" , |
9221 | callback: [](QQmlEngine *, QJSEngine *) -> QObject * { |
9222 | return new CppSingleton2; |
9223 | }); |
9224 | |
9225 | auto cleanup = qScopeGuard(f: [&]() { |
9226 | QQmlMetaType::unregisterType(type: singletonTypeId1); |
9227 | QQmlMetaType::unregisterType(type: singletonTypeId2); |
9228 | }); |
9229 | |
9230 | QQmlComponent component(&engine, testFileUrl(fileName: "SingletonLookupTest.qml" )); |
9231 | QScopedPointer<QObject> test(component.create()); |
9232 | QVERIFY2(!test.isNull(), qPrintable(component.errorString())); |
9233 | |
9234 | auto singleton1 = engine.singletonInstance<CppSingleton1*>(qmlTypeId: singletonTypeId1); |
9235 | QVERIFY(singleton1); |
9236 | |
9237 | auto singleton2 = engine.singletonInstance<CppSingleton2*>(qmlTypeId: singletonTypeId2); |
9238 | QVERIFY(singleton2); |
9239 | |
9240 | QCOMPARE(test->property("firstLookup" ).toInt(), singleton1->testVar); |
9241 | QCOMPARE(test->property("secondLookup" ).toInt(), singleton2->testVar); |
9242 | } |
9243 | |
9244 | void tst_qqmlecmascript::getThisObject() |
9245 | { |
9246 | QQmlEngine engine; |
9247 | QQmlComponent component(&engine, testFileUrl(fileName: "getThis.qml" )); |
9248 | QVERIFY(component.isReady()); |
9249 | QScopedPointer<QObject> test(component.create()); |
9250 | QVERIFY(!test.isNull()); |
9251 | |
9252 | QTRY_COMPARE(qvariant_cast<QObject *>(test->property("self" )), test.data()); |
9253 | } |
9254 | |
9255 | // QTBUG-77954 |
9256 | void tst_qqmlecmascript::semicolonAfterProperty() |
9257 | { |
9258 | QQmlEngine engine; |
9259 | QQmlComponent component(&engine, testFileUrl(fileName: "semicolonAfterProperty.qml" )); |
9260 | QVERIFY(component.isReady()); |
9261 | QScopedPointer<QObject> test(component.create()); |
9262 | QVERIFY(!test.isNull()); |
9263 | } |
9264 | |
9265 | void tst_qqmlecmascript::hugeStack() |
9266 | { |
9267 | QQmlEngine engine; |
9268 | QQmlComponent component(&engine, testFileUrl(fileName: "hugeStack.qml" )); |
9269 | QVERIFY(component.isReady()); |
9270 | QScopedPointer<QObject> test(component.create()); |
9271 | QVERIFY(!test.isNull()); |
9272 | |
9273 | QVariant huge = test->property(name: "longList" ); |
9274 | QCOMPARE(qvariant_cast<QJSValue>(huge).property(QLatin1String("length" )).toInt(), 33059); |
9275 | } |
9276 | |
9277 | void tst_qqmlecmascript::gcCrashRegressionTest() |
9278 | { |
9279 | const QString qmljs = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmljs" ; |
9280 | if (!QFile::exists(fileName: qmljs)) { |
9281 | QSKIP("Tets requires qmljs" ); |
9282 | } |
9283 | QProcess process; |
9284 | |
9285 | QTemporaryFile infile; |
9286 | QVERIFY(infile.open()); |
9287 | infile.write(data: R"js( |
9288 | function i_want_to_break_free() { |
9289 | var n = 400; |
9290 | var m = 10; |
9291 | var regex = new RegExp("(ab)".repeat(n), "g"); // g flag to trigger the vulnerable path |
9292 | var part = "ab".repeat(n); // matches have to be at least size 2 to prevent interning |
9293 | var s = (part + "|").repeat(m); |
9294 | var cnt = 0; |
9295 | var ary = []; |
9296 | s.replace(regex, function() { |
9297 | for (var i = 1; i < arguments.length-2; ++i) { |
9298 | if (typeof arguments[i] !== 'string') { |
9299 | i_am_free = arguments[i]; |
9300 | throw "success"; |
9301 | } |
9302 | ary[cnt++] = arguments[i]; // root everything to force GC |
9303 | } |
9304 | return "x"; |
9305 | }); |
9306 | } |
9307 | try { i_want_to_break_free(); } catch (e) {console.log("hi") } |
9308 | console.log(typeof(i_am_free)); // will print "object" |
9309 | )js" ); |
9310 | infile.close(); |
9311 | |
9312 | QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); |
9313 | environment.insert(name: "QV4_GC_MAX_STACK_SIZE" , value: "32768" ); |
9314 | |
9315 | process.setProcessEnvironment(environment); |
9316 | process.start(program: qmljs, arguments: QStringList({infile.fileName()})); |
9317 | QVERIFY(process.waitForStarted()); |
9318 | const qint64 pid = process.processId(); |
9319 | QVERIFY(pid != 0); |
9320 | QVERIFY(process.waitForFinished()); |
9321 | QCOMPARE(process.exitCode(), 0); |
9322 | } |
9323 | |
9324 | void tst_qqmlecmascript::variantConversionMethod() |
9325 | { |
9326 | QQmlEngine qmlengine; |
9327 | |
9328 | VariantConvertObject obj; |
9329 | qmlengine.rootContext()->setContextProperty("variantObject" , &obj); |
9330 | |
9331 | QQmlComponent component(&qmlengine, testFileUrl(fileName: "variantConvert.qml" )); |
9332 | QScopedPointer<QObject> o(component.create()); |
9333 | QVERIFY(o != nullptr); |
9334 | QCOMPARE(obj.funcCalled, QLatin1String("QModelIndex" )); |
9335 | } |
9336 | |
9337 | void tst_qqmlecmascript::proxyIteration() |
9338 | { |
9339 | QQmlEngine engine; |
9340 | QQmlComponent component(&engine, testFileUrl(fileName: "proxyIteration.qml" )); |
9341 | QScopedPointer<QObject> root(component.create()); |
9342 | QVERIFY2(root != nullptr, qPrintable(component.errorString())); |
9343 | QCOMPARE(root->property("sum" ).toInt(), 6); |
9344 | } |
9345 | |
9346 | void tst_qqmlecmascript::proxyHandlerTraps() |
9347 | { |
9348 | const QString expression = QStringLiteral(R"SNIPPET( |
9349 | (function(){ |
9350 | const target = { |
9351 | prop: 47 |
9352 | }; |
9353 | const handler = { |
9354 | getOwnPropertyDescriptor(target, prop) { |
9355 | return { configurable: true, enumerable: true, value: 47 }; |
9356 | } |
9357 | }; |
9358 | const proxy = new Proxy(target, handler); |
9359 | |
9360 | // QTBUG-88786 |
9361 | if (!proxy.propertyIsEnumerable("prop")) |
9362 | throw Error("FAIL: propertyisEnumerable"); |
9363 | if (!proxy.hasOwnProperty("prop")) |
9364 | throw Error("FAIL: hasOwnProperty"); |
9365 | |
9366 | return "SUCCESS"; |
9367 | })() |
9368 | )SNIPPET" ); |
9369 | |
9370 | QJSEngine engine; |
9371 | QJSValue value = engine.evaluate(program: expression); |
9372 | QVERIFY(value.isString() && value.toString() == QStringLiteral("SUCCESS" )); |
9373 | } |
9374 | |
9375 | void tst_qqmlecmascript::functionAsDefaultArgument() |
9376 | { |
9377 | QQmlEngine engine; |
9378 | QQmlComponent component(&engine, testFileUrl(fileName: "functionAsDefaultArgument.qml" )); |
9379 | QScopedPointer<QObject> root(component.create()); |
9380 | QVERIFY(root); |
9381 | QCOMPARE(root->objectName(), "didRun" ); |
9382 | } |
9383 | |
9384 | QTEST_MAIN(tst_qqmlecmascript) |
9385 | |
9386 | #include "tst_qqmlecmascript.moc" |
9387 | |