1 /***************************************************************************
2   testqgsfeaturelistcombobox.cpp
3 
4  ---------------------
5  begin                : 3.10.2017
6  copyright            : (C) 2017 by Matthias Kuhn
7  email                : matthias@opengis.ch
8  ***************************************************************************
9  *                                                                         *
10  *   This program is free software; you can redistribute it and/or modify  *
11  *   it under the terms of the GNU General Public License as published by  *
12  *   the Free Software Foundation; either version 2 of the License, or     *
13  *   (at your option) any later version.                                   *
14  *                                                                         *
15  ***************************************************************************/
16 
17 #include "qgstest.h"
18 
19 #include "qgsapplication.h"
20 #include "qgsfeaturelistcombobox.h"
21 #include "qgsfilterlineedit.h"
22 #include "qgsvectorlayer.h"
23 #include "qgsfeaturefiltermodel.h"
24 #include "qgsgui.h"
25 
26 #include <memory>
27 
28 #include <QLineEdit>
29 #include <QSignalSpy>
30 
31 class QgsFilterLineEdit;
32 
33 class TestQgsFeatureListComboBox : public QObject
34 {
35     Q_OBJECT
36   public:
37     TestQgsFeatureListComboBox() = default;
38 
39   private slots:
40     void initTestCase(); // will be called before the first testfunction is executed.
41     void cleanupTestCase(); // will be called after the last testfunction was executed.
42     void init(); // will be called before each testfunction is executed.
43     void cleanup(); // will be called after every testfunction.
44 
45     void testSetGetLayer();
46     void testSetGetForeignKey();
47     void testMultipleForeignKeys();
48     void testAllowNull();
49     void testValuesAndSelection();
50     void testValuesAndSelection_data();
51     void nullRepresentation();
52     void testNotExistingYetFeature();
53     void testFeatureFurtherThanFetchLimit();
54 
55   private:
56 
57     std::unique_ptr<QgsVectorLayer> mLayer;
58 
59     friend class QgsFeatureListComboBox;
60 };
61 
initTestCase()62 void TestQgsFeatureListComboBox::initTestCase()
63 {
64   QgsApplication::init();
65   QgsApplication::initQgis();
66 
67   // Set up the QgsSettings environment
68   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
69   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
70   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST-FEATURELIST-COMBOBOX" ) );
71 
72 }
73 
cleanupTestCase()74 void TestQgsFeatureListComboBox::cleanupTestCase()
75 {
76   QgsApplication::exitQgis();
77 }
78 
init()79 void TestQgsFeatureListComboBox::init()
80 {
81   // create layer
82   mLayer.reset( new QgsVectorLayer( QStringLiteral( "LineString?field=pk:int&field=material:string&field=diameter:int&field=raccord:string" ), QStringLiteral( "vl2" ), QStringLiteral( "memory" ) ) );
83   mLayer->setDisplayExpression( QStringLiteral( "pk" ) );
84 
85   // add features
86   mLayer->startEditing();
87 
88   QgsFeature ft1( mLayer->fields() );
89   ft1.setAttribute( QStringLiteral( "pk" ), 10 );
90   ft1.setAttribute( QStringLiteral( "material" ), "iron" );
91   ft1.setAttribute( QStringLiteral( "diameter" ), 120 );
92   ft1.setAttribute( QStringLiteral( "raccord" ), "brides" );
93   mLayer->addFeature( ft1 );
94 
95   QgsFeature ft2( mLayer->fields() );
96   ft2.setAttribute( QStringLiteral( "pk" ), 11 );
97   ft2.setAttribute( QStringLiteral( "material" ), "iron" );
98   ft2.setAttribute( QStringLiteral( "diameter" ), 120 );
99   ft2.setAttribute( QStringLiteral( "raccord" ), "sleeve" );
100   mLayer->addFeature( ft2 );
101 
102   QgsFeature ft3( mLayer->fields() );
103   ft3.setAttribute( QStringLiteral( "pk" ), 12 );
104   ft3.setAttribute( QStringLiteral( "material" ), "steel" );
105   ft3.setAttribute( QStringLiteral( "diameter" ), 120 );
106   ft3.setAttribute( QStringLiteral( "raccord" ), "collar" );
107   mLayer->addFeature( ft3 );
108 
109   QgsFeatureList flist;
110   for ( int i = 13; i < 40; i++ )
111   {
112     QgsFeature f( mLayer->fields() );
113     f.setAttribute( QStringLiteral( "pk" ), i );
114     f.setAttribute( QStringLiteral( "material" ), QStringLiteral( "material_%1" ).arg( i ) );
115     f.setAttribute( QStringLiteral( "diameter" ), i );
116     f.setAttribute( QStringLiteral( "raccord" ), QStringLiteral( "raccord_%1" ).arg( i ) );
117     flist << f;
118   }
119   mLayer->addFeatures( flist );
120 
121   mLayer->commitChanges();
122 }
123 
cleanup()124 void TestQgsFeatureListComboBox::cleanup()
125 {
126 }
127 
testSetGetLayer()128 void TestQgsFeatureListComboBox::testSetGetLayer()
129 {
130   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
131 
132   QVERIFY( cb->sourceLayer() == nullptr );
133   cb->setSourceLayer( mLayer.get() );
134   QCOMPARE( cb->sourceLayer(), mLayer.get() );
135 }
136 
testSetGetForeignKey()137 void TestQgsFeatureListComboBox::testSetGetForeignKey()
138 {
139   QgsFeatureListComboBox cb;
140 
141   Q_NOWARN_DEPRECATED_PUSH
142   QVERIFY( cb.identifierValue().isNull() );
143 
144   cb.setSourceLayer( mLayer.get() );
145   cb.setDisplayExpression( "\"material\"" );
146   cb.setIdentifierField( "material" );
147 
148   QSignalSpy spy( &cb, &QgsFeatureListComboBox::identifierValueChanged );
149   QTest::keyClicks( cb.lineEdit(), "ro" );
150   QTest::keyClick( cb.lineEdit(), Qt::Key_Enter );
151 
152   spy.wait();
153 
154   QCOMPARE( cb.identifierValue().toString(), QStringLiteral( "iron" ) );
155 
156   Q_NOWARN_DEPRECATED_POP
157 }
158 
testMultipleForeignKeys()159 void TestQgsFeatureListComboBox::testMultipleForeignKeys()
160 {
161   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
162 
163   QgsApplication::setNullRepresentation( QStringLiteral( "nope" ) );
164 
165   QVERIFY( cb->identifierValues().isEmpty() );
166 
167   cb->setSourceLayer( mLayer.get() );
168   cb->setIdentifierFields( QStringList() << "material" << "diameter" << "raccord" );
169   cb->setDisplayExpression( "\"material\" || ' ' || \"diameter\" || ' ' || \"raccord\"" );
170   cb->setAllowNull( true );
171 
172   cb->setIdentifierValues( QVariantList() << "gold" << 777 << "rush" );
173   QCOMPARE( cb->identifierValues(), QVariantList() << "gold" << 777 << "rush" );
174 
175   cb->setIdentifierValuesToNull();
176   QCOMPARE( cb->identifierValues().count(), 3 );
177   QCOMPARE( cb->identifierValues(), QVariantList() << QVariant( QVariant::Int ) << QVariant( QVariant::Int ) << QVariant( QVariant::Int ) );
178 
179   cb->setIdentifierValues( QVariantList() << "silver" << 888 << "fish" );
180   QCOMPARE( cb->identifierValues(), QVariantList() << "silver" << 888 << "fish" );
181 
182   cb->setIdentifierValuesToNull();
183   QCOMPARE( cb->identifierValues().count(), 3 );
184   QCOMPARE( cb->identifierValues(), QVariantList() << QVariant( QVariant::Int ) << QVariant( QVariant::Int ) << QVariant( QVariant::Int ) );
185 
186   cb->setIdentifierFields( QStringList() << "material" << "raccord" );
187   cb->setDisplayExpression( "\"material\" || ' ' || \"raccord\"" );
188   cb->setAllowNull( true );
189 
190   cb->setIdentifierValues( QVariantList() << "gold" << "fish" );
191   QCOMPARE( cb->identifierValues().count(), 2 );
192   QCOMPARE( cb->identifierValues(), QVariantList() << "gold" << "fish" );
193 
194   cb->setIdentifierValuesToNull();
195   QCOMPARE( cb->identifierValues().count(), 2 );
196   QCOMPARE( cb->identifierValues(), QVariantList() << QVariant( QVariant::Int ) << QVariant( QVariant::Int ) );
197 }
198 
testAllowNull()199 void TestQgsFeatureListComboBox::testAllowNull()
200 {
201   //QVERIFY( false );
202   // Note to self: implement this!
203 }
204 
testValuesAndSelection_data()205 void TestQgsFeatureListComboBox::testValuesAndSelection_data()
206 {
207   QTest::addColumn<bool>( "allowNull" );
208 
209   QTest::newRow( "allowNull=true" ) << true;
210   QTest::newRow( "allowNull=false" ) << false;
211 }
212 
testValuesAndSelection()213 void TestQgsFeatureListComboBox::testValuesAndSelection()
214 {
215   QFETCH( bool, allowNull );
216 
217   QgsApplication::setNullRepresentation( QStringLiteral( "nope" ) );
218   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
219 
220   QSignalSpy spy( cb.get(), &QgsFeatureListComboBox::identifierValueChanged );
221 
222   cb->setSourceLayer( mLayer.get() );
223   cb->setAllowNull( allowNull );
224   cb->setIdentifierFields( {QStringLiteral( "raccord" )} );
225   cb->setDisplayExpression( QStringLiteral( "\"raccord\"" ) );
226 
227   //check if everything is fine:
228   spy.wait();
229   QCOMPARE( cb->currentIndex(), allowNull ? cb->nullIndex() : 0 );
230   QCOMPARE( cb->currentText(), allowNull ? QStringLiteral( "nope" ) : QStringLiteral( "brides" ) );
231 
232   //check if text correct, selected and if the clear button disappeared:
233   cb->mLineEdit->clearValue();
234   QCOMPARE( cb->currentIndex(), allowNull ? cb->nullIndex() : 0 );
235   QCOMPARE( cb->currentText(), allowNull ? QStringLiteral( "nope" ) : QString() );
236   QCOMPARE( cb->lineEdit()->selectedText(), allowNull ? QStringLiteral( "nope" ) : QString() );
237   QVERIFY( ! cb->mLineEdit->mClearAction );
238 
239   //check if text is selected after receiving focus
240   cb->setFocus();
241   QCOMPARE( cb->currentIndex(), allowNull ? cb->nullIndex() : 0 );
242   QCOMPARE( cb->currentText(), allowNull ? QStringLiteral( "nope" ) : QString() );
243   QCOMPARE( cb->lineEdit()->selectedText(), allowNull ? QStringLiteral( "nope" ) : QString() );
244   QVERIFY( ! cb->mLineEdit->mClearAction );
245 
246   //check with another entry, clear button needs to be there then:
247   QTest::keyClicks( cb.get(), QStringLiteral( "sleeve" ) );
248   spy.wait();
249   QCOMPARE( cb->currentText(), QStringLiteral( "sleeve" ) );
250   QVERIFY( cb->mLineEdit->mClearAction );
251 }
252 
nullRepresentation()253 void TestQgsFeatureListComboBox::nullRepresentation()
254 {
255   QgsApplication::setNullRepresentation( QStringLiteral( "nope" ) );
256   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
257 
258   QgsFeatureFilterModel *model = qobject_cast<QgsFeatureFilterModel *>( cb->model() );
259   QEventLoop loop;
260   connect( model, &QgsFeatureFilterModel::filterJobCompleted, &loop, &QEventLoop::quit );
261 
262   cb->setAllowNull( true );
263   cb->setSourceLayer( mLayer.get() );
264 
265   loop.exec();
266   QCOMPARE( cb->lineEdit()->text(), QStringLiteral( "nope" ) );
267   QCOMPARE( cb->nullIndex(), 0 );
268 }
269 
270 
testNotExistingYetFeature()271 void TestQgsFeatureListComboBox::testNotExistingYetFeature()
272 {
273   // test behavior when feature list combo box identifier values references a
274   // not existing yet feature (created but not saved for instance)
275 
276   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
277   QgsFeatureFilterModel *model = qobject_cast<QgsFeatureFilterModel *>( cb->model() );
278   QEventLoop loop;
279   connect( model, &QgsFeatureFilterModel::filterJobCompleted, &loop, &QEventLoop::quit );
280 
281   QgsApplication::setNullRepresentation( QStringLiteral( "nope" ) );
282 
283   QVERIFY( cb->identifierValues().isEmpty() );
284 
285   cb->setSourceLayer( mLayer.get() );
286   cb->setAllowNull( true );
287 
288   cb->setIdentifierValues( QVariantList() << 42 );
289 
290   loop.exec();
291   QCOMPARE( cb->currentText(), QStringLiteral( "(42)" ) );
292 }
293 
testFeatureFurtherThanFetchLimit()294 void TestQgsFeatureListComboBox::testFeatureFurtherThanFetchLimit()
295 {
296   const int fetchLimit = 20;
297   QVERIFY( fetchLimit < mLayer->featureCount() );
298   std::unique_ptr<QgsFeatureListComboBox> cb( new QgsFeatureListComboBox() );
299   QgsFeatureFilterModel *model = qobject_cast<QgsFeatureFilterModel *>( cb->model() );
300   QSignalSpy spy( cb.get(), &QgsFeatureListComboBox::identifierValueChanged );
301   model->setFetchLimit( 20 );
302   model->setAllowNull( false );
303   cb->setSourceLayer( mLayer.get() );
304   cb->setIdentifierFields( {QStringLiteral( "pk" )} );
305   spy.wait();
306   QCOMPARE( model->mEntries.count(), 20 );
307   for ( int i = 0; i < 20; i++ )
308     QCOMPARE( model->mEntries.at( i ).identifierFields.at( 0 ).toInt(), i + 10 );
309   cb->setIdentifierValues( {33} );
310   spy.wait();
311   QCOMPARE( cb->lineEdit()->text(), QStringLiteral( "33" ) );
312   QCOMPARE( model->mEntries.count(), 21 );
313   QCOMPARE( model->mEntries.at( 0 ).identifierFields.at( 0 ).toInt(), 33 );
314 }
315 
316 QGSTEST_MAIN( TestQgsFeatureListComboBox )
317 #include "testqgsfeaturelistcombobox.moc"
318