1 /***************************************************************************
2 testqgsapplocatorfilters.cpp
3 --------------------------
4 Date : 2018-02-24
5 Copyright : (C) 2018 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot 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 #include "qgstest.h"
16 #include "qgisapp.h"
17 #include "qgslocatorfilter.h"
18 #include "qgslocator.h"
19 #include "qgsprintlayout.h"
20 #include "qgslayoutmanager.h"
21 #include "locator/qgsinbuiltlocatorfilters.h"
22 #include <QSignalSpy>
23 #include <QClipboard>
24
25 /**
26 * \ingroup UnitTests
27 * This is a unit test for the field calculator
28 */
29 class TestQgsAppLocatorFilters : public QObject
30 {
31 Q_OBJECT
32
33 private slots:
34 void initTestCase();// will be called before the first testfunction is executed.
35 void cleanupTestCase();// will be called after the last testfunction was executed.
36 void testCalculator();
37 void testLayers();
38 void testLayouts();
39 void testSearchActiveLayer();
40 void testActiveLayerFieldRestriction();
41 void testActiveLayerCompletion();
42 void testSearchAllLayers();
43 void testSearchAllLayersPrioritizeExactMatch();
44 void testGoto();
45
46 private:
47 QgisApp *mQgisApp = nullptr;
48
49 QList< QgsLocatorResult > gatherResults( QgsLocatorFilter *filter, const QString &string, const QgsLocatorContext &context );
50 };
51
52 //runs before all tests
initTestCase()53 void TestQgsAppLocatorFilters::initTestCase()
54 {
55 // init QGIS's paths - true means that all path will be inited from prefix
56 QgsApplication::init();
57 QgsApplication::initQgis();
58 mQgisApp = new QgisApp();
59 }
60
61 //runs after all tests
cleanupTestCase()62 void TestQgsAppLocatorFilters::cleanupTestCase()
63 {
64 QgsApplication::exitQgis();
65 }
66
testCalculator()67 void TestQgsAppLocatorFilters::testCalculator()
68 {
69 QgsExpressionCalculatorLocatorFilter filter;
70
71 // valid expression
72 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "1+2" ), QgsLocatorContext() );
73 QCOMPARE( results.count(), 1 );
74 QCOMPARE( results.at( 0 ).userData.toInt(), 3 );
75
76 // trigger result
77 filter.triggerResult( results.at( 0 ) );
78 QCOMPARE( QApplication::clipboard()->text(), QStringLiteral( "3" ) );
79
80 // invalid expression
81 results = gatherResults( &filter, QStringLiteral( "1+" ), QgsLocatorContext() );
82 QVERIFY( results.empty() );
83 }
84
testLayers()85 void TestQgsAppLocatorFilters::testLayers()
86 {
87 QgsVectorLayer *l1 = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "aaaaa" ), QStringLiteral( "memory" ) );
88 QgsVectorLayer *l2 = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "abc" ), QStringLiteral( "memory" ) );
89 QgsVectorLayer *l3 = new QgsVectorLayer( QStringLiteral( "Point" ), QStringLiteral( "ccccc" ), QStringLiteral( "memory" ) );
90 QgsProject::instance()->addMapLayers( QList< QgsMapLayer *>() << l1 << l2 << l3 );
91
92 QgsLayerTreeLocatorFilter filter;
93
94 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "xxxxx" ), QgsLocatorContext() );
95 QCOMPARE( results.count(), 0 );
96
97 results = gatherResults( &filter, QStringLiteral( "aa" ), QgsLocatorContext() );
98 QCOMPARE( results.count(), 1 );
99 QCOMPARE( results.at( 0 ).userData.toString(), l1->id() );
100
101 results = gatherResults( &filter, QStringLiteral( "A" ), QgsLocatorContext() );
102 QCOMPARE( results.count(), 2 );
103 QCOMPARE( results.at( 0 ).userData.toString(), l1->id() );
104 QCOMPARE( results.at( 1 ).userData.toString(), l2->id() );
105
106 results = gatherResults( &filter, QString(), QgsLocatorContext() );
107 QCOMPARE( results.count(), 0 );
108
109 QgsLocatorContext context;
110 context.usingPrefix = true;
111 results = gatherResults( &filter, QString(), context );
112 QCOMPARE( results.count(), 3 );
113 QCOMPARE( results.at( 0 ).userData.toString(), l1->id() );
114 QCOMPARE( results.at( 1 ).userData.toString(), l2->id() );
115 QCOMPARE( results.at( 2 ).userData.toString(), l3->id() );
116 }
117
testLayouts()118 void TestQgsAppLocatorFilters::testLayouts()
119 {
120 QgsPrintLayout *pl1 = new QgsPrintLayout( QgsProject::instance() );
121 pl1->setName( QStringLiteral( "aaaaaa" ) );
122 QgsProject::instance()->layoutManager()->addLayout( pl1 );
123 QgsPrintLayout *pl2 = new QgsPrintLayout( QgsProject::instance() );
124 pl2->setName( QStringLiteral( "abc" ) );
125 QgsProject::instance()->layoutManager()->addLayout( pl2 );
126 QgsPrintLayout *pl3 = new QgsPrintLayout( QgsProject::instance() );
127 pl3->setName( QStringLiteral( "ccccc" ) );
128 QgsProject::instance()->layoutManager()->addLayout( pl3 );
129
130 QgsLayoutLocatorFilter filter;
131
132 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "xxxxx" ), QgsLocatorContext() );
133 QCOMPARE( results.count(), 0 );
134
135 results = gatherResults( &filter, QStringLiteral( "aa" ), QgsLocatorContext() );
136 QCOMPARE( results.count(), 1 );
137 QCOMPARE( results.at( 0 ).userData.toString(), pl1->name() );
138
139 results = gatherResults( &filter, QStringLiteral( "A" ), QgsLocatorContext() );
140 QCOMPARE( results.count(), 2 );
141 QCOMPARE( results.at( 0 ).userData.toString(), pl1->name() );
142 QCOMPARE( results.at( 1 ).userData.toString(), pl2->name() );
143
144 results = gatherResults( &filter, QString(), QgsLocatorContext() );
145 QCOMPARE( results.count(), 0 );
146
147 QgsLocatorContext context;
148 context.usingPrefix = true;
149 results = gatherResults( &filter, QString(), context );
150 QCOMPARE( results.count(), 3 );
151 QCOMPARE( results.at( 0 ).userData.toString(), pl1->name() );
152 QCOMPARE( results.at( 1 ).userData.toString(), pl2->name() );
153 QCOMPARE( results.at( 2 ).userData.toString(), pl3->name() );
154 }
155
testSearchActiveLayer()156 void TestQgsAppLocatorFilters::testSearchActiveLayer()
157 {
158 QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_integer:integer&field=my_double:double&key=pk" );
159 QgsVectorLayer *vl = new QgsVectorLayer( layerDef, QStringLiteral( "Layer" ), QStringLiteral( "memory" ) );
160 QgsProject::instance()->addMapLayer( vl );
161
162 QgsFeature f;
163 f.setAttributes( QVector<QVariant>() << 1001 << "A nice feature" << 1234567890 << 12345.6789 );
164 f.setGeometry( QgsGeometry::fromWkt( "Point (-71.123 78.23)" ) );
165 vl->dataProvider()->addFeature( f );
166 QgsFeature f2;
167 f2.setAttributes( QVector<QVariant>() << 100 << "@home" << 13579 << 13.57 );
168 f2.setGeometry( QgsGeometry::fromWkt( "Point (-71.223 78.33)" ) );
169 vl->dataProvider()->addFeature( f2 );
170
171 mQgisApp->setActiveLayer( vl );
172
173 QgsActiveLayerFeaturesLocatorFilter filter;
174 QgsLocatorContext context;
175
176 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "12345.6789" ), context );
177 QCOMPARE( results.count(), 1 );
178
179 results = gatherResults( &filter, QStringLiteral( "12345.67" ), context );
180 QCOMPARE( results.count(), 0 );
181
182 results = gatherResults( &filter, QStringLiteral( "1234567890" ), context );
183 QCOMPARE( results.count(), 1 );
184
185 results = gatherResults( &filter, QStringLiteral( "nice" ), context );
186 QCOMPARE( results.count(), 1 );
187
188 results = gatherResults( &filter, QStringLiteral( "@my_text nice" ), context );
189 QCOMPARE( results.count(), 1 );
190
191 results = gatherResults( &filter, QStringLiteral( "@my_integer nice" ), context );
192 QCOMPARE( results.count(), 0 );
193
194 results = gatherResults( &filter, QStringLiteral( "@unknown_field nice" ), context );
195 QCOMPARE( results.count(), 0 );
196
197 // check with display expression, feature should not be shown twice
198 vl->setDisplayExpression( QStringLiteral( "concat(\"my_text\", ' ', \"my_double\")" ) );
199 results = gatherResults( &filter, QStringLiteral( "nice" ), context );
200 QCOMPARE( results.count(), 1 );
201 results = gatherResults( &filter, QStringLiteral( "a feature" ), context );
202 QCOMPARE( results.count(), 1 );
203 results = gatherResults( &filter, QStringLiteral( "nice .678" ), context );
204 QCOMPARE( results.count(), 1 );
205
206 results = gatherResults( &filter, QStringLiteral( "@my_text @home" ), context );
207 QCOMPARE( results.count(), 1 );
208
209 QgsProject::instance()->removeAllMapLayers();
210 }
211
testActiveLayerFieldRestriction()212 void TestQgsAppLocatorFilters::testActiveLayerFieldRestriction()
213 {
214 bool isRestricting = false;
215
216 QString search = QStringLiteral( "@my_field search" );
217 QString restr = QgsActiveLayerFeaturesLocatorFilter::fieldRestriction( search, &isRestricting );
218 QVERIFY( isRestricting );
219 QCOMPARE( restr, QStringLiteral( "my_field" ) );
220 QCOMPARE( search, QStringLiteral( "search" ) );
221
222 search = QStringLiteral( "@home" );
223 restr = QgsActiveLayerFeaturesLocatorFilter::fieldRestriction( search, &isRestricting );
224 QVERIFY( isRestricting );
225 QCOMPARE( restr, QStringLiteral( "home" ) );
226 QCOMPARE( search, QStringLiteral( "" ) );
227
228 search = QStringLiteral( "@" );
229 restr = QgsActiveLayerFeaturesLocatorFilter::fieldRestriction( search, &isRestricting );
230 QVERIFY( isRestricting );
231 QCOMPARE( restr, QString() );
232 QCOMPARE( search, QString() );
233
234 search = QStringLiteral( "hello there" );
235 restr = QgsActiveLayerFeaturesLocatorFilter::fieldRestriction( search, &isRestricting );
236 QVERIFY( !isRestricting );
237 QVERIFY( restr.isNull() );
238 QCOMPARE( search, QStringLiteral( "hello there" ) );
239 }
240
testActiveLayerCompletion()241 void TestQgsAppLocatorFilters::testActiveLayerCompletion()
242 {
243 QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_integer:integer&field=my_double:double&key=pk" );
244 QgsVectorLayer *vl = new QgsVectorLayer( layerDef, QStringLiteral( "Layer" ), QStringLiteral( "memory" ) );
245 QgsProject::instance()->addMapLayer( vl );
246 mQgisApp->setActiveLayer( vl );
247
248 QgsFeedback f;
249 QgsActiveLayerFeaturesLocatorFilter filter;
250 QgsLocatorContext context;
251 context.usingPrefix = true;
252
253 QCOMPARE( filter.prepare( QStringLiteral( "" ), context ), QStringList( { "@pk ", "@my_text ", "@my_integer ", "@my_double " } ) );
254 QCOMPARE( filter.prepare( QStringLiteral( "@my_i" ), context ), QStringList( { "@my_integer " } ) );
255
256 QgsProject::instance()->removeAllMapLayers();
257 }
258
testSearchAllLayers()259 void TestQgsAppLocatorFilters::testSearchAllLayers()
260 {
261 QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_number:integer&key=pk" );
262 QgsVectorLayer *l1 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 1" ), QStringLiteral( "memory" ) );
263 QgsVectorLayer *l2 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 2" ), QStringLiteral( "memory" ) );
264
265 QgsProject::instance()->addMapLayers( QList< QgsMapLayer *>() << l1 << l2 );
266
267 QgsFeature f1;
268 f1.setAttributes( QVector<QVariant>() << 1001 << "A nice feature" << 6789 );
269 f1.setGeometry( QgsGeometry::fromWkt( "Point (-71.123 78.23)" ) );
270 QgsFeature f2;
271 f2.setAttributes( QVector<QVariant>() << 1002 << "Something crazy" << 2 );
272 f2.setGeometry( QgsGeometry::fromWkt( "Point (-72.123 78.23)" ) );
273 QgsFeature f3;
274 f3.setAttributes( QVector<QVariant>() << 2001 << "Another feature" << 6789 );
275 f3.setGeometry( QgsGeometry::fromWkt( "Point (-73.123 78.23)" ) );
276
277 l1->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 );
278 l2->dataProvider()->addFeatures( QgsFeatureList() << f3 );
279
280 QgsAllLayersFeaturesLocatorFilter filter;
281 QgsLocatorContext context;
282
283 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "100" ), context );
284
285 l1->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );
286 l2->setDisplayExpression( QStringLiteral( "\"my_text\" || ' is ' || \"my_number\"" ) );
287
288 results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
289 QCOMPARE( results.count(), 2 );
290
291 l2->setFlags( l2->flags() & ~QgsMapLayer::Searchable );
292
293 results = gatherResults( &filter, QStringLiteral( "feature is 6789" ), context );
294 QCOMPARE( results.count(), 1 );
295
296 QgsProject::instance()->removeAllMapLayers();
297 }
298
testSearchAllLayersPrioritizeExactMatch()299 void TestQgsAppLocatorFilters::testSearchAllLayersPrioritizeExactMatch()
300 {
301 QString layerDef = QStringLiteral( "Point?crs=epsg:4326&field=pk:integer&field=my_text:string&field=my_number:integer&key=pk" );
302 QgsVectorLayer *l1 = new QgsVectorLayer( layerDef, QStringLiteral( "Layer 1" ), QStringLiteral( "memory" ) );
303
304 QgsProject::instance()->addMapLayers( QList< QgsMapLayer *>() << l1 );
305
306 QgsFeature f1;
307 f1.setAttributes( QVector<QVariant>() << 100 << "A nice feature" << 100 );
308 f1.setGeometry( QgsGeometry::fromWkt( "Point (-71.123 78.23)" ) );
309 QgsFeature f2;
310 f2.setAttributes( QVector<QVariant>() << 101 << "Something crazy" << 3 );
311 f2.setGeometry( QgsGeometry::fromWkt( "Point (-72.123 78.23)" ) );
312 QgsFeature f3;
313 f3.setAttributes( QVector<QVariant>() << 102 << "Another feature" << 1 );
314 f3.setGeometry( QgsGeometry::fromWkt( "Point (-73.123 78.23)" ) );
315
316 l1->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 );
317
318 QgsAllLayersFeaturesLocatorFilter filter;
319 QgsLocatorContext context;
320 context.usingPrefix = true; // Searching for short strings is only available with prefix
321
322 l1->setDisplayExpression( QStringLiteral( "\"my_number\"" ) );
323
324 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "1" ), context );
325 QCOMPARE( results.count(), 2 );
326 QCOMPARE( results.first().displayString, QStringLiteral( "1" ) );
327 QCOMPARE( results.last().displayString, QStringLiteral( "100" ) );
328
329 QgsProject::instance()->removeAllMapLayers();
330 }
331
gatherResults(QgsLocatorFilter * filter,const QString & string,const QgsLocatorContext & context)332 QList<QgsLocatorResult> TestQgsAppLocatorFilters::gatherResults( QgsLocatorFilter *filter, const QString &string, const QgsLocatorContext &context )
333 {
334 QSignalSpy spy( filter, &QgsLocatorFilter::resultFetched );
335 QgsFeedback f;
336 filter->prepare( string, context );
337 filter->fetchResults( string, context, &f );
338
339 QList< QgsLocatorResult > results;
340 for ( int i = 0; i < spy.count(); ++ i )
341 {
342 QVariant v = spy.at( i ).at( 0 );
343 QgsLocatorResult result = v.value<QgsLocatorResult>();
344 results.append( result );
345 }
346 return results;
347 }
348
testGoto()349 void TestQgsAppLocatorFilters::testGoto()
350 {
351 QgsGotoLocatorFilter filter;
352
353 // simple goto
354 QList< QgsLocatorResult > results = gatherResults( &filter, QStringLiteral( "4 5" ), QgsLocatorContext() );
355 QCOMPARE( results.count(), 2 );
356 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 4 5 (Map CRS, )" ) );
357 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 4, 5 ) );
358 QCOMPARE( results.at( 1 ).displayString, QObject::tr( "Go to 4° 5° (EPSG:4326 - WGS 84)" ) );
359 QCOMPARE( results.at( 1 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 4, 5 ) );
360
361 // locale-specific goto
362 results = gatherResults( &filter, QStringLiteral( "1,234.56 789.012" ), QgsLocatorContext() );
363 QCOMPARE( results.count(), 1 );
364 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 1,234.56 789.012 (Map CRS, )" ) );
365 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 1234.56, 789.012 ) );
366
367 // degree/minuste/second coordinates goto
368 // easting northing
369 results = gatherResults( &filter, QStringLiteral( "40deg 1' 0\" E 11deg 55' 0\" S" ), QgsLocatorContext() );
370 QCOMPARE( results.count(), 1 );
371 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 40.01666667° -11.91666667° (EPSG:4326 - WGS 84)" ) );
372 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 40.0166666667, -11.9166666667 ) );
373
374 // northing easting
375 results = gatherResults( &filter, QStringLiteral( "14°49′48″N 01°48′45″E" ), QgsLocatorContext() );
376 QCOMPARE( results.count(), 1 );
377 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 1.8125° 14.83° (EPSG:4326 - WGS 84)" ) );
378 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 1.8125, 14.83 ) );
379
380 // northing, esting (comma separated)
381 results = gatherResults( &filter, QStringLiteral( "14°49′48″N, 01°48′45″E" ), QgsLocatorContext() );
382 QCOMPARE( results.count(), 1 );
383 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 1.8125° 14.83° (EPSG:4326 - WGS 84)" ) );
384 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 1.8125, 14.83 ) );
385
386 // OSM/Leaflet/OpenLayers
387 results = gatherResults( &filter, QStringLiteral( "https://www.openstreetmap.org/#map=15/44.5546/6.4936" ), QgsLocatorContext() );
388 QCOMPARE( results.count(), 1 );
389 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 6.4936° 44.5546° at scale 1:22569 (EPSG:4326 - WGS 84)" ) );
390 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 6.4936, 44.5546 ) );
391 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "scale" )].toDouble(), 22569.0 );
392
393 // Google Maps
394 results = gatherResults( &filter, QStringLiteral( "https://www.google.com/maps/@44.5546,6.4936,15.25z" ), QgsLocatorContext() );
395 QCOMPARE( results.count(), 1 );
396 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 6.4936° 44.5546° at scale 1:22569 (EPSG:4326 - WGS 84)" ) );
397 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 6.4936, 44.5546 ) );
398 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "scale" )].toDouble(), 22569.0 );
399
400 results = gatherResults( &filter, QStringLiteral( "https://www.google.com/maps/@7.8750,81.0149,574195m/data=!3m1!1e3" ), QgsLocatorContext() );
401 QCOMPARE( results.count(), 1 );
402 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 81.0149, 7.8750 ) );
403
404 results = gatherResults( &filter, QStringLiteral( "https://www.google.com/maps/@27.7132,85.3288,3a,75y,278.89h,90t/data=!3m8!1e1!3m6!1sAF1QipMrXuXozGc9x9bxx5uPl_3ys4H-rNVqMLr6EYLA!2e10!3e11!6shttps:%2F%2Flh5.googleusercontent.com%2Fp%2FAF1QipMrXuXozGc9x9bxx5uPl_3ys4H-rNVqMLr6EYLA%3Dw203-h100-k-no-pi2.869903-ya293.58762-ro-1.9255565-fo100!7i3840!8i1920" ), QgsLocatorContext() );
405 QCOMPARE( results.count(), 1 );
406 QCOMPARE( results.at( 0 ).displayString, QObject::tr( "Go to 85.3288° 27.7132° at scale 1:282 (EPSG:4326 - WGS 84)" ) );
407 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "point" )].value<QgsPointXY>(), QgsPointXY( 85.3288, 27.7132 ) );
408 QCOMPARE( results.at( 0 ).userData.toMap()[QStringLiteral( "scale" )].toDouble(), 282.0 );
409 }
410
411 QGSTEST_MAIN( TestQgsAppLocatorFilters )
412 #include "testqgsapplocatorfilters.moc"
413