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/qqmlbind_p.h>
32#include <QtQuick/private/qquickrectangle_p.h>
33#include "../../shared/util.h"
34
35class tst_qqmlbinding : public QQmlDataTest
36{
37 Q_OBJECT
38public:
39 tst_qqmlbinding();
40
41private slots:
42 void binding();
43 void whenAfterValue();
44 void restoreBinding();
45 void restoreBindingValue();
46 void restoreBindingVarValue();
47 void restoreBindingJSValue();
48 void restoreBindingWithLoop();
49 void restoreBindingWithoutCrash();
50 void deletedObject();
51 void warningOnUnknownProperty();
52 void warningOnReadOnlyProperty();
53 void disabledOnUnknownProperty();
54 void disabledOnReadonlyProperty();
55 void delayed();
56 void bindingOverwriting();
57 void bindToQmlComponent();
58 void bindingDoesNoWeirdConversion();
59 void bindNaNToInt();
60
61private:
62 QQmlEngine engine;
63};
64
65tst_qqmlbinding::tst_qqmlbinding()
66{
67}
68
69void tst_qqmlbinding::binding()
70{
71 QQmlEngine engine;
72 QQmlComponent c(&engine, testFileUrl(fileName: "test-binding.qml"));
73 QScopedPointer<QQuickRectangle> rect { qobject_cast<QQuickRectangle*>(object: c.create()) };
74 QVERIFY(rect != nullptr);
75
76 QQmlBind *binding3 = qobject_cast<QQmlBind*>(object: rect->findChild<QQmlBind*>(aName: "binding3"));
77 QVERIFY(binding3 != nullptr);
78
79 QCOMPARE(rect->color(), QColor("yellow"));
80 QCOMPARE(rect->property("text").toString(), QString("Hello"));
81 QCOMPARE(binding3->when(), false);
82
83 rect->setProperty(name: "changeColor", value: true);
84 QCOMPARE(rect->color(), QColor("red"));
85
86 QCOMPARE(binding3->when(), true);
87
88 QQmlBind *binding = qobject_cast<QQmlBind*>(object: rect->findChild<QQmlBind*>(aName: "binding1"));
89 QVERIFY(binding != nullptr);
90 QCOMPARE(binding->object(), qobject_cast<QObject*>(rect.get()));
91 QCOMPARE(binding->property(), QLatin1String("text"));
92 QCOMPARE(binding->value().toString(), QLatin1String("Hello"));
93}
94
95void tst_qqmlbinding::whenAfterValue()
96{
97 QQmlEngine engine;
98 QQmlComponent c(&engine, testFileUrl(fileName: "test-binding2.qml"));
99 QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(object: c.create())};
100
101 QVERIFY(rect != nullptr);
102 QCOMPARE(rect->color(), QColor("yellow"));
103 QCOMPARE(rect->property("text").toString(), QString("Hello"));
104
105 rect->setProperty(name: "changeColor", value: true);
106 QCOMPARE(rect->color(), QColor("red"));
107}
108
109void tst_qqmlbinding::restoreBinding()
110{
111 QQmlEngine engine;
112 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBinding.qml"));
113 QScopedPointer<QQuickRectangle> rect { qobject_cast<QQuickRectangle*>(object: c.create()) };
114 QVERIFY(rect != nullptr);
115
116 QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
117 QVERIFY(myItem != nullptr);
118
119 myItem->setY(25);
120 QCOMPARE(myItem->x(), qreal(100-25));
121
122 myItem->setY(13);
123 QCOMPARE(myItem->x(), qreal(100-13));
124
125 //Binding takes effect
126 myItem->setY(51);
127 QCOMPARE(myItem->x(), qreal(51));
128
129 myItem->setY(88);
130 QCOMPARE(myItem->x(), qreal(88));
131
132 //original binding restored
133 myItem->setY(49);
134 QCOMPARE(myItem->x(), qreal(100-49));
135}
136
137void tst_qqmlbinding::restoreBindingValue()
138{
139 QQmlEngine engine;
140 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBinding2.qml"));
141 QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(object: c.create()));
142 QVERIFY(!rect.isNull());
143
144 auto myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
145 QVERIFY(myItem != nullptr);
146
147 QCOMPARE(myItem->height(), 100);
148 myItem->setProperty(name: "when", value: QVariant(false));
149 QCOMPARE(myItem->height(), 300); // make sure the original value was restored
150
151 myItem->setProperty(name: "when", value: QVariant(true));
152 QCOMPARE(myItem->height(), 100); // make sure the value specified in Binding is set
153 rect->setProperty(name: "boundValue", value: 200);
154 QCOMPARE(myItem->height(), 200); // make sure the changed binding value is set
155 myItem->setProperty(name: "when", value: QVariant(false));
156 // make sure that the original value is back, not e.g. the value from before the
157 // change (i.e. 100)
158 QCOMPARE(myItem->height(), 300);
159}
160
161void tst_qqmlbinding::restoreBindingVarValue()
162{
163 QQmlEngine engine;
164 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBinding3.qml"));
165 QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(object: c.create()));
166 QVERIFY(!rect.isNull());
167
168 auto myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
169 QVERIFY(myItem != nullptr);
170
171 QCOMPARE(myItem->property("foo"), 13);
172 myItem->setProperty(name: "when", value: QVariant(false));
173 QCOMPARE(myItem->property("foo"), 42); // make sure the original value was restored
174
175 myItem->setProperty(name: "when", value: QVariant(true));
176 QCOMPARE(myItem->property("foo"), 13); // make sure the value specified in Binding is set
177 rect->setProperty(name: "boundValue", value: 31337);
178 QCOMPARE(myItem->property("foo"), 31337); // make sure the changed binding value is set
179 myItem->setProperty(name: "when", value: QVariant(false));
180 // make sure that the original value is back, not e.g. the value from before the
181 // change (i.e. 100)
182 QCOMPARE(myItem->property("foo"), 42);
183}
184
185void tst_qqmlbinding::restoreBindingJSValue()
186{
187 QQmlEngine engine;
188 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBinding4.qml"));
189 QScopedPointer<QQuickRectangle> rect(qobject_cast<QQuickRectangle*>(object: c.create()));
190 QVERIFY(!rect.isNull());
191
192 auto myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
193 QVERIFY(myItem != nullptr);
194
195 QCOMPARE(myItem->property("fooCheck"), false);
196 myItem->setProperty(name: "when", value: QVariant(false));
197 QCOMPARE(myItem->property("fooCheck"), true); // make sure the original value was restored
198
199 myItem->setProperty(name: "when", value: QVariant(true));
200 QCOMPARE(myItem->property("fooCheck"), false); // make sure the value specified in Binding is set
201 rect->setProperty(name: "boundValue", value: 31337);
202 QCOMPARE(myItem->property("fooCheck"), false); // make sure the changed binding value is set
203 myItem->setProperty(name: "when", value: QVariant(false));
204 // make sure that the original value is back, not e.g. the value from before the change
205 QCOMPARE(myItem->property("fooCheck"), true);
206
207}
208
209void tst_qqmlbinding::restoreBindingWithLoop()
210{
211 QQmlEngine engine;
212 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBindingWithLoop.qml"));
213 QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(object: c.create())};
214 QVERIFY(rect != nullptr);
215
216 QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
217 QVERIFY(myItem != nullptr);
218
219 myItem->setY(25);
220 QCOMPARE(myItem->x(), qreal(25 + 100));
221
222 myItem->setY(13);
223 QCOMPARE(myItem->x(), qreal(13 + 100));
224
225 //Binding takes effect
226 rect->setProperty(name: "activateBinding", value: true);
227 myItem->setY(51);
228 QCOMPARE(myItem->x(), qreal(51));
229
230 myItem->setY(88);
231 QCOMPARE(myItem->x(), qreal(88));
232
233 //original binding restored
234 QString warning = c.url().toString() + QLatin1String(":9:5: QML Rectangle: Binding loop detected for property \"x\"");
235 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning));
236 rect->setProperty(name: "activateBinding", value: false);
237 QCOMPARE(myItem->x(), qreal(88 + 100)); //if loop handling changes this could be 90 + 100
238
239 myItem->setY(49);
240 QCOMPARE(myItem->x(), qreal(49 + 100));
241}
242
243void tst_qqmlbinding::restoreBindingWithoutCrash()
244{
245 QQmlEngine engine;
246 QQmlComponent c(&engine, testFileUrl(fileName: "restoreBindingWithoutCrash.qml"));
247 QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(object: c.create())};
248 QVERIFY(rect != nullptr);
249
250 QQuickRectangle *myItem = qobject_cast<QQuickRectangle*>(object: rect->findChild<QQuickRectangle*>(aName: "myItem"));
251 QVERIFY(myItem != nullptr);
252
253 myItem->setY(25);
254 QCOMPARE(myItem->x(), qreal(100-25));
255
256 myItem->setY(13);
257 QCOMPARE(myItem->x(), qreal(100-13));
258
259 //Binding takes effect
260 myItem->setY(51);
261 QCOMPARE(myItem->x(), qreal(51));
262
263 myItem->setY(88);
264 QCOMPARE(myItem->x(), qreal(88));
265
266 //state sets a new binding
267 rect->setState("state1");
268 //this binding temporarily takes effect. We may want to change this behavior in the future
269 QCOMPARE(myItem->x(), qreal(112));
270
271 //Binding still controls this value
272 myItem->setY(104);
273 QCOMPARE(myItem->x(), qreal(104));
274
275 //original binding restored
276 myItem->setY(49);
277 QCOMPARE(myItem->x(), qreal(100-49));
278}
279
280//QTBUG-20692
281void tst_qqmlbinding::deletedObject()
282{
283 QQmlEngine engine;
284 QQmlComponent c(&engine, testFileUrl(fileName: "deletedObject.qml"));
285 QScopedPointer<QQuickRectangle> rect {qobject_cast<QQuickRectangle*>(object: c.create())};
286 QVERIFY(rect != nullptr);
287
288 QGuiApplication::sendPostedEvents(receiver: nullptr, event_type: QEvent::DeferredDelete);
289
290 //don't crash
291 rect->setProperty(name: "activateBinding", value: true);
292}
293
294void tst_qqmlbinding::warningOnUnknownProperty()
295{
296 QQmlTestMessageHandler messageHandler;
297
298 QQmlEngine engine;
299 QQmlComponent c(&engine, testFileUrl(fileName: "unknownProperty.qml"));
300 QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(object: c.create()) };
301 QVERIFY(item);
302
303 QCOMPARE(messageHandler.messages().count(), 1);
304
305 const QString expectedMessage = c.url().toString() + QLatin1String(":6:5: QML Binding: Property 'unknown' does not exist on Item.");
306 QCOMPARE(messageHandler.messages().first(), expectedMessage);
307}
308
309void tst_qqmlbinding::warningOnReadOnlyProperty()
310{
311 QQmlTestMessageHandler messageHandler;
312
313 QQmlEngine engine;
314 QQmlComponent c(&engine, testFileUrl(fileName: "readonlyProperty.qml"));
315 QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(object: c.create()) };
316 QVERIFY(item);
317
318 QCOMPARE(messageHandler.messages().count(), 1);
319
320 const QString expectedMessage = c.url().toString() + QLatin1String(":8:5: QML Binding: Property 'name' on Item is read-only.");
321 QCOMPARE(messageHandler.messages().first(), expectedMessage);
322}
323
324void tst_qqmlbinding::disabledOnUnknownProperty()
325{
326 QQmlTestMessageHandler messageHandler;
327
328 QQmlEngine engine;
329 QQmlComponent c(&engine, testFileUrl(fileName: "disabledUnknown.qml"));
330 QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(object: c.create()) };
331 QVERIFY(item);
332
333 QCOMPARE(messageHandler.messages().count(), 0);
334}
335
336void tst_qqmlbinding::disabledOnReadonlyProperty()
337{
338 QQmlTestMessageHandler messageHandler;
339
340 QQmlEngine engine;
341 QQmlComponent c(&engine, testFileUrl(fileName: "disabledReadonly.qml"));
342 QScopedPointer<QQuickItem> item { qobject_cast<QQuickItem *>(object: c.create()) };
343 QVERIFY(item);
344 QCOMPARE(messageHandler.messages().count(), 0);
345}
346
347void tst_qqmlbinding::delayed()
348{
349 QQmlEngine engine;
350 QQmlComponent c(&engine, testFileUrl(fileName: "delayed.qml"));
351 QScopedPointer<QQuickItem> item {qobject_cast<QQuickItem*>(object: c.create())};
352
353 QVERIFY(item != nullptr);
354 // update on creation
355 QCOMPARE(item->property("changeCount").toInt(), 1);
356
357 QMetaObject::invokeMethod(obj: item.get(), member: "updateText");
358 // doesn't update immediately
359 QCOMPARE(item->property("changeCount").toInt(), 1);
360
361 // only updates once (non-delayed would update twice)
362 QTRY_COMPARE(item->property("changeCount").toInt(), 2);
363}
364
365void tst_qqmlbinding::bindingOverwriting()
366{
367 QQmlTestMessageHandler messageHandler;
368 QLoggingCategory::setFilterRules(QStringLiteral("qt.qml.binding.removal.info=true"));
369
370 QQmlEngine engine;
371 QQmlComponent c(&engine, testFileUrl(fileName: "bindingOverwriting.qml"));
372 QScopedPointer<QQuickItem> item {qobject_cast<QQuickItem*>(object: c.create())};
373 QVERIFY(item);
374
375 QLoggingCategory::setFilterRules(QString());
376 QCOMPARE(messageHandler.messages().count(), 2);
377}
378
379void tst_qqmlbinding::bindToQmlComponent()
380{
381 QQmlEngine engine;
382 QQmlComponent c(&engine, testFileUrl(fileName: "bindToQMLComponent.qml"));
383 QVERIFY(c.create());
384}
385
386// QTBUG-78943
387void tst_qqmlbinding::bindingDoesNoWeirdConversion()
388{
389 QQmlEngine engine;
390 QQmlComponent c(&engine, testFileUrl(fileName: "noUnexpectedStringConversion.qml"));
391 QScopedPointer<QObject> o {c.create()};
392 QVERIFY(o);
393 QObject *colorRect = o->findChild<QObject*>(aName: "colorRect");
394 QVERIFY(colorRect);
395 QCOMPARE(qvariant_cast<QColor>(colorRect->property("color")), QColorConstants::Red);
396 QObject *colorLabel = o->findChild<QObject*>(aName: "colorLabel");
397 QCOMPARE(colorLabel->property("text").toString(), QLatin1String("red"));
398 QVERIFY(colorLabel);
399}
400
401//QTBUG-72442
402void tst_qqmlbinding::bindNaNToInt()
403{
404 QQmlEngine engine;
405 QQmlComponent c(&engine, testFileUrl(fileName: "nanPropertyToInt.qml"));
406 QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(object: c.create()));
407
408 QVERIFY(item != nullptr);
409 QCOMPARE(item->property("val").toInt(), 0);
410}
411QTEST_MAIN(tst_qqmlbinding)
412
413#include "tst_qqmlbinding.moc"
414

source code of qtdeclarative/tests/auto/qml/qqmlbinding/tst_qqmlbinding.cpp