1 /*
2   qtivipropertymodeltest.cpp
3 
4   This file is part of GammaRay, the Qt application inspection and
5   manipulation tool.
6 
7   Copyright (C) 2016-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8   Author: Filipe Azevedo <filipe.azevedo@kdab.com>
9 
10   Licensees holding valid commercial KDAB GammaRay licenses may use this file in
11   accordance with GammaRay Commercial License Agreement provided with the Software.
12 
13   Contact info@kdab.com if any conditions of this licensing are not clear to you.
14 
15   This program is free software; you can redistribute it and/or modify
16   it under the terms of the GNU General Public License as published by
17   the Free Software Foundation, either version 2 of the License, or
18   (at your option) any later version.
19 
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24 
25   You should have received a copy of the GNU General Public License
26   along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 */
28 
29 #include "baseprobetest.h"
30 #include "testhelpers.h"
31 
32 #include <plugins/qtivi/qtivipropertymodel.h>
33 
34 #include <common/objectbroker.h>
35 #include <common/objectid.h>
36 
37 #include <3rdparty/qt/modeltest.h>
38 
39 #include <QDebug>
40 #include <QIviServiceObject>
41 #include <QIviClimateControl>
42 #if defined(QT_IVIMEDIA_LIB)
43 #include <QIviAmFmTuner>
44 #endif
45 
46 #include <algorithm>
47 
48 //#define ENABLE_LOG
49 
50 using namespace GammaRay;
51 using namespace TestHelpers;
52 
53 class QtIviPropertyModelTest : public BaseProbeTest
54 {
55     Q_OBJECT
56     QIviClimateControl *m_climate;
57 #if defined(QT_IVIMEDIA_LIB)
58     QIviAmFmTuner *m_amfm;
59 #endif
60     QAbstractItemModel *m_model;
61 
62     typedef QSharedPointer<QtIviPropertyModel::IviCarrierProperty> IviCarrierProperty;
63 
64 public:
QtIviPropertyModelTest(QObject * parent=nullptr)65     explicit QtIviPropertyModelTest(QObject *parent = nullptr)
66         : BaseProbeTest(parent)
67         , m_climate(nullptr)
68 #if defined(QT_IVIMEDIA_LIB)
69         , m_amfm(nullptr)
70 #endif
71         , m_model(nullptr)
72     {
73     }
74 
75 private:
createIviProperties()76     void createIviProperties()
77     {
78         m_climate = new QIviClimateControl(QString(), this);
79         QVERIFY(m_climate);
80         m_climate->setDiscoveryMode(QIviAbstractZonedFeature::LoadOnlySimulationBackends);
81         QVERIFY(m_climate->startAutoDiscovery() == QIviAbstractFeature::SimulationBackendLoaded);
82         QVERIFY(m_climate->isValid());
83 #if defined(QT_IVIMEDIA_LIB)
84         m_amfm = new QIviAmFmTuner(this);
85         QVERIFY(m_amfm);
86         m_amfm->setDiscoveryMode(QIviAbstractZonedFeature::LoadOnlySimulationBackends);
87         QVERIFY(m_amfm->startAutoDiscovery() == QIviAbstractFeature::SimulationBackendLoaded);
88         QVERIFY(m_amfm->isValid());
89 #endif
90     }
91 
carrierIndex(QObject * carrier,int column) const92     QModelIndex carrierIndex(QObject *carrier, int column) const
93     {
94         const auto index(m_model->match(m_model->index(0, 0), ObjectModel::ObjectIdRole,
95                                         QVariant::fromValue(ObjectId(carrier)), 1,
96                                         Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)).value(0));
97         return index.sibling(index.row(), column);
98     }
99 
carrierPropertyIndex(QObject * carrier,const QString & property,int column) const100     QModelIndex carrierPropertyIndex(QObject *carrier, const QString &property, int column) const
101     {
102         const QModelIndex parent(carrierIndex(carrier, 0));
103         const auto index(parent.isValid()
104                          ? m_model->match(m_model->index(0, 0, parent), Qt::DisplayRole, property, 1,
105                                           Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)).value(0)
106                          : QModelIndex());
107         return index.sibling(index.row(), column);
108     }
109 
carrierPropertyData(QObject * carrier,const QString & property,int column,int role) const110     QVariant carrierPropertyData(QObject *carrier, const QString &property, int column, int role) const
111     {
112         return carrierPropertyIndex(carrier, property, column).data(role);
113     }
114 
carrierPropertyValue(QObject * carrier,const QString & property) const115     QVariant carrierPropertyValue(QObject *carrier, const QString &property) const
116     {
117         return carrierPropertyData(carrier, property, QtIviPropertyModel::ValueColumn,
118                                    QtIviPropertyModel::RawValue);
119     }
120 
carrierPropertyIndex(QIviProperty * property,int column) const121     QModelIndex carrierPropertyIndex(QIviProperty *property, int column) const
122     {
123         const auto index(m_model->match(m_model->index(0, 0), ObjectModel::ObjectIdRole,
124                                         QVariant::fromValue(ObjectId(property)), 1,
125                                         Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap | Qt::MatchRecursive))
126                          .value(0));
127         return index.sibling(index.row(), column);
128     }
129 
carrierPropertyData(QIviProperty * property,int column,int role) const130     QVariant carrierPropertyData(QIviProperty *property, int column, int role) const
131     {
132         return carrierPropertyIndex(property, column).data(role);
133     }
134 
carrierPropertyValue(QIviProperty * property) const135     QVariant carrierPropertyValue(QIviProperty *property) const
136     {
137         return carrierPropertyData(property, QtIviPropertyModel::ValueColumn,
138                                    QtIviPropertyModel::RawValue);
139     }
140 
iviCarrierProperty(QObject * carrier,const QString & property) const141     IviCarrierProperty iviCarrierProperty(QObject *carrier, const QString &property) const {
142         if (!carrier)
143             return IviCarrierProperty::create();
144 
145         const QMetaObject *mo(carrier->metaObject());
146         const int index(mo->indexOfProperty(qPrintable(property)));
147 
148         if (index == -1)
149             return IviCarrierProperty::create();
150 
151         const QMetaProperty metaProperty(mo->property(index));
152 
153         if (metaProperty.userType() == qMetaTypeId<QIviProperty *>()) {
154             return IviCarrierProperty::create(metaProperty.read(carrier).value<QIviProperty *>(), metaProperty);
155         }
156 
157         return IviCarrierProperty::create(metaProperty);
158     }
159 
160     // Check ivi object properties vs model properties
testIviObject(QObject * iviObject) const161     bool testIviObject(QObject *iviObject) const
162     {
163 #if defined(ENABLE_LOG)
164         qWarning() << "Testing ivi object: " << iviObject;
165 #endif
166         QVERIFY_RETURN_FALSE(iviObject);
167 
168         QVector<QString> iviPropertyNames;
169         QMap<QString, QVariant> iviProperties;
170         const QMetaObject *iviMo(iviObject->metaObject());
171         int propertyOffset = -1;
172 
173         if (qobject_cast<QIviServiceObject *>(iviObject))
174             propertyOffset = QIviServiceObject::staticMetaObject.propertyOffset();
175         else if (qobject_cast<QIviAbstractFeature *>(iviObject))
176             propertyOffset = QIviAbstractFeature::staticMetaObject.propertyOffset();
177 
178         QVERIFY_RETURN_FALSE(propertyOffset != -1);
179 
180         for (int i = propertyOffset; i < iviMo->propertyCount(); ++i) {
181             const QMetaProperty property(iviMo->property(i));
182 
183             iviPropertyNames << QLatin1String(property.name());
184             QVERIFY_RETURN_FALSE(!iviPropertyNames.last().isEmpty());
185 
186             if (property.userType() == qMetaTypeId<QIviProperty *>()) {
187                 QIviProperty *ivi = property.read(iviObject).value<QIviProperty *>();
188                 QVERIFY_RETURN_FALSE(ivi);
189                 iviProperties[iviPropertyNames.last()] = ivi->value();
190             } else {
191                 iviProperties[iviPropertyNames.last()] = property.read(iviObject);
192             }
193         }
194 
195         std::stable_sort(iviPropertyNames.begin(), iviPropertyNames.end());
196         QVERIFY_RETURN_FALSE(!iviPropertyNames.isEmpty());
197         QVERIFY_RETURN_FALSE(!iviProperties.isEmpty());
198 
199         const QModelIndex carrierIndex(this->carrierIndex(iviObject, 0));
200         const int carrierPropertyCount(m_model->rowCount(carrierIndex));
201         QVERIFY_RETURN_FALSE(carrierIndex.isValid());
202         QCOMPARE_RETURN_FALSE(iviProperties.count(), carrierPropertyCount);
203 
204         QVector<QString> carrierModelPropertyNames;
205 
206         for (int i = 0; i < carrierPropertyCount; ++i) {
207             const QModelIndex nameIndex(m_model->index(i, QtIviPropertyModel::NameColumn, carrierIndex));
208             carrierModelPropertyNames << nameIndex.data(Qt::DisplayRole).toString();
209             QVERIFY_RETURN_FALSE(!carrierModelPropertyNames.last().isEmpty());
210             QCOMPARE_RETURN_FALSE(iviProperties[carrierModelPropertyNames.last()],
211                     carrierPropertyValue(iviObject, carrierModelPropertyNames.last()));
212         }
213 
214         std::stable_sort(carrierModelPropertyNames.begin(), carrierModelPropertyNames.end());
215         QCOMPARE_RETURN_FALSE(carrierModelPropertyNames.count(), iviPropertyNames.count());
216         QVERIFY_RETURN_FALSE(carrierModelPropertyNames == iviPropertyNames);
217 
218         if (QIviAbstractZonedFeature *zonedFeature = qobject_cast<QIviAbstractZonedFeature *>(iviObject)) {
219             foreach (QIviAbstractZonedFeature *subZone, zonedFeature->zones()) {
220                 if (!testIviObject(subZone)) {
221                     return false;
222                 }
223             }
224         }
225 
226         return true;
227     }
228 
229 private slots:
initTestCase()230     void initTestCase()
231     {
232         createIviProperties();
233         createProbe();
234         m_model = ObjectBroker::model("com.kdab.GammaRay.PropertyModel");
235         QVERIFY(m_model);
236     }
237 
testModelContent()238     void testModelContent()
239     {
240         QVERIFY(new ModelTest(m_model, this));
241         QVERIFY(testIviObject(m_climate));
242 #if defined(QT_IVIMEDIA_LIB)
243         QVERIFY(testIviObject(m_amfm));
244 #endif
245     }
246 
testModelContentChanges_data()247     void testModelContentChanges_data()
248     {
249         QTest::addColumn<QObject *>("carrier");
250         QTest::addColumn<QString>("property");
251         QTest::addColumn<bool>("override");
252         QTest::addColumn<QVariant>("value");
253         QTest::addColumn<QVariant>("expect");
254         QTest::addColumn<bool>("invalidProperty");
255 
256         // Invalid value mean nothing to set
257         // Invalid expect mean use value
258 
259         QTest::newRow("climate-default-climateMode")
260                 << (QObject *)m_climate
261                 << "climateMode"
262                 << true
263                 << QVariant()
264                 << QVariant::fromValue(QIviClimateControl::ClimateOn)
265                 << false
266         ;
267 
268         QTest::newRow("climate-climateMode-Off")
269                 << (QObject *)m_climate
270                 << "climateMode"
271                 << false
272                 << QVariant::fromValue(QIviClimateControl::ClimateOff)
273                 << QVariant()
274                 << false
275         ;
276 
277         QTest::newRow("climate-airflowDirections-Floor")
278                 << (QObject *)m_climate
279                 << "airflowDirections"
280                 << false
281                 << QVariant::fromValue(QIviClimateControl::AirflowDirections(QIviClimateControl::Floor))
282                 << QVariant()
283                 << false
284         ;
285 
286         QTest::newRow("climate-airflowDirections-Floor|Dashboard")
287                 << (QObject *)m_climate
288                 << "airflowDirections"
289                 << false
290                 << QVariant::fromValue(QIviClimateControl::AirflowDirections(QIviClimateControl::Floor |
291                                                                              QIviClimateControl::Dashboard))
292                 << QVariant()
293                 << false
294         ;
295 
296         QTest::newRow("climate-FrontLeft-targetTemperature-25")
297                 << (QObject *)m_climate->zoneAt("FrontLeft")
298                 << "targetTemperature"
299                 << false
300                 << QVariant(25)
301                 << QVariant()
302                 << false
303         ;
304 
305         QTest::newRow("climate-FrontRight-seatHeater-8")
306                 << (QObject *)m_climate->zoneAt("FrontRight")
307                 << "seatHeater"
308                 << false
309                 << QVariant(8)
310                 << QVariant()
311                 << false
312         ;
313 
314         QTest::newRow("unknown-property")
315                 << (QObject *)nullptr
316                 << "hey"
317                 << false
318                 << QVariant(42)
319                 << QVariant()
320                 << true
321         ;
322 
323         QTest::newRow("climate-unknown-property-override")
324                 << (QObject *)m_climate->zoneAt("FrontLeft")
325                 << "hey"
326                 << true
327                 << QVariant(42)
328                 << QVariant()
329                 << true
330         ;
331 
332         QTest::newRow("climate-FrontLeft-heater-true(readonly)")
333                 << (QObject *)m_climate->zoneAt("FrontLeft")
334                 << "heater"
335                 << false
336                 << QVariant(true)
337                 << QVariant(false)
338                 << false
339         ;
340 
341 #if defined(QT_IVIMEDIA_LIB)
342         QTest::newRow("amfm-default-discoveryMode")
343                 << (QObject *)m_amfm
344                 << "discoveryMode"
345                 << false
346                 << QVariant()
347                 << QVariant::fromValue(QIviAbstractFeature::LoadOnlySimulationBackends)
348                 << false
349         ;
350 
351         QTest::newRow("amfm-discoveryMode-AutoDiscovery")
352                 << (QObject *)m_amfm
353                 << "discoveryMode"
354                 << false
355                 << QVariant::fromValue(QIviAbstractFeature::AutoDiscovery)
356                 << QVariant()
357                 << false
358         ;
359 
360         QTest::newRow("amfm-band-AMBand")
361                 << (QObject *)m_amfm
362                 << "band"
363                 << false
364                 << QVariant::fromValue(QIviAmFmTuner::AMBand)
365                 << QVariant()
366                 << false
367         ;
368 
369         QTest::newRow("amfm-unknown-property-override")
370                 << (QObject *)m_amfm
371                 << "hey"
372                 << true
373                 << QVariant(42)
374                 << QVariant()
375                 << true
376         ;
377 
378         QTest::newRow("amfm-scanRunning-true(readonly)")
379                 << (QObject *)m_amfm
380                 << "scanRunning"
381                 << false
382                 << QVariant(true)
383                 << QVariant(false)
384                 << false
385         ;
386 #endif
387     }
388 
testModelContentChanges()389     void testModelContentChanges()
390     {
391         QFETCH(QObject *, carrier);
392         QFETCH(QString, property);
393         QFETCH(bool, override);
394         QFETCH(QVariant, value);
395         QFETCH(QVariant, expect);
396         QFETCH(bool, invalidProperty);
397         const QVariant expectValue(expect.isValid() ? expect : value);
398         const IviCarrierProperty iviProperty(iviCarrierProperty(carrier, property));
399 
400         if (invalidProperty) {
401             QEXPECT_FAIL("", "Expected not found property", Continue);
402         }
403         QVERIFY(iviProperty->isValid());
404 
405         const QModelIndex valueIndex(carrierPropertyIndex(carrier, property, QtIviPropertyModel::ValueColumn));
406         if (iviProperty->m_iviProperty) {
407             QCOMPARE(valueIndex, carrierPropertyIndex(iviProperty->m_iviProperty, valueIndex.column()));
408         }
409         QCOMPARE(valueIndex.isValid(), iviProperty->isValid());
410         QCOMPARE(!invalidProperty, iviProperty->isValid());
411 
412 
413 #if defined(ENABLE_LOG)
414         qWarning("Original value: %s", invalidProperty ? "" : qPrintable(iviProperty->value().toString()));
415 #endif
416         const QVariant originalValue = iviProperty->cppValue(carrier);
417         const QModelIndex overrideIndex(valueIndex.sibling(valueIndex.row(), QtIviPropertyModel::OverrideColumn));
418 
419 #if defined(ENABLE_LOG)
420         qWarning("Setting override: %i", (int)override);
421 #endif
422         const bool willOverride(iviProperty->isAvailable() && iviProperty->isOverridable() &&
423                                 (overrideIndex.data(Qt::CheckStateRole).value<Qt::CheckState>() == Qt::Checked) != override);
424         QCOMPARE(m_model->setData(overrideIndex, override ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole), willOverride);
425         QCOMPARE((overrideIndex.data(Qt::CheckStateRole).value<Qt::CheckState>() == Qt::Checked),
426                  (!iviProperty->isAvailable() ? false : override));
427 
428         if (value.isValid()) {
429 #if defined(ENABLE_LOG)
430             qWarning("Setting value: %s", qPrintable(value.toString()));
431 #endif
432             const bool willWrite(iviProperty->isAvailable() && iviProperty->isWritable() &&
433                                  valueIndex.data(QtIviPropertyModel::RawValue) != value);
434             QCOMPARE(m_model->setData(valueIndex, value, Qt::EditRole), willWrite);
435             if (willWrite) {
436                 QCOMPARE(valueIndex.data(QtIviPropertyModel::RawValue), value);
437             }
438         }
439 
440         if (iviProperty->isValid()) {
441             QCOMPARE(iviProperty->cppValue(carrier), iviProperty->isAvailable() && !override ? expectValue : originalValue);
442         }
443         QCOMPARE(iviProperty->isAvailable(),
444                 valueIndex.flags().testFlag(Qt::ItemIsEnabled));
445         QCOMPARE(iviProperty->isAvailable() && iviProperty->isWritable(),
446                  valueIndex.flags().testFlag(Qt::ItemIsEditable));
447         QCOMPARE(valueIndex.data(QtIviPropertyModel::RawValue),
448                  invalidProperty ? QVariant() : expectValue);
449     }
450 };
451 
452 QTEST_MAIN(QtIviPropertyModelTest)
453 
454 #include "qtivipropertymodeltest.moc"
455