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