1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <qstandardpaths.h> |
30 | #include <qtest.h> |
31 | #include <qqml.h> |
32 | #include <qqmlprivate.h> |
33 | #include <qqmlengine.h> |
34 | #include <qqmlcomponent.h> |
35 | |
36 | #include <private/qqmlmetatype_p.h> |
37 | #include <private/qqmlpropertyvalueinterceptor_p.h> |
38 | #include <private/qqmlengine_p.h> |
39 | #include "../../shared/util.h" |
40 | |
41 | class tst_qqmlmetatype : public QQmlDataTest |
42 | { |
43 | Q_OBJECT |
44 | public: |
45 | tst_qqmlmetatype() {} |
46 | |
47 | private slots: |
48 | void initTestCase(); |
49 | |
50 | void qmlParserStatusCast(); |
51 | void qmlPropertyValueSourceCast(); |
52 | void qmlPropertyValueInterceptorCast(); |
53 | void qmlType(); |
54 | void invalidQmlTypeName(); |
55 | void prettyTypeName(); |
56 | void registrationType(); |
57 | void compositeType(); |
58 | void externalEnums(); |
59 | |
60 | void isList(); |
61 | |
62 | void defaultObject(); |
63 | void unregisterCustomType(); |
64 | void unregisterCustomSingletonType(); |
65 | |
66 | void normalizeUrls(); |
67 | void unregisterAttachedProperties(); |
68 | void revisionedGroupedProperties(); |
69 | |
70 | void enumsInRecursiveImport_data(); |
71 | void enumsInRecursiveImport(); |
72 | }; |
73 | |
74 | class TestType : public QObject |
75 | { |
76 | Q_OBJECT |
77 | Q_PROPERTY(int foo READ foo) |
78 | |
79 | Q_CLASSINFO("DefaultProperty" , "foo" ) |
80 | public: |
81 | int foo() { return 0; } |
82 | }; |
83 | QML_DECLARE_TYPE(TestType); |
84 | |
85 | class TestType2 : public QObject |
86 | { |
87 | Q_OBJECT |
88 | }; |
89 | |
90 | class TestType3 : public QObject |
91 | { |
92 | Q_OBJECT |
93 | }; |
94 | |
95 | class ExternalEnums : public QObject |
96 | { |
97 | Q_OBJECT |
98 | Q_ENUMS(QStandardPaths::StandardLocation QStandardPaths::LocateOptions) |
99 | public: |
100 | ExternalEnums(QObject *parent = nullptr) : QObject(parent) {} |
101 | |
102 | static QObject *create(QQmlEngine *engine, QJSEngine *scriptEngine) { |
103 | Q_UNUSED(scriptEngine); |
104 | return new ExternalEnums(engine); |
105 | } |
106 | }; |
107 | QML_DECLARE_TYPE(ExternalEnums); |
108 | |
109 | QObject *testTypeProvider(QQmlEngine *engine, QJSEngine *scriptEngine) |
110 | { |
111 | Q_UNUSED(engine); |
112 | Q_UNUSED(scriptEngine); |
113 | return new TestType(); |
114 | } |
115 | |
116 | class ParserStatusTestType : public QObject, public QQmlParserStatus |
117 | { |
118 | Q_OBJECT |
119 | void classBegin(){} |
120 | void componentComplete(){} |
121 | Q_CLASSINFO("DefaultProperty" , "foo" ) // Missing default property |
122 | Q_INTERFACES(QQmlParserStatus) |
123 | }; |
124 | QML_DECLARE_TYPE(ParserStatusTestType); |
125 | |
126 | class ValueSourceTestType : public QObject, public QQmlPropertyValueSource |
127 | { |
128 | Q_OBJECT |
129 | Q_INTERFACES(QQmlPropertyValueSource) |
130 | public: |
131 | virtual void setTarget(const QQmlProperty &) {} |
132 | }; |
133 | QML_DECLARE_TYPE(ValueSourceTestType); |
134 | |
135 | class ValueInterceptorTestType : public QObject, public QQmlPropertyValueInterceptor |
136 | { |
137 | Q_OBJECT |
138 | Q_INTERFACES(QQmlPropertyValueInterceptor) |
139 | public: |
140 | virtual void setTarget(const QQmlProperty &) {} |
141 | virtual void write(const QVariant &) {} |
142 | }; |
143 | QML_DECLARE_TYPE(ValueInterceptorTestType); |
144 | |
145 | void tst_qqmlmetatype::initTestCase() |
146 | { |
147 | QQmlDataTest::initTestCase(); |
148 | qmlRegisterType<TestType>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "TestType" ); |
149 | qmlRegisterSingletonType<TestType>(uri: "Test" , versionMajor: 1, versionMinor: 0, typeName: "TestTypeSingleton" , callback: testTypeProvider); |
150 | qmlRegisterType<ParserStatusTestType>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ParserStatusTestType" ); |
151 | qmlRegisterType<ValueSourceTestType>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ValueSourceTestType" ); |
152 | qmlRegisterType<ValueInterceptorTestType>(uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ValueInterceptorTestType" ); |
153 | |
154 | QUrl testTypeUrl(testFileUrl(fileName: "CompositeType.qml" )); |
155 | qmlRegisterType(url: testTypeUrl, uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "TestTypeComposite" ); |
156 | } |
157 | |
158 | void tst_qqmlmetatype::qmlParserStatusCast() |
159 | { |
160 | QVERIFY(!QQmlMetaType::qmlType(QMetaType::Int).isValid()); |
161 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).isValid()); |
162 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).parserStatusCast(), -1); |
163 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ValueSourceTestType *>()).isValid()); |
164 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<ValueSourceTestType *>()).parserStatusCast(), -1); |
165 | |
166 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ParserStatusTestType *>()).isValid()); |
167 | int cast = QQmlMetaType::qmlType(typeId: qMetaTypeId<ParserStatusTestType *>()).parserStatusCast(); |
168 | QVERIFY(cast != -1); |
169 | QVERIFY(cast != 0); |
170 | |
171 | ParserStatusTestType t; |
172 | QVERIFY(reinterpret_cast<char *>((QObject *)&t) != reinterpret_cast<char *>((QQmlParserStatus *)&t)); |
173 | |
174 | QQmlParserStatus *status = reinterpret_cast<QQmlParserStatus *>(reinterpret_cast<char *>((QObject *)&t) + cast); |
175 | QCOMPARE(status, (QQmlParserStatus*)&t); |
176 | } |
177 | |
178 | void tst_qqmlmetatype::qmlPropertyValueSourceCast() |
179 | { |
180 | QVERIFY(!QQmlMetaType::qmlType(QMetaType::Int).isValid()); |
181 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).isValid()); |
182 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).propertyValueSourceCast(), -1); |
183 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ParserStatusTestType *>()).isValid()); |
184 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<ParserStatusTestType *>()).propertyValueSourceCast(), -1); |
185 | |
186 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ValueSourceTestType *>()).isValid()); |
187 | int cast = QQmlMetaType::qmlType(typeId: qMetaTypeId<ValueSourceTestType *>()).propertyValueSourceCast(); |
188 | QVERIFY(cast != -1); |
189 | QVERIFY(cast != 0); |
190 | |
191 | ValueSourceTestType t; |
192 | QVERIFY(reinterpret_cast<char *>((QObject *)&t) != reinterpret_cast<char *>((QQmlPropertyValueSource *)&t)); |
193 | |
194 | QQmlPropertyValueSource *source = reinterpret_cast<QQmlPropertyValueSource *>(reinterpret_cast<char *>((QObject *)&t) + cast); |
195 | QCOMPARE(source, (QQmlPropertyValueSource*)&t); |
196 | } |
197 | |
198 | void tst_qqmlmetatype::qmlPropertyValueInterceptorCast() |
199 | { |
200 | QVERIFY(!QQmlMetaType::qmlType(QMetaType::Int).isValid()); |
201 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).isValid()); |
202 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<TestType *>()).propertyValueInterceptorCast(), -1); |
203 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ParserStatusTestType *>()).isValid()); |
204 | QCOMPARE(QQmlMetaType::qmlType(qMetaTypeId<ParserStatusTestType *>()).propertyValueInterceptorCast(), -1); |
205 | |
206 | QVERIFY(QQmlMetaType::qmlType(qMetaTypeId<ValueInterceptorTestType *>()).isValid()); |
207 | int cast = QQmlMetaType::qmlType(typeId: qMetaTypeId<ValueInterceptorTestType *>()).propertyValueInterceptorCast(); |
208 | QVERIFY(cast != -1); |
209 | QVERIFY(cast != 0); |
210 | |
211 | ValueInterceptorTestType t; |
212 | QVERIFY(reinterpret_cast<char *>((QObject *)&t) != reinterpret_cast<char *>((QQmlPropertyValueInterceptor *)&t)); |
213 | |
214 | QQmlPropertyValueInterceptor *interceptor = reinterpret_cast<QQmlPropertyValueInterceptor *>(reinterpret_cast<char *>((QObject *)&t) + cast); |
215 | QCOMPARE(interceptor, (QQmlPropertyValueInterceptor*)&t); |
216 | } |
217 | |
218 | void tst_qqmlmetatype::qmlType() |
219 | { |
220 | QQmlType type = QQmlMetaType::qmlType(name: QString("ParserStatusTestType" ), module: QString("Test" ), 1, 0); |
221 | QVERIFY(type.isValid()); |
222 | QVERIFY(type.module() == QLatin1String("Test" )); |
223 | QVERIFY(type.elementName() == QLatin1String("ParserStatusTestType" )); |
224 | QCOMPARE(type.qmlTypeName(), QLatin1String("Test/ParserStatusTestType" )); |
225 | |
226 | type = QQmlMetaType::qmlType(qualifiedName: "Test/ParserStatusTestType" , 1, 0); |
227 | QVERIFY(type.isValid()); |
228 | QVERIFY(type.module() == QLatin1String("Test" )); |
229 | QVERIFY(type.elementName() == QLatin1String("ParserStatusTestType" )); |
230 | QCOMPARE(type.qmlTypeName(), QLatin1String("Test/ParserStatusTestType" )); |
231 | } |
232 | |
233 | void tst_qqmlmetatype::invalidQmlTypeName() |
234 | { |
235 | QTest::ignoreMessage(type: QtWarningMsg, message: "Invalid QML element name \"testtype\"; type names must begin with an uppercase letter" ); |
236 | QTest::ignoreMessage(type: QtWarningMsg, message: "Invalid QML element name \"Test$Type\"" ); |
237 | QTest::ignoreMessage(type: QtWarningMsg, message: "Invalid QML element name \"EndingInSlash/\"" ); |
238 | |
239 | QCOMPARE(qmlRegisterType<TestType>("TestNamespace" , 1, 0, "Test$Type" ), -1); // should fail due to invalid QML type name. |
240 | QCOMPARE(qmlRegisterType<TestType>("Test" , 1, 0, "EndingInSlash/" ), -1); |
241 | QCOMPARE(qmlRegisterType<TestType>("Test" , 1, 0, "testtype" ), -1); |
242 | } |
243 | |
244 | void tst_qqmlmetatype::prettyTypeName() |
245 | { |
246 | TestType2 obj2; |
247 | QCOMPARE(QQmlMetaType::prettyTypeName(&obj2), QString("TestType2" )); |
248 | QVERIFY(qmlRegisterType<TestType2>("Test" , 1, 0, "" ) >= 0); |
249 | QCOMPARE(QQmlMetaType::prettyTypeName(&obj2), QString("TestType2" )); |
250 | |
251 | TestType3 obj3; |
252 | QCOMPARE(QQmlMetaType::prettyTypeName(&obj3), QString("TestType3" )); |
253 | QVERIFY(qmlRegisterType<TestType3>("Test" , 1, 0, "OtherName" ) >= 0); |
254 | QCOMPARE(QQmlMetaType::prettyTypeName(&obj3), QString("OtherName" )); |
255 | } |
256 | |
257 | void tst_qqmlmetatype::isList() |
258 | { |
259 | QCOMPARE(QQmlMetaType::isList(QMetaType::UnknownType), false); |
260 | QCOMPARE(QQmlMetaType::isList(QMetaType::Int), false); |
261 | |
262 | QQmlListProperty<TestType> list; |
263 | |
264 | QCOMPARE(QQmlMetaType::isList(qMetaTypeId<QQmlListProperty<TestType> >()), true); |
265 | } |
266 | |
267 | void tst_qqmlmetatype::defaultObject() |
268 | { |
269 | QVERIFY(!QQmlMetaType::defaultProperty(&QObject::staticMetaObject).name()); |
270 | QVERIFY(!QQmlMetaType::defaultProperty(&ParserStatusTestType::staticMetaObject).name()); |
271 | QCOMPARE(QString(QQmlMetaType::defaultProperty(&TestType::staticMetaObject).name()), QString("foo" )); |
272 | |
273 | QObject o; |
274 | TestType t; |
275 | ParserStatusTestType p; |
276 | |
277 | QVERIFY(QQmlMetaType::defaultProperty((QObject *)nullptr).name() == nullptr); |
278 | QVERIFY(!QQmlMetaType::defaultProperty(&o).name()); |
279 | QVERIFY(!QQmlMetaType::defaultProperty(&p).name()); |
280 | QCOMPARE(QString(QQmlMetaType::defaultProperty(&t).name()), QString("foo" )); |
281 | } |
282 | |
283 | void tst_qqmlmetatype::registrationType() |
284 | { |
285 | QQmlType type = QQmlMetaType::qmlType(name: QString("TestType" ), module: QString("Test" ), 1, 0); |
286 | QVERIFY(type.isValid()); |
287 | QVERIFY(!type.isInterface()); |
288 | QVERIFY(!type.isSingleton()); |
289 | QVERIFY(!type.isComposite()); |
290 | |
291 | type = QQmlMetaType::qmlType(name: QString("TestTypeSingleton" ), module: QString("Test" ), 1, 0); |
292 | QVERIFY(type.isValid()); |
293 | QVERIFY(!type.isInterface()); |
294 | QVERIFY(type.isSingleton()); |
295 | QVERIFY(!type.isComposite()); |
296 | |
297 | type = QQmlMetaType::qmlType(name: QString("TestTypeComposite" ), module: QString("Test" ), 1, 0); |
298 | QVERIFY(type.isValid()); |
299 | QVERIFY(!type.isInterface()); |
300 | QVERIFY(!type.isSingleton()); |
301 | QVERIFY(type.isComposite()); |
302 | } |
303 | |
304 | void tst_qqmlmetatype::compositeType() |
305 | { |
306 | QQmlEngine engine; |
307 | |
308 | //Loading the test file also loads all composite types it imports |
309 | QQmlComponent c(&engine, testFileUrl(fileName: "testImplicitComposite.qml" )); |
310 | QScopedPointer<QObject> obj(c.create()); |
311 | QVERIFY(obj); |
312 | |
313 | QQmlType type = QQmlMetaType::qmlType(name: QString("ImplicitType" ), module: QString("" ), 1, 0); |
314 | QVERIFY(type.isValid()); |
315 | QVERIFY(type.module().isEmpty()); |
316 | QCOMPARE(type.elementName(), QLatin1String("ImplicitType" )); |
317 | QCOMPARE(type.qmlTypeName(), QLatin1String("ImplicitType" )); |
318 | QCOMPARE(type.sourceUrl(), testFileUrl("ImplicitType.qml" )); |
319 | } |
320 | |
321 | void tst_qqmlmetatype::externalEnums() |
322 | { |
323 | QQmlEngine engine; |
324 | qmlRegisterSingletonType<ExternalEnums>(uri: "x.y.z" , versionMajor: 1, versionMinor: 0, typeName: "ExternalEnums" , callback: ExternalEnums::create); |
325 | |
326 | QQmlComponent c(&engine, testFileUrl(fileName: "testExternalEnums.qml" )); |
327 | QScopedPointer<QObject> obj(c.create()); |
328 | QVERIFY(obj); |
329 | QVariant a = obj->property(name: "a" ); |
330 | QCOMPARE(a.userType(), QVariant::Int); |
331 | QCOMPARE(a.toInt(), int(QStandardPaths::DocumentsLocation)); |
332 | QVariant b = obj->property(name: "b" ); |
333 | QCOMPARE(b.userType(), QVariant::Int); |
334 | QCOMPARE(b.toInt(), int(QStandardPaths::DocumentsLocation)); |
335 | |
336 | } |
337 | |
338 | class Controller1 : public QObject |
339 | { |
340 | Q_OBJECT |
341 | Q_PROPERTY(QString string MEMBER m_string) |
342 | Q_PROPERTY(Controller1Enum enumVal MEMBER m_enumVal) |
343 | public: |
344 | enum Controller1Enum { |
345 | ENUM_VALUE_1 = 1, |
346 | ENUM_VALUE_2 = 2 |
347 | }; |
348 | Q_ENUMS(Controller1Enum) |
349 | |
350 | Controller1(QObject *parent = nullptr) : QObject(parent), m_string("Controller #1" ), |
351 | m_enumVal(ENUM_VALUE_1) |
352 | {} |
353 | private: |
354 | QString m_string; |
355 | Controller1Enum m_enumVal; |
356 | }; |
357 | |
358 | class Controller2 : public QObject |
359 | { |
360 | Q_OBJECT |
361 | Q_PROPERTY(QString string MEMBER m_string) |
362 | Q_PROPERTY(Controller2Enum enumVal MEMBER m_enumVal) |
363 | public: |
364 | enum Controller2Enum { |
365 | ENUM_VALUE_1 = 111, |
366 | ENUM_VALUE_2 = 222 |
367 | }; |
368 | Q_ENUMS(Controller2Enum) |
369 | |
370 | Controller2(QObject *parent = nullptr) : QObject(parent), m_string("Controller #2" ), |
371 | m_enumVal(ENUM_VALUE_1) |
372 | {} |
373 | private: |
374 | QString m_string; |
375 | Controller2Enum m_enumVal; |
376 | }; |
377 | |
378 | void tst_qqmlmetatype::unregisterCustomType() |
379 | { |
380 | int controllerId = 0; |
381 | { |
382 | QQmlEngine engine; |
383 | QQmlType type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
384 | QVERIFY(!type.isValid()); |
385 | controllerId = qmlRegisterType<Controller1>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, qmlName: "Controller" ); |
386 | type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
387 | QVERIFY(type.isValid()); |
388 | QVERIFY(!type.isInterface()); |
389 | QVERIFY(!type.isSingleton()); |
390 | QVERIFY(!type.isComposite()); |
391 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomType.qml" )); |
392 | QScopedPointer<QObject> obj(c.create()); |
393 | QVERIFY(obj); |
394 | QObject *controller = obj->findChild<QObject *>(aName: "controller" ); |
395 | QVERIFY(qobject_cast<Controller1 *>(controller)); |
396 | QVariant stringVal = controller->property(name: "string" ); |
397 | QCOMPARE(stringVal.userType(), QVariant::String); |
398 | QCOMPARE(stringVal.toString(), QStringLiteral("Controller #1" )); |
399 | QVariant enumVal = controller->property(name: "enumVal" ); |
400 | QCOMPARE(enumVal.userType(), QVariant::Int); |
401 | QCOMPARE(enumVal.toInt(), 1); |
402 | } |
403 | QQmlMetaType::unregisterType(type: controllerId); |
404 | { |
405 | QQmlEngine engine; |
406 | QQmlType type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
407 | QVERIFY(!type.isValid()); |
408 | controllerId = qmlRegisterType<Controller2>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, qmlName: "Controller" ); |
409 | type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
410 | QVERIFY(type.isValid()); |
411 | QVERIFY(!type.isInterface()); |
412 | QVERIFY(!type.isSingleton()); |
413 | QVERIFY(!type.isComposite()); |
414 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomType.qml" )); |
415 | QScopedPointer<QObject> obj(c.create()); |
416 | QVERIFY(obj); |
417 | QObject *controller = obj->findChild<QObject *>(aName: "controller" ); |
418 | QVERIFY(qobject_cast<Controller2 *>(controller)); |
419 | QVariant stringVal = controller->property(name: "string" ); |
420 | QCOMPARE(stringVal.userType(), QVariant::String); |
421 | QCOMPARE(stringVal.toString(), QStringLiteral("Controller #2" )); |
422 | QVariant enumVal = controller->property(name: "enumVal" ); |
423 | QCOMPARE(enumVal.userType(), QVariant::Int); |
424 | QCOMPARE(enumVal.toInt(), 111); |
425 | } |
426 | QQmlMetaType::unregisterType(type: controllerId); |
427 | { |
428 | QQmlEngine engine; |
429 | QQmlType type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
430 | QVERIFY(!type.isValid()); |
431 | controllerId = qmlRegisterType<Controller1>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, qmlName: "Controller" ); |
432 | type = QQmlMetaType::qmlType(name: QString("Controller" ), module: QString("mytypes" ), 1, 0); |
433 | QVERIFY(type.isValid()); |
434 | QVERIFY(!type.isInterface()); |
435 | QVERIFY(!type.isSingleton()); |
436 | QVERIFY(!type.isComposite()); |
437 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomType.qml" )); |
438 | QScopedPointer<QObject> obj(c.create()); |
439 | QVERIFY(obj); |
440 | QObject *controller = obj->findChild<QObject *>(aName: "controller" ); |
441 | QVERIFY(qobject_cast<Controller1 *>(controller)); |
442 | QVariant stringVal = controller->property(name: "string" ); |
443 | QCOMPARE(stringVal.userType(), QVariant::String); |
444 | QCOMPARE(stringVal.toString(), QStringLiteral("Controller #1" )); |
445 | QVariant enumVal = controller->property(name: "enumVal" ); |
446 | QCOMPARE(enumVal.userType(), QVariant::Int); |
447 | QCOMPARE(enumVal.toInt(), 1); |
448 | } |
449 | } |
450 | |
451 | class StaticProvider1 : public QObject |
452 | { |
453 | Q_OBJECT |
454 | public: |
455 | StaticProvider1(QObject *parent = nullptr) : QObject(parent) {} |
456 | Q_INVOKABLE QString singletonGetString() { return "StaticProvider #1" ; } |
457 | }; |
458 | |
459 | static QObject* createStaticProvider1(QQmlEngine *, QJSEngine *) |
460 | { |
461 | return new StaticProvider1; |
462 | } |
463 | |
464 | class StaticProvider2 : public QObject |
465 | { |
466 | Q_OBJECT |
467 | public: |
468 | StaticProvider2(QObject *parent = nullptr) : QObject(parent) {} |
469 | Q_INVOKABLE QString singletonGetString() { return "StaticProvider #2" ; } |
470 | }; |
471 | |
472 | static QObject* createStaticProvider2(QQmlEngine *, QJSEngine *) |
473 | { |
474 | return new StaticProvider2; |
475 | } |
476 | |
477 | void tst_qqmlmetatype::unregisterCustomSingletonType() |
478 | { |
479 | int staticProviderId = 0; |
480 | { |
481 | QQmlEngine engine; |
482 | staticProviderId = qmlRegisterSingletonType<StaticProvider1>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, typeName: "StaticProvider" , callback: createStaticProvider1); |
483 | QQmlType type = QQmlMetaType::qmlType(name: QString("StaticProvider" ), module: QString("mytypes" ), 1, 0); |
484 | QVERIFY(type.isValid()); |
485 | QVERIFY(!type.isInterface()); |
486 | QVERIFY(type.isSingleton()); |
487 | QVERIFY(!type.isComposite()); |
488 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomSingletonType.qml" )); |
489 | QScopedPointer<QObject> obj(c.create()); |
490 | QVERIFY(obj.data()); |
491 | QVariant stringVal = obj->property(name: "text" ); |
492 | QCOMPARE(stringVal.userType(), QVariant::String); |
493 | QCOMPARE(stringVal.toString(), QStringLiteral("StaticProvider #1" )); |
494 | } |
495 | QQmlMetaType::unregisterType(type: staticProviderId); |
496 | { |
497 | QQmlEngine engine; |
498 | staticProviderId = qmlRegisterSingletonType<StaticProvider2>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, typeName: "StaticProvider" , callback: createStaticProvider2); |
499 | QQmlType type = QQmlMetaType::qmlType(name: QString("StaticProvider" ), module: QString("mytypes" ), 1, 0); |
500 | QVERIFY(type.isValid()); |
501 | QVERIFY(!type.isInterface()); |
502 | QVERIFY(type.isSingleton()); |
503 | QVERIFY(!type.isComposite()); |
504 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomSingletonType.qml" )); |
505 | QScopedPointer<QObject> obj(c.create()); |
506 | QVERIFY(obj.data()); |
507 | QVariant stringVal = obj->property(name: "text" ); |
508 | QCOMPARE(stringVal.userType(), QVariant::String); |
509 | QCOMPARE(stringVal.toString(), QStringLiteral("StaticProvider #2" )); |
510 | } |
511 | QQmlMetaType::unregisterType(type: staticProviderId); |
512 | { |
513 | QQmlEngine engine; |
514 | staticProviderId = qmlRegisterSingletonType<StaticProvider1>(uri: "mytypes" , versionMajor: 1, versionMinor: 0, typeName: "StaticProvider" , callback: createStaticProvider1); |
515 | QQmlType type = QQmlMetaType::qmlType(name: QString("StaticProvider" ), module: QString("mytypes" ), 1, 0); |
516 | QVERIFY(type.isValid()); |
517 | QVERIFY(!type.isInterface()); |
518 | QVERIFY(type.isSingleton()); |
519 | QVERIFY(!type.isComposite()); |
520 | QQmlComponent c(&engine, testFileUrl(fileName: "testUnregisterCustomSingletonType.qml" )); |
521 | QScopedPointer<QObject> obj(c.create()); |
522 | QVERIFY(obj.data()); |
523 | QVariant stringVal = obj->property(name: "text" ); |
524 | QCOMPARE(stringVal.userType(), QVariant::String); |
525 | QCOMPARE(stringVal.toString(), QStringLiteral("StaticProvider #1" )); |
526 | } |
527 | } |
528 | |
529 | void tst_qqmlmetatype::normalizeUrls() |
530 | { |
531 | const QUrl url("qrc:///tstqqmlmetatype/data/CompositeType.qml" ); |
532 | QVERIFY(!QQmlMetaType::qmlType(url).isValid()); |
533 | const auto registrationId = qmlRegisterType(url, uri: "Test" , versionMajor: 1, versionMinor: 0, qmlName: "ResourceCompositeType" ); |
534 | QVERIFY(QQmlMetaType::qmlType(url, /*includeNonFileImports=*/true).isValid()); |
535 | QUrl normalizedURL("qrc:/tstqqmlmetatype/data/CompositeType.qml" ); |
536 | QVERIFY(QQmlMetaType::qmlType(normalizedURL, /*includeNonFileImports=*/true).isValid()); |
537 | QQmlMetaType::unregisterType(type: registrationId); |
538 | QVERIFY(!QQmlMetaType::qmlType(url, /*includeNonFileImports=*/true).isValid()); |
539 | } |
540 | |
541 | void tst_qqmlmetatype::unregisterAttachedProperties() |
542 | { |
543 | qmlClearTypeRegistrations(); |
544 | |
545 | const QUrl dummy("qrc:///doesnotexist.qml" ); |
546 | { |
547 | QQmlEngine e; |
548 | QQmlComponent c(&e); |
549 | c.setData("import QtQuick 2.2\n Item { }" , baseUrl: dummy); |
550 | |
551 | const QQmlType attachedType = QQmlMetaType::qmlType(qualifiedName: "QtQuick/KeyNavigation" , 2, 2); |
552 | QCOMPARE(attachedType.attachedPropertiesType(QQmlEnginePrivate::get(&e)), |
553 | attachedType.metaObject()); |
554 | |
555 | QScopedPointer<QObject> obj(c.create()); |
556 | QVERIFY(obj); |
557 | } |
558 | |
559 | qmlClearTypeRegistrations(); |
560 | { |
561 | QQmlEngine e; |
562 | QQmlComponent c(&e); |
563 | |
564 | // The extra import shuffles the type IDs around, so that we |
565 | // get a different ID for the attached properties. If the attached |
566 | // properties aren't properly cleared, this will crash. |
567 | c.setData("import QtQml.StateMachine 1.0 \n" |
568 | "import QtQuick 2.2 \n" |
569 | "Item { KeyNavigation.up: null }" , baseUrl: dummy); |
570 | |
571 | const QQmlType attachedType = QQmlMetaType::qmlType(qualifiedName: "QtQuick/KeyNavigation" , 2, 2); |
572 | QCOMPARE(attachedType.attachedPropertiesType(QQmlEnginePrivate::get(&e)), |
573 | attachedType.metaObject()); |
574 | |
575 | QScopedPointer<QObject> obj(c.create()); |
576 | QVERIFY(obj); |
577 | } |
578 | } |
579 | |
580 | class Grouped : public QObject |
581 | { |
582 | Q_OBJECT |
583 | Q_PROPERTY(int prop READ prop WRITE setProp NOTIFY propChanged REVISION 1) |
584 | public: |
585 | int prop() const { return m_prop; } |
586 | void setProp(int prop) |
587 | { |
588 | if (prop != m_prop) { |
589 | m_prop = prop; |
590 | emit propChanged(prop); |
591 | } |
592 | } |
593 | |
594 | signals: |
595 | Q_REVISION(1) void propChanged(int prop); |
596 | |
597 | private: |
598 | int m_prop = 0; |
599 | }; |
600 | |
601 | class MyItem : public QObject |
602 | { |
603 | Q_OBJECT |
604 | Q_PROPERTY(Grouped *grouped READ grouped CONSTANT) |
605 | public: |
606 | MyItem() : m_grouped(new Grouped) {} |
607 | Grouped *grouped() const { return m_grouped.data(); } |
608 | |
609 | private: |
610 | QScopedPointer<Grouped> m_grouped; |
611 | }; |
612 | |
613 | void tst_qqmlmetatype::revisionedGroupedProperties() |
614 | { |
615 | qmlClearTypeRegistrations(); |
616 | qmlRegisterType<MyItem>(uri: "GroupedTest" , versionMajor: 1, versionMinor: 0, qmlName: "MyItem" ); |
617 | qmlRegisterType<MyItem, 1>(uri: "GroupedTest" , versionMajor: 1, versionMinor: 1, qmlName: "MyItem" ); |
618 | qmlRegisterUncreatableType<Grouped>(uri: "GroupedTest" , versionMajor: 1, versionMinor: 0, qmlName: "Grouped" , reason: "Grouped" ); |
619 | qmlRegisterUncreatableType<Grouped, 1>(uri: "GroupedTest" , versionMajor: 1, versionMinor: 1, qmlName: "Grouped" , reason: "Grouped" ); |
620 | |
621 | { |
622 | QQmlEngine engine; |
623 | QQmlComponent valid(&engine, testFileUrl(fileName: "revisionedGroupedPropertiesValid.qml" )); |
624 | QVERIFY(valid.isReady()); |
625 | QScopedPointer<QObject> obj(valid.create()); |
626 | QVERIFY(!obj.isNull()); |
627 | } |
628 | |
629 | { |
630 | QQmlEngine engine; |
631 | QQmlComponent invalid(&engine, testFileUrl(fileName: "revisionedGroupedPropertiesInvalid.qml" )); |
632 | QVERIFY(invalid.isError()); |
633 | } |
634 | } |
635 | |
636 | void tst_qqmlmetatype::enumsInRecursiveImport_data() |
637 | { |
638 | QTest::addColumn<QString>(name: "importPath" ); |
639 | QTest::addColumn<QUrl>(name: "componentUrl" ); |
640 | |
641 | QTest::addRow(format: "data directory" ) << dataDirectory() |
642 | << testFileUrl(fileName: "enumsInRecursiveImport.qml" ); |
643 | |
644 | // The qrc case behaves differently because we failed to detect the recursion in type loading |
645 | // due to varying numbers of slashes after the "qrc:" in the URLs. |
646 | QTest::addRow(format: "resources" ) << QStringLiteral("qrc:/data" ) |
647 | << QUrl("qrc:/data/enumsInRecursiveImport.qml" ); |
648 | } |
649 | |
650 | void tst_qqmlmetatype::enumsInRecursiveImport() |
651 | { |
652 | QFETCH(QString, importPath); |
653 | QFETCH(QUrl, componentUrl); |
654 | |
655 | qmlClearTypeRegistrations(); |
656 | QQmlEngine engine; |
657 | engine.addImportPath(dir: importPath); |
658 | QQmlComponent c(&engine, componentUrl); |
659 | QVERIFY(c.isReady()); |
660 | QScopedPointer<QObject> obj(c.create()); |
661 | QVERIFY(!obj.isNull()); |
662 | QTRY_COMPARE(obj->property("color" ).toString(), QString("green" )); |
663 | } |
664 | |
665 | QTEST_MAIN(tst_qqmlmetatype) |
666 | |
667 | #include "tst_qqmlmetatype.moc" |
668 | |