summaryrefslogtreecommitdiffstats
path: root/tests/auto/shared/bindableutils.h
blob: 4a45f89645ba87c7374fdd1b0a0f4ee2ff5220da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtScxml module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#ifndef BINDABLEUTILS_H
#define BINDABLEUTILS_H

#include <QtTest>
#include <QObject>

// This is a helper function to test basics of typical bindable
//  properties that are read-only (but can change) Primarily ensure:
// - properties work as before bindings
// - added bindable aspects work
//
// "TestedClass" is the class type we are testing
// "TestedData" is the data type of the property we are testing
// "testedClass" is an instance of the class we are interested testing
// "data0" is the initial datavalue
// "data1" is an instance of the propertydata and must differ from data0
// "propertyName" is the name of the property we are interested in testing
// "modifierFunction" that does whatever is needed to change the underlying property
// A custom "dataComparator" can be provided for cases that the data doesn't have operator==
template<typename TestedClass, typename TestedData>
void testReadableBindableBasics(TestedClass& testedClass, TestedData data0, TestedData data1,
                        const char* propertyName,
                        std::function<void()> modifierFunction = [](){ qWarning() << "Error, data modifier function must be provided"; },
                        std::function<bool(TestedData,TestedData)> dataComparator = [](TestedData d1, TestedData d2) { return d1 == d2; })
{
    // Get the property we are testing
    const QMetaObject *metaObject = testedClass.metaObject();
    QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));

    // Generate a string to help identify failures (as this is a generic template)
    QString id(metaObject->className());
    id.append(QStringLiteral("::"));
    id.append(propertyName);

    // Fail gracefully if preconditions to use this helper function are not met:
    QVERIFY2(metaProperty.isBindable() && metaProperty.hasNotifySignal(), qPrintable(id));
    QVERIFY2(modifierFunction, qPrintable(id));
    // Create a signal spy for the property changed -signal
    QSignalSpy spy(&testedClass, metaProperty.notifySignal());
    QUntypedBindable bindable = metaProperty.bindable(&testedClass);

    // Verify initial data is as expected
    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data0), qPrintable(id));
    // Use the property as the source in a binding
    QProperty<bool> data1Used([&](){
        return dataComparator(testedClass.property(propertyName).template value<TestedData>(), data1);
    });
    // Verify binding's initial state
    QVERIFY2(data1Used == false, qPrintable(id));
    // Call the supplied modifier function and verify that the value and binding both change
    modifierFunction();
    QVERIFY2(data1Used == true, qPrintable(id));
    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data1), qPrintable(id));
    QVERIFY2(spy.count() == 1, qPrintable(id + ", actual: " + QString::number(spy.count())));
}


// This is a helper function to test basics of typical bindable
//  properties that are writable. Primarily ensure:
// - properties work as before bindings
// - added bindable aspects work
//
// "TestedClass" is the class type we are testing
// "TestedData" is the data type of the property we are testing
// "testedClass" is an instance of the class we are interested testing
// "data1" and "data2" are two different instances of property data to set and get
// The "data1" and "data2" must differ from one another, and
// the "data1" must differ from instance property's initial state
// "propertyName" is the name of the property we are interested in testing
// A custom "dataComparator" can be provided for cases that the data doesn't have operator==
template<typename TestedClass, typename TestedData>
void testWritableBindableBasics(TestedClass& testedClass, TestedData data1,
                        TestedData data2, const char* propertyName,
                        std::function<bool(TestedData,TestedData)> dataComparator = [](TestedData d1, TestedData d2) { return d1 == d2; })
{
    // Get the property we are testing
    const QMetaObject *metaObject = testedClass.metaObject();
    QMetaProperty metaProperty = metaObject->property(metaObject->indexOfProperty(propertyName));

    // Generate a string to help identify failures (as this is a generic template)
    QString id(metaObject->className());
    id.append(QStringLiteral("::"));
    id.append(propertyName);

    // Fail gracefully if preconditions to use this helper function are not met:
    QVERIFY2(metaProperty.isBindable() && metaProperty.isWritable()
            && metaProperty.hasNotifySignal(), qPrintable(id));
    // Create a signal spy for the property changed -signal
    QSignalSpy spy(&testedClass, metaProperty.notifySignal());
    QUntypedBindable bindable = metaProperty.bindable(&testedClass);

    // Test basic property read and write
    testedClass.setProperty(propertyName, QVariant::fromValue(data1));

    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data1), qPrintable(id));
    QVERIFY2(spy.count() == 1, qPrintable(id + ", actual: " + QString::number(spy.count())));

    // Test setting a binding as a source for the property
    QProperty<TestedData> property1(data1);
    QProperty<TestedData> property2(data2);
    QVERIFY2(!bindable.hasBinding(), qPrintable(id));
    bindable.setBinding(Qt::makePropertyBinding(property2));
    QVERIFY2(bindable.hasBinding(), qPrintable(id));
    // Check that the value also changed
    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data2), qPrintable(id));
    QVERIFY2(spy.count() == 2, qPrintable(id + ", actual: " + QString::number(spy.count())));
    // Same test but with a lambda binding (cast to be able to set the lambda directly)
    QBindable<TestedData> *typedBindable = static_cast<QBindable<TestedData>*>(&bindable);
    typedBindable->setBinding([&](){ return property1.value(); });
    QVERIFY2(typedBindable->hasBinding(), qPrintable(id));
    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data1), qPrintable(id));
    QVERIFY2(spy.count() == 3, qPrintable(id + ", actual: " + QString::number(spy.count())));

    // Remove binding by setting a value directly
    QVERIFY2(bindable.hasBinding(), qPrintable(id));
    testedClass.setProperty(propertyName, QVariant::fromValue(data2));
    QVERIFY2(dataComparator(testedClass.property(propertyName).template value<TestedData>(), data2), qPrintable(id));
    QVERIFY2(!bindable.hasBinding(), qPrintable(id));
    QVERIFY2(spy.count() == 4, qPrintable(id + ", actual: " + QString::number(spy.count())));

    // Test using the property as the source in a binding
    QProperty<bool> data1Used([&](){
        return dataComparator(testedClass.property(propertyName).template value<TestedData>(), data1);
    });
    QVERIFY2(data1Used == false, qPrintable(id));
    testedClass.setProperty(propertyName, QVariant::fromValue(data1));
    QVERIFY2(data1Used == true, qPrintable(id));
}

#endif // BINDABLEUTILS_H