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 | #include <qtest.h> |
29 | #include <QtQml/qqmlengine.h> |
30 | #include <QtQml/qqmlcomponent.h> |
31 | #include <private/qqmldata_p.h> |
32 | #include <private/qqmlbinding_p.h> |
33 | #include <QtQuick/private/qquicktext_p.h> |
34 | #include <QtQuick/private/qquickrectangle_p.h> |
35 | #include "../../shared/util.h" |
36 | #include <qqmlcontext.h> |
37 | |
38 | class tst_bindingdependencyapi : public QObject |
39 | { |
40 | Q_OBJECT |
41 | public: |
42 | tst_bindingdependencyapi(); |
43 | |
44 | private slots: |
45 | void testSingleDep_data(); |
46 | void testSingleDep(); |
47 | void testManyDeps_data(); |
48 | void testManyDeps(); |
49 | void testConditionalDependencies_data(); |
50 | void testConditionalDependencies(); |
51 | void testBindingLoop(); |
52 | |
53 | private: |
54 | bool findProperties(const QVector<QQmlProperty> &properties, QObject *obj, const QString &propertyName, const QVariant &value); |
55 | }; |
56 | |
57 | tst_bindingdependencyapi::tst_bindingdependencyapi() |
58 | { |
59 | } |
60 | |
61 | |
62 | void tst_bindingdependencyapi::testSingleDep_data() |
63 | { |
64 | QTest::addColumn<QByteArray>(name: "code" ); |
65 | QTest::addColumn<QString>(name: "referencedObjectName" ); |
66 | |
67 | QTest::addRow(format: "context property" ) |
68 | << QByteArray("import QtQuick 2.0\n" |
69 | "Rectangle {\n" |
70 | "objectName: \"rect\"\n" |
71 | "property string labelText: \"Hello world!\"\n" |
72 | "Text { text: labelText }\n" |
73 | "}" ) << "rect" ; |
74 | |
75 | QTest::addRow(format: "scope property" ) |
76 | << QByteArray("import QtQuick 2.0\n" |
77 | "Rectangle {\n" |
78 | "property string labelText: \"I am wrong!\"\n" |
79 | "Text {\n" |
80 | "objectName: \"text\"\n" |
81 | "property string labelText: \"Hello world!\"\n" |
82 | "text: labelText\n" |
83 | "}\n" |
84 | "}" ) << "text" ; |
85 | |
86 | QTest::addRow(format: "id object property" ) |
87 | << QByteArray("import QtQuick 2.0\n" |
88 | "Rectangle {\n" |
89 | "id: rect\n" |
90 | "objectName: \"rect\"\n" |
91 | "property string labelText: \"Hello world!\"\n" |
92 | "Text { text: rect.labelText }\n" |
93 | "}" ) << "rect" ; |
94 | |
95 | QTest::addRow(format: "dynamic context property" ) |
96 | << QByteArray("import QtQuick 2.0\n" |
97 | "Rectangle {\n" |
98 | "objectName: \"rect\"\n" |
99 | "property string labelText: \"Hello world!\"\n" |
100 | "Text { Component.onCompleted: text = Qt.binding(function() { return labelText; }); }\n" |
101 | "}" ) << "rect" ; |
102 | |
103 | QTest::addRow(format: "dynamic scope property" ) |
104 | << QByteArray("import QtQuick 2.0\n" |
105 | "Rectangle {\n" |
106 | "property string labelText: \"I am wrong!\"\n" |
107 | "Text {\n" |
108 | "objectName: \"text\"\n" |
109 | "property string labelText: \"Hello world!\"\n" |
110 | "Component.onCompleted: text = Qt.binding(function() { return labelText; });\n" |
111 | "}\n" |
112 | "}" ) << "text" ; |
113 | |
114 | QTest::addRow(format: "dynamic id object property" ) |
115 | << QByteArray("import QtQuick 2.0\n" |
116 | "Rectangle {\n" |
117 | "id: rect\n" |
118 | "objectName: \"rect\"\n" |
119 | "property string labelText: \"Hello world!\"\n" |
120 | "Text { Component.onCompleted: text = Qt.binding(function() { return rect.labelText; }); }\n" |
121 | "}" ) << "rect" ; |
122 | } |
123 | |
124 | void tst_bindingdependencyapi::testSingleDep() |
125 | { |
126 | QFETCH(QByteArray, code); |
127 | QFETCH(QString, referencedObjectName); |
128 | |
129 | QQmlEngine engine; |
130 | QQmlComponent c(&engine); |
131 | c.setData(code, baseUrl: QUrl()); |
132 | QObject *rect = c.create(); |
133 | QTest::qWait(ms: 10); |
134 | QVERIFY(rect != nullptr); |
135 | QObject *text = rect->findChildren<QQuickText *>().front(); |
136 | |
137 | QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(aName: referencedObjectName); |
138 | |
139 | auto data = QQmlData::get(object: text); |
140 | QVERIFY(data); |
141 | auto b = data->bindings; |
142 | QVERIFY(b); |
143 | auto binding = dynamic_cast<QQmlBinding*>(b); |
144 | QVERIFY(binding); |
145 | auto dependencies = binding->dependencies(); |
146 | QCOMPARE(dependencies.size(), 1); |
147 | auto dependency = dependencies.front(); |
148 | QVERIFY(dependency.isValid()); |
149 | QCOMPARE(quintptr(dependency.object()), quintptr(referencedObject)); |
150 | QCOMPARE(dependency.property().name(), "labelText" ); |
151 | QCOMPARE(dependency.read().toString(), QStringLiteral("Hello world!" )); |
152 | QCOMPARE(dependency, QQmlProperty(referencedObject, "labelText" )); |
153 | |
154 | delete rect; |
155 | } |
156 | |
157 | bool tst_bindingdependencyapi::findProperties(const QVector<QQmlProperty> &properties, QObject *obj, const QString &propertyName, const QVariant &value) |
158 | { |
159 | auto dep = std::find_if(first: properties.cbegin(), last: properties.cend(), pred: [&](const QQmlProperty &dep) { |
160 | return dep.object() == obj |
161 | && dep.property().name() == propertyName |
162 | && dep.read() == value; |
163 | }); |
164 | if (dep == properties.cend()) { |
165 | qWarning() << "Searched for property with:" << "{ object:" << obj << ", propertyName:" << propertyName << ", value:" << value << "}" << "but only found:" ; |
166 | for (auto dep : properties) { |
167 | qWarning() << "{ object:" << dep.object() << ", propertyName:" << dep.property().name() << ", value:" << dep.read() << "}" ; |
168 | } |
169 | return false; |
170 | } |
171 | return true; |
172 | } |
173 | |
174 | void tst_bindingdependencyapi::testManyDeps_data() |
175 | { |
176 | QTest::addColumn<QByteArray>(name: "code" ); |
177 | |
178 | QTest::addRow(format: "permanent binding" ) |
179 | << QByteArray("import QtQuick 2.0\n" |
180 | "Rectangle {\n" |
181 | "id: rect\n" |
182 | "objectName: 'rect'\n" |
183 | "property string name: 'world'\n" |
184 | "Text {\n" |
185 | "text: config.helloWorldTemplate.arg(greeting).arg(rect.name) \n" |
186 | "property string greeting: 'Hello'\n" |
187 | "}\n" |
188 | "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n" |
189 | "}" ); |
190 | |
191 | QTest::addRow(format: "dynamic binding" ) |
192 | << QByteArray("import QtQuick 2.0\n" |
193 | "Rectangle {\n" |
194 | "id: rect\n" |
195 | "objectName: 'rect'\n" |
196 | "property string name: 'world'\n" |
197 | "Text {\n" |
198 | "Component.onCompleted: text = Qt.binding(function() { return config.helloWorldTemplate.arg(greeting).arg(rect.name); }); \n" |
199 | "property string greeting: 'Hello'\n" |
200 | "}\n" |
201 | "QtObject { id: config; objectName: 'config'; property string helloWorldTemplate: '%1 %2!' }\n" |
202 | "}" ); |
203 | } |
204 | |
205 | void tst_bindingdependencyapi::testManyDeps() |
206 | { |
207 | QFETCH(QByteArray, code); |
208 | QQmlEngine engine; |
209 | QQmlComponent c(&engine); |
210 | c.setData(code, baseUrl: QUrl()); |
211 | QObject *rect = c.create(); |
212 | if (c.isError()) { |
213 | qWarning() << c.errorString(); |
214 | } |
215 | QTest::qWait(ms: 100); |
216 | QVERIFY(rect != nullptr); |
217 | QObject *text = rect->findChildren<QQuickText *>().front(); |
218 | QObject *configObj = rect->findChild<QObject *>(aName: "config" ); |
219 | |
220 | auto data = QQmlData::get(object: text); |
221 | QVERIFY(data); |
222 | auto b = data->bindings; |
223 | QVERIFY(b); |
224 | auto binding = dynamic_cast<QQmlBinding*>(b); |
225 | QVERIFY(binding); |
226 | auto dependencies = binding->dependencies(); |
227 | QCOMPARE(dependencies.size(), 3); |
228 | |
229 | QVERIFY(findProperties(dependencies, rect, "name" , "world" )); |
230 | QVERIFY(findProperties(dependencies, text, "greeting" , "Hello" )); |
231 | QVERIFY(findProperties(dependencies, configObj, "helloWorldTemplate" , "%1 %2!" )); |
232 | |
233 | delete rect; |
234 | } |
235 | |
236 | void tst_bindingdependencyapi::testConditionalDependencies_data() |
237 | { |
238 | QTest::addColumn<QByteArray>(name: "code" ); |
239 | QTest::addColumn<QString>(name: "referencedObjectName" ); |
240 | |
241 | QTest::addRow(format: "id object property" ) |
242 | << QByteArray("import QtQuick 2.0\n" |
243 | "Rectangle {\n" |
244 | "id: rect\n" |
245 | "objectName: \"rect\"\n" |
246 | "property bool haveDep: false\n" |
247 | "property string labelText: \"Hello world!\"\n" |
248 | "Text { text: rect.haveDep ? rect.labelText : '' }\n" |
249 | "}" ) << "rect" ; |
250 | |
251 | QTest::addRow(format: "dynamic context property" ) |
252 | << QByteArray("import QtQuick 2.0\n" |
253 | "Rectangle {\n" |
254 | "objectName: \"rect\"\n" |
255 | "property bool haveDep: false\n" |
256 | "property string labelText: \"Hello world!\"\n" |
257 | "Text { Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; }); }\n" |
258 | "}" ) << "rect" ; |
259 | |
260 | QTest::addRow(format: "dynamic scope property" ) |
261 | << QByteArray("import QtQuick 2.0\n" |
262 | "Rectangle {\n" |
263 | "property string labelText: \"I am wrong!\"\n" |
264 | "Text {\n" |
265 | "objectName: \"text\"\n" |
266 | "property bool haveDep: false\n" |
267 | "property string labelText: \"Hello world!\"\n" |
268 | "Component.onCompleted: text = Qt.binding(function() { return haveDep ? labelText : ''; });\n" |
269 | "}\n" |
270 | "}" ) << "text" ; |
271 | |
272 | QTest::addRow(format: "dynamic id object property" ) |
273 | << QByteArray("import QtQuick 2.0\n" |
274 | "Rectangle {\n" |
275 | "id: rect\n" |
276 | "objectName: \"rect\"\n" |
277 | "property bool haveDep: false\n" |
278 | "property string labelText: \"Hello world!\"\n" |
279 | "Text { Component.onCompleted: text = Qt.binding(function() { return rect.haveDep ? rect.labelText : ''; }); }\n" |
280 | "}" ) << "rect" ; |
281 | } |
282 | |
283 | void tst_bindingdependencyapi::testConditionalDependencies() |
284 | { |
285 | QFETCH(QByteArray, code); |
286 | QFETCH(QString, referencedObjectName); |
287 | |
288 | QQmlEngine engine; |
289 | QQmlComponent c(&engine); |
290 | c.setData(code, baseUrl: QUrl()); |
291 | QObject *rect = c.create(); |
292 | QTest::qWait(ms: 10); |
293 | QVERIFY(rect != nullptr); |
294 | QObject *text = rect->findChildren<QQuickText *>().front(); |
295 | |
296 | QObject *referencedObject = rect->objectName() == referencedObjectName ? rect : rect->findChild<QObject *>(aName: referencedObjectName); |
297 | |
298 | auto data = QQmlData::get(object: text); |
299 | QVERIFY(data); |
300 | auto b = data->bindings; |
301 | QVERIFY(b); |
302 | auto binding = dynamic_cast<QQmlBinding*>(b); |
303 | QVERIFY(binding); |
304 | auto dependencies = binding->dependencies(); |
305 | QCOMPARE(dependencies.size(), 1); |
306 | QVERIFY(findProperties(dependencies, referencedObject, "haveDep" , false)); |
307 | |
308 | referencedObject->setProperty(name: "haveDep" , value: true); |
309 | dependencies = binding->dependencies(); |
310 | QCOMPARE(dependencies.size(), 2); |
311 | QVERIFY(findProperties(dependencies, referencedObject, "haveDep" , true)); |
312 | QVERIFY(findProperties(dependencies, referencedObject, "labelText" , "Hello world!" )); |
313 | |
314 | referencedObject->setProperty(name: "haveDep" , value: false); |
315 | dependencies = binding->dependencies(); |
316 | QCOMPARE(dependencies.size(), 1); |
317 | QVERIFY(findProperties(dependencies, referencedObject, "haveDep" , false)); |
318 | |
319 | delete rect; |
320 | } |
321 | |
322 | void tst_bindingdependencyapi::testBindingLoop() |
323 | { |
324 | QQmlEngine engine; |
325 | QQmlComponent c(&engine); |
326 | c.setData(QByteArray("import QtQuick 2.0\n" |
327 | "Rectangle {\n" |
328 | "property string labelText: label.text\n" |
329 | "Text {\n" |
330 | "id: label\n" |
331 | "text: labelText\n" |
332 | "}\n" |
333 | "}" ), baseUrl: QUrl()); |
334 | QObject *rect = c.create(); |
335 | if (c.isError()) { |
336 | qWarning() << c.errorString(); |
337 | } |
338 | QTest::qWait(ms: 100); |
339 | QVERIFY(rect != nullptr); |
340 | QObject *text = rect->findChildren<QQuickText *>().front(); |
341 | |
342 | auto data = QQmlData::get(object: text); |
343 | QVERIFY(data); |
344 | auto b = data->bindings; |
345 | QVERIFY(b); |
346 | auto binding = dynamic_cast<QQmlBinding*>(b); |
347 | QVERIFY(binding); |
348 | auto dependencies = binding->dependencies(); |
349 | QCOMPARE(dependencies.size(), 1); |
350 | auto dependency = dependencies.front(); |
351 | QVERIFY(dependency.isValid()); |
352 | QCOMPARE(quintptr(dependency.object()), quintptr(rect)); |
353 | QCOMPARE(dependency.property().name(), "labelText" ); |
354 | |
355 | delete rect; |
356 | } |
357 | |
358 | QTEST_MAIN(tst_bindingdependencyapi) |
359 | |
360 | #include "tst_bindingdependencyapi.moc" |
361 | |