1 /***************************************************************************
2 testqgsfieldexpressionwidget.cpp
3 --------------------------------------
4 Date : January 2016
5 Copyright : (C) 2016 Denis Rouzaud
6 Email : denis.rouzaud@gmail.com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
17 #include "qgstest.h"
18 #include <QObject>
19 #include <QSignalSpy>
20
21 //qgis includes...
22 #include <qgsvectorlayer.h>
23 #include <qgsapplication.h>
24 #include <qgsvectorlayerjoinbuffer.h>
25 #include <qgsfieldexpressionwidget.h>
26 #include <qgsproject.h>
27
28 /**
29 * @ingroup UnitTests
30 * This is a unit test for the field expression widget
31 *
32 * \see QgsFieldExpressionWidget
33 */
34 class TestQgsFieldExpressionWidget : public QObject
35 {
36 Q_OBJECT
37
38 public:
39 TestQgsFieldExpressionWidget() = default;
40
41 private slots:
42 void initTestCase(); // will be called before the first testfunction is executed.
43 void cleanupTestCase(); // will be called after the last testfunction was executed.
44 void init(); // will be called before each testfunction is executed.
45 void cleanup(); // will be called after every testfunction.
46
47 void testRemoveJoin();
48 void asExpression();
49 void testIsValid();
50 void testFilters();
51 void setNull();
52
53 private:
54 QgsFieldExpressionWidget *mWidget = nullptr;
55 QgsVectorLayer *mLayerA = nullptr;
56 QgsVectorLayer *mLayerB = nullptr;
57 };
58
59 // runs before all tests
initTestCase()60 void TestQgsFieldExpressionWidget::initTestCase()
61 {
62 QgsApplication::init();
63 QgsApplication::initQgis();
64
65 // Set up the QgsSettings environment
66 QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
67 QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
68 QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
69
70 // Create memory layers
71 // LAYER A //
72 mLayerA = new QgsVectorLayer( QStringLiteral( "Point?field=id_a:integer" ), QStringLiteral( "A" ), QStringLiteral( "memory" ) );
73 QVERIFY( mLayerA->isValid() );
74 QVERIFY( mLayerA->fields().count() == 1 );
75 QgsProject::instance()->addMapLayer( mLayerA );
76 // LAYER B //
77 mLayerB = new QgsVectorLayer( QStringLiteral( "Point?field=id_b:integer&field=value_b" ), QStringLiteral( "B" ), QStringLiteral( "memory" ) );
78 QVERIFY( mLayerB->isValid() );
79 QVERIFY( mLayerB->fields().count() == 2 );
80 QgsProject::instance()->addMapLayer( mLayerB );
81
82 // init widget
83 mWidget = new QgsFieldExpressionWidget();
84 mWidget->setLayer( mLayerA );
85
86
87 }
88
init()89 void TestQgsFieldExpressionWidget::init()
90 {
91 }
92
cleanup()93 void TestQgsFieldExpressionWidget::cleanup()
94 {
95 }
96
cleanupTestCase()97 void TestQgsFieldExpressionWidget::cleanupTestCase()
98 {
99 QgsApplication::exitQgis();
100 }
101
testRemoveJoin()102 void TestQgsFieldExpressionWidget::testRemoveJoin()
103 {
104
105 QVERIFY( mLayerA->fields().count() == 1 );
106
107 QgsVectorLayerJoinInfo joinInfo;
108 joinInfo.setTargetFieldName( QStringLiteral( "id_a" ) );
109 joinInfo.setJoinLayer( mLayerB );
110 joinInfo.setJoinFieldName( QStringLiteral( "id_b" ) );
111 joinInfo.setUsingMemoryCache( false );
112 joinInfo.setPrefix( QStringLiteral( "B_" ) );
113 mLayerA->addJoin( joinInfo );
114
115 QVERIFY( mLayerA->fields().count() == 2 );
116
117 const QString expr = QStringLiteral( "'hello '|| B_value_b" );
118 mWidget->setField( expr );
119
120 bool isExpression, isValid;
121 QVERIFY( mWidget->isValidExpression() );
122 QCOMPARE( mWidget->currentField( &isExpression, &isValid ), expr );
123 QVERIFY( isExpression );
124 QVERIFY( isValid );
125
126 QVERIFY( mLayerA->removeJoin( mLayerB->id() ) );
127
128 QVERIFY( mLayerA->fields().count() == 1 );
129
130 QCOMPARE( mWidget->mCombo->currentText(), expr );
131
132 QCOMPARE( mWidget->currentField( &isExpression, &isValid ), expr );
133 QVERIFY( isExpression );
134 // QVERIFY( !isValid ); TODO: the expression should not be valid anymore since the field doesn't exist anymore. Maybe we need a new expression method to get more details.
135 }
136
asExpression()137 void TestQgsFieldExpressionWidget::asExpression()
138 {
139 QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=fld:int&field=fld2:int&field=fld3:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
140 layer->dataProvider()->addAttributes( QList< QgsField >() << QgsField( QStringLiteral( "a space" ), QVariant::String ) );
141 layer->updateFields();
142 QgsProject::instance()->addMapLayer( layer );
143
144 std::unique_ptr< QgsFieldExpressionWidget > widget( new QgsFieldExpressionWidget() );
145 widget->setLayer( layer );
146
147 const QSignalSpy spy( widget.get(), static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ) );
148 const QSignalSpy spy2( widget.get(), static_cast < void ( QgsFieldExpressionWidget::* )( const QString &, bool ) >( &QgsFieldExpressionWidget::fieldChanged ) );
149
150 // check with field set
151 widget->setField( QStringLiteral( "fld" ) );
152 QCOMPARE( widget->asExpression(), QStringLiteral( "\"fld\"" ) );
153 QCOMPARE( spy.count(), 1 );
154 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "fld" ) );
155 QCOMPARE( spy2.count(), 1 );
156 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "fld" ) );
157 QVERIFY( spy2.constLast().at( 1 ).toBool() );
158
159 // check with expressions set
160 widget->setField( QStringLiteral( "fld + 1" ) );
161 QCOMPARE( widget->asExpression(), QStringLiteral( "fld + 1" ) );
162 QCOMPARE( spy.count(), 2 );
163 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "fld + 1" ) );
164 QCOMPARE( spy2.count(), 2 );
165 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "fld + 1" ) );
166 QVERIFY( spy2.constLast().at( 1 ).toBool() );
167
168 widget->setField( QStringLiteral( "1" ) );
169 QCOMPARE( widget->asExpression(), QStringLiteral( "1" ) );
170 QCOMPARE( spy.count(), 3 );
171 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "1" ) );
172 QCOMPARE( spy2.count(), 3 );
173 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "1" ) );
174 QVERIFY( spy2.constLast().at( 1 ).toBool() );
175
176 widget->setField( QStringLiteral( "\"fld2\"" ) );
177 QCOMPARE( widget->asExpression(), QStringLiteral( "\"fld2\"" ) );
178 QCOMPARE( spy.count(), 4 );
179 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "fld2" ) );
180 QCOMPARE( spy2.count(), 4 );
181 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "fld2" ) );
182 QVERIFY( spy2.constLast().at( 1 ).toBool() );
183
184 // check switching back to a field
185 widget->setField( QStringLiteral( "fld3" ) );
186 QCOMPARE( widget->asExpression(), QStringLiteral( "\"fld3\"" ) );
187 QCOMPARE( spy.count(), 5 );
188 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "fld3" ) );
189 QCOMPARE( spy2.count(), 5 );
190 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "fld3" ) );
191 QVERIFY( spy2.constLast().at( 1 ).toBool() );
192
193 // and back to null
194 widget->setField( QString() );
195 QVERIFY( widget->asExpression().isEmpty() );
196 QCOMPARE( spy.count(), 6 );
197 QVERIFY( spy.constLast().at( 0 ).toString().isEmpty() );
198 QCOMPARE( spy2.count(), 6 );
199 QVERIFY( spy2.constLast().at( 0 ).toString().isEmpty() );
200 QVERIFY( spy2.constLast().at( 1 ).toBool() );
201
202 // field name with space
203 widget->setField( QStringLiteral( "a space" ) );
204 QCOMPARE( widget->asExpression(), QStringLiteral( "\"a space\"" ) );
205 bool isExpression = true;
206 QCOMPARE( widget->currentField( &isExpression ), QStringLiteral( "a space" ) );
207 QVERIFY( !isExpression );
208 QCOMPARE( spy.count(), 7 );
209 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "a space" ) );
210 QCOMPARE( spy2.count(), 7 );
211 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "a space" ) );
212 QVERIFY( spy2.constLast().at( 1 ).toBool() );
213
214 widget->setField( QString() );
215 QVERIFY( widget->asExpression().isEmpty() );
216 widget->setExpression( QStringLiteral( "\"a space\"" ) );
217 QCOMPARE( widget->asExpression(), QStringLiteral( "\"a space\"" ) );
218 isExpression = true;
219 QCOMPARE( widget->currentField( &isExpression ), QStringLiteral( "a space" ) );
220 QVERIFY( !isExpression );
221 QCOMPARE( spy.count(), 9 );
222 QCOMPARE( spy.constLast().at( 0 ).toString(), QStringLiteral( "a space" ) );
223 QCOMPARE( spy2.count(), 9 );
224 QCOMPARE( spy2.constLast().at( 0 ).toString(), QStringLiteral( "a space" ) );
225 QVERIFY( spy2.constLast().at( 1 ).toBool() );
226
227 QgsProject::instance()->removeMapLayer( layer );
228 }
229
testIsValid()230 void TestQgsFieldExpressionWidget::testIsValid()
231 {
232 QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=fld:int&field=name%20with%20space:string" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
233 QgsProject::instance()->addMapLayer( layer );
234
235 std::unique_ptr< QgsFieldExpressionWidget > widget( new QgsFieldExpressionWidget() );
236 widget->setLayer( layer );
237
238 // also check the fieldChanged signal to ensure that the emitted bool isValid value is correct
239 QSignalSpy spy( widget.get(), SIGNAL( fieldChanged( QString, bool ) ) );
240
241 // check with simple field name set
242 bool isExpression = false;
243 bool isValid = false;
244 widget->setField( QStringLiteral( "fld" ) );
245 QCOMPARE( widget->currentField( &isExpression, &isValid ), QStringLiteral( "fld" ) );
246 QVERIFY( !isExpression );
247 QVERIFY( isValid );
248 QVERIFY( widget->isValidExpression() );
249 QCOMPARE( spy.count(), 1 );
250 QCOMPARE( spy.last().at( 0 ).toString(), QStringLiteral( "fld" ) );
251 QVERIFY( spy.last().at( 1 ).toBool() );
252
253
254 //check with complex field name set
255 widget->setField( QStringLiteral( "name with space" ) );
256 QCOMPARE( widget->currentField( &isExpression, &isValid ), QStringLiteral( "name with space" ) );
257 QVERIFY( !isExpression );
258 QVERIFY( isValid );
259 QVERIFY( !widget->isValidExpression() );
260 QCOMPARE( spy.count(), 2 );
261 QCOMPARE( spy.last().at( 0 ).toString(), QStringLiteral( "name with space" ) );
262 QVERIFY( spy.last().at( 1 ).toBool() );
263
264 //check with valid expression set
265 widget->setField( QStringLiteral( "2 * 4" ) );
266 QCOMPARE( widget->currentField( &isExpression, &isValid ), QStringLiteral( "2 * 4" ) );
267 QVERIFY( isExpression );
268 QVERIFY( isValid );
269 QVERIFY( widget->isValidExpression() );
270 QCOMPARE( spy.count(), 3 );
271 QCOMPARE( spy.last().at( 0 ).toString(), QStringLiteral( "2 * 4" ) );
272 QVERIFY( spy.last().at( 1 ).toBool() );
273
274 //check with invalid expression set
275 widget->setField( QStringLiteral( "2 *" ) );
276 QCOMPARE( widget->currentField( &isExpression, &isValid ), QStringLiteral( "2 *" ) );
277 QVERIFY( isExpression );
278 QVERIFY( !isValid );
279 QVERIFY( !widget->isValidExpression() );
280 QCOMPARE( spy.count(), 4 );
281 QCOMPARE( spy.last().at( 0 ).toString(), QStringLiteral( "2 *" ) );
282 QVERIFY( !spy.last().at( 1 ).toBool() );
283
284 QgsProject::instance()->removeMapLayer( layer );
285 }
286
testFilters()287 void TestQgsFieldExpressionWidget::testFilters()
288 {
289 QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=intfld:int&field=stringfld:string&field=string2fld:string&field=longfld:long&field=doublefld:double&field=datefld:date&field=timefld:time&field=datetimefld:datetime" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
290 QgsProject::instance()->addMapLayer( layer );
291
292 std::unique_ptr< QgsFieldExpressionWidget > widget( new QgsFieldExpressionWidget() );
293 widget->setLayer( layer );
294
295 QCOMPARE( widget->mCombo->count(), 8 );
296 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "intfld" ) );
297 QCOMPARE( widget->mCombo->itemText( 1 ), QStringLiteral( "stringfld" ) );
298 QCOMPARE( widget->mCombo->itemText( 2 ), QStringLiteral( "string2fld" ) );
299 QCOMPARE( widget->mCombo->itemText( 3 ), QStringLiteral( "longfld" ) );
300 QCOMPARE( widget->mCombo->itemText( 4 ), QStringLiteral( "doublefld" ) );
301 QCOMPARE( widget->mCombo->itemText( 5 ), QStringLiteral( "datefld" ) );
302 QCOMPARE( widget->mCombo->itemText( 6 ), QStringLiteral( "timefld" ) );
303 QCOMPARE( widget->mCombo->itemText( 7 ), QStringLiteral( "datetimefld" ) );
304
305 widget->setFilters( QgsFieldProxyModel::String );
306 QCOMPARE( widget->mCombo->count(), 2 );
307 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "stringfld" ) );
308 QCOMPARE( widget->mCombo->itemText( 1 ), QStringLiteral( "string2fld" ) );
309
310 widget->setFilters( QgsFieldProxyModel::Int );
311 QCOMPARE( widget->mCombo->count(), 1 );
312 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "intfld" ) );
313
314 widget->setFilters( QgsFieldProxyModel::LongLong );
315 QCOMPARE( widget->mCombo->count(), 1 );
316 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "longfld" ) );
317
318 widget->setFilters( QgsFieldProxyModel::Double );
319 QCOMPARE( widget->mCombo->count(), 1 );
320 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "doublefld" ) );
321
322 widget->setFilters( QgsFieldProxyModel::Numeric );
323 QCOMPARE( widget->mCombo->count(), 3 );
324 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "intfld" ) );
325 QCOMPARE( widget->mCombo->itemText( 1 ), QStringLiteral( "longfld" ) );
326 QCOMPARE( widget->mCombo->itemText( 2 ), QStringLiteral( "doublefld" ) );
327
328 widget->setFilters( QgsFieldProxyModel::Date );
329 QCOMPARE( widget->mCombo->count(), 2 );
330 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "datefld" ) );
331 QCOMPARE( widget->mCombo->itemText( 1 ), QStringLiteral( "datetimefld" ) );
332
333 widget->setFilters( QgsFieldProxyModel::Time );
334 QCOMPARE( widget->mCombo->count(), 1 );
335 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "timefld" ) );
336
337 widget->setFilters( QgsFieldProxyModel::DateTime );
338 QCOMPARE( widget->mCombo->count(), 1 );
339 QCOMPARE( widget->mCombo->itemText( 0 ), QStringLiteral( "datetimefld" ) );
340
341 QgsProject::instance()->removeMapLayer( layer );
342 }
343
setNull()344 void TestQgsFieldExpressionWidget::setNull()
345 {
346 // test that QgsFieldExpressionWidget can be set to an empty value
347 QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "point?field=fld:int&field=fld2:int&field=fld3:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
348 QgsProject::instance()->addMapLayer( layer );
349
350 std::unique_ptr< QgsFieldExpressionWidget > widget( new QgsFieldExpressionWidget() );
351 widget->setLayer( layer );
352
353 widget->setField( QString() );
354 QVERIFY( widget->currentField().isEmpty() );
355
356 widget->setField( QStringLiteral( "fld2" ) );
357 QCOMPARE( widget->currentField(), QStringLiteral( "fld2" ) );
358
359 widget->setField( QString() );
360 QVERIFY( widget->currentField().isEmpty() );
361
362 QgsProject::instance()->removeMapLayer( layer );
363 }
364
365 QGSTEST_MAIN( TestQgsFieldExpressionWidget )
366 #include "testqgsfieldexpressionwidget.moc"
367