1 /***************************************************************************
2      testqgsvectorfilewriter.cpp
3      --------------------------------------
4     Date                 : Frida  Nov 23  2007
5     Copyright            : (C) 2007 by Tim Sutton
6     Email                : tim@linfiniti.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 
17 #include <QApplication>
18 #include <QObject>
19 #include <QSplashScreen>
20 #include <QString>
21 #include <QStringList>
22 
23 #include "qgisapp.h"
24 #include "qgsapplication.h"
25 #include "qgsfeature.h"
26 #include "qgsfeaturestore.h"
27 #include "qgsfield.h"
28 #include "qgsclipboard.h"
29 #include "qgsvectorlayer.h"
30 #include "qgsgeometry.h"
31 #include "qgspoint.h"
32 #include "qgssettings.h"
33 
34 /**
35  * \ingroup UnitTests
36  * This is a unit test for the QgisApp clipboard.
37  */
38 class TestQgisAppClipboard : public QObject
39 {
40     Q_OBJECT
41 
42   public:
43     TestQgisAppClipboard();
44 
45   private slots:
46     void initTestCase();// will be called before the first testfunction is executed.
47     void cleanupTestCase();// will be called after the last testfunction was executed.
init()48     void init() {} // will be called before each testfunction is executed.
cleanup()49     void cleanup() {} // will be called after every testfunction.
50 
51     void copyPaste();
52     void copyToText();
53     void pasteWkt();
54     void pasteGeoJson();
55     void retrieveFields();
56     void clipboardLogic(); //test clipboard logic
57 
58   private:
59     QgisApp *mQgisApp = nullptr;
60     QString mTestDataDir;
61 };
62 
63 TestQgisAppClipboard::TestQgisAppClipboard() = default;
64 
65 //runs before all tests
initTestCase()66 void TestQgisAppClipboard::initTestCase()
67 {
68   // Set up the QgsSettings environment
69   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
70   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
71   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
72 
73   qDebug() << "TestQgisAppClipboard::initTestCase()";
74   // init QGIS's paths - true means that all path will be inited from prefix
75   QgsApplication::init();
76   QgsApplication::initQgis();
77   mTestDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
78   mQgisApp = new QgisApp();
79 }
80 
81 //runs after all tests
cleanupTestCase()82 void TestQgisAppClipboard::cleanupTestCase()
83 {
84   QgsApplication::exitQgis();
85 }
86 
copyPaste()87 void TestQgisAppClipboard::copyPaste()
88 {
89   qDebug() << "TestQgisAppClipboard::copyPaste()";
90 
91   QMap<QString, int> filesCounts;
92   filesCounts.insert( QStringLiteral( "points.shp" ), 17 );
93   filesCounts.insert( QStringLiteral( "lines.shp" ), 6 );
94   filesCounts.insert( QStringLiteral( "polys.shp" ), 10 );
95 
96   Q_FOREACH ( const QString &fileName, filesCounts.keys() )
97   {
98     // add vector layer
99     QString filePath = mTestDataDir + fileName;
100     qDebug() << "add vector layer: " << filePath;
101     QgsVectorLayer *inputLayer = mQgisApp->addVectorLayer( filePath, fileName, QStringLiteral( "ogr" ) );
102     QVERIFY( inputLayer->isValid() );
103 
104     // copy all features to clipboard
105     inputLayer->selectAll();
106     mQgisApp->copySelectionToClipboard( inputLayer );
107 
108     QgsFeatureList features = mQgisApp->clipboard()->copyOf();
109     qDebug() << features.size() << " features copied to clipboard";
110 
111     QVERIFY( features.size() == filesCounts.value( fileName ) );
112 
113     QgsVectorLayer *pastedLayer = mQgisApp->pasteAsNewMemoryVector( QStringLiteral( "pasted" ) );
114     QVERIFY( pastedLayer );
115     QVERIFY( pastedLayer->isValid() );
116     qDebug() << pastedLayer->featureCount() << " features in pasted layer";
117     QVERIFY( pastedLayer->featureCount() == filesCounts.value( fileName ) );
118   }
119 }
120 
copyToText()121 void TestQgisAppClipboard::copyToText()
122 {
123   //set clipboard to some QgsFeatures
124   QgsFields fields;
125   fields.append( QgsField( QStringLiteral( "int_field" ), QVariant::Int ) );
126   fields.append( QgsField( QStringLiteral( "string_field" ), QVariant::String ) );
127   QgsFeature feat( fields, 5 );
128   feat.setAttribute( QStringLiteral( "int_field" ), 9 );
129   feat.setAttribute( QStringLiteral( "string_field" ), "val" );
130   feat.setGeometry( QgsGeometry( new QgsPoint( 5, 6 ) ) );
131   QgsFeature feat2( fields, 6 );
132   feat2.setAttribute( QStringLiteral( "int_field" ), 19 );
133   feat2.setAttribute( QStringLiteral( "string_field" ), "val2" );
134   feat2.setGeometry( QgsGeometry( new QgsPoint( 7, 8 ) ) );
135   QgsFeatureStore feats;
136   feats.addFeature( feat );
137   feats.addFeature( feat2 );
138   feats.setFields( fields );
139   mQgisApp->clipboard()->replaceWithCopyOf( feats );
140 
141   // attributes only
142   QgsSettings settings;
143   settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesOnly );
144   QString result, resultHtml;
145   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
146   QCOMPARE( result, QString( "int_field\tstring_field\n9\tval\n19\tval2" ) );
147 
148   // attributes with WKT
149   settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKT );
150   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
151   QCOMPARE( result, QString( "wkt_geom\tint_field\tstring_field\nPoint (5 6)\t9\tval\nPoint (7 8)\t19\tval2" ) );
152 
153   // HTML test
154   mQgisApp->clipboard()->replaceWithCopyOf( feats );
155   result = mQgisApp->clipboard()->data( "text/html" );
156   QCOMPARE( result, QString( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"><html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"/></head><body><table border=\"1\"><tr><td>wkt_geom</td><td>int_field</td><td>string_field</td></tr><tr><td>Point (5 6)</td><td>9</td><td>val</td></tr><tr><td>Point (7 8)</td><td>19</td><td>val2</td></tr></table></body></html>" ) );
157 
158   // GeoJSON
159   settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::GeoJSON );
160   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
161   QString expected =  "{\"features\":[{\"geometry\":{\"coordinates\":[5.0,6.0],\"type\":\"Point\"},\"id\":5,"
162                       "\"properties\":{\"int_field\":9,\"string_field\":\"val\"},\"type\":\"Feature\"},"
163                       "{\"geometry\":{\"coordinates\":[7.0,8.0],\"type\":\"Point\"},\"id\":6,"
164                       "\"properties\":{\"int_field\":19,\"string_field\":\"val2\"},\"type\":\"Feature\"}],"
165                       "\"type\":\"FeatureCollection\"}";
166   QCOMPARE( result, expected );
167 
168   // test CRS is transformed correctly for GeoJSON
169 
170   QgsCoordinateReferenceSystem crs( QStringLiteral( "EPSG:3111" ) );
171   feats = QgsFeatureStore();
172   feats.setCrs( crs );
173   feat.setGeometry( QgsGeometry( new QgsPoint( 2502577, 2403869 ) ) );
174   feats.addFeature( feat );
175   feats.setFields( fields );
176   mQgisApp->clipboard()->replaceWithCopyOf( feats );
177 
178   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
179 
180   // just test coordinates as integers - that's enough to verify that reprojection has occurred
181   // and helps avoid rounding issues
182   QRegExp regex( "\\[([-\\d.]+),([-\\d.]+)\\]" );
183   ( void )regex.indexIn( result );
184   QStringList list = regex.capturedTexts();
185   QCOMPARE( list.count(), 3 );
186 
187   int x = std::round( list.at( 1 ).toDouble() );
188   int y = std::round( list.at( 2 ).toDouble() );
189 
190   QCOMPARE( x, 145 );
191   QCOMPARE( y, -38 );
192 
193   // test that multiline text fields are quoted to render correctly as csv files in WKT mode
194   QgsFeature feat3( fields, 7 );
195   feat3.setAttribute( QStringLiteral( "string_field" ), "Single line text" );
196   feat3.setAttribute( QStringLiteral( "int_field" ), 1 );
197   feat3.setGeometry( QgsGeometry( new QgsPoint( 5, 6 ) ) );
198   QgsFeature feat4( fields, 8 );
199   feat4.setAttribute( QStringLiteral( "string_field" ), "Unix Multiline \nText" );
200   feat4.setAttribute( QStringLiteral( "int_field" ), 2 );
201   feat4.setGeometry( QgsGeometry( new QgsPoint( 7, 8 ) ) );
202   QgsFeature feat5( fields, 9 );
203   feat5.setAttribute( QStringLiteral( "string_field" ), "Windows Multiline \r\nText" );
204   feat5.setAttribute( QStringLiteral( "int_field" ), 3 );
205   feat5.setGeometry( QgsGeometry( new QgsPoint( 9, 10 ) ) );
206   QgsFeatureStore featsML;
207   featsML.addFeature( feat3 );
208   featsML.addFeature( feat4 );
209   featsML.addFeature( feat5 );
210   featsML.setFields( fields );
211   mQgisApp->clipboard()->replaceWithCopyOf( featsML );
212 
213   // attributes only
214   settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesOnly );
215   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
216   qDebug() << result;
217   QCOMPARE( result, QString( "int_field\tstring_field\n1\tSingle line text\n2\t\"Unix Multiline \nText\"\n3\t\"Windows Multiline \r\nText\"" ) );
218 
219   // attributes with WKT
220   settings.setEnumValue( QStringLiteral( "/qgis/copyFeatureFormat" ), QgsClipboard::AttributesWithWKT );
221   mQgisApp->clipboard()->generateClipboardText( result, resultHtml );
222   QCOMPARE( result, QString( "wkt_geom\tint_field\tstring_field\nPoint (5 6)\t1\tSingle line text\nPoint (7 8)\t2\t\"Unix Multiline \nText\"\nPoint (9 10)\t3\t\"Windows Multiline \r\nText\"" ) );
223 }
224 
pasteWkt()225 void TestQgisAppClipboard::pasteWkt()
226 {
227   mQgisApp->clipboard()->setText( QStringLiteral( "POINT (125 10)\nPOINT (111 30)" ) );
228 
229   QgsFeatureList features = mQgisApp->clipboard()->copyOf();
230   QCOMPARE( features.length(), 2 );
231   QVERIFY( features.at( 0 ).hasGeometry() && !features.at( 0 ).geometry().isNull() );
232   QCOMPARE( features.at( 0 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
233   QgsGeometry featureGeom = features.at( 0 ).geometry();
234   const QgsPoint *point = dynamic_cast< const QgsPoint * >( featureGeom.constGet() );
235   QCOMPARE( point->x(), 125.0 );
236   QCOMPARE( point->y(), 10.0 );
237   QVERIFY( features.at( 1 ).hasGeometry() && !features.at( 1 ).geometry().isNull() );
238   QCOMPARE( features.at( 1 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
239   point = dynamic_cast< const QgsPoint * >( features.at( 1 ).geometry().constGet() );
240   QCOMPARE( point->x(), 111.0 );
241   QCOMPARE( point->y(), 30.0 );
242 
243   // be sure parsing does not consider attached parameters that
244   // can change geometryType as in https://github.com/qgis/QGIS/issues/24769
245   mQgisApp->clipboard()->setText( QStringLiteral( "POINT (111 30)\t GoodFieldValue\nPOINT (125 10)\t(WrongFieldValue)" ) );
246 
247   features = mQgisApp->clipboard()->copyOf();
248   QCOMPARE( features.length(), 2 );
249 
250   QVERIFY( features.at( 0 ).hasGeometry() && !features.at( 0 ).geometry().isNull() );
251   QCOMPARE( features.at( 0 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
252   featureGeom = features.at( 0 ).geometry();
253   point = dynamic_cast< const QgsPoint * >( featureGeom.constGet() );
254   QCOMPARE( point->x(), 111.0 );
255   QCOMPARE( point->y(), 30.0 );
256 
257   QVERIFY( features.at( 1 ).hasGeometry() && !features.at( 1 ).geometry().isNull() );
258   QCOMPARE( features.at( 1 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
259   point = dynamic_cast< const QgsPoint * >( features.at( 1 ).geometry().constGet() );
260   QCOMPARE( point->x(), 125.0 );
261   QCOMPARE( point->y(), 10.0 );
262 
263   //clipboard should support features without geometry
264   mQgisApp->clipboard()->setText( QStringLiteral( "\tMNL\t11\t282\tkm\t\t\t\n\tMNL\t11\t347.80000000000001\tkm\t\t\t" ) );
265   features = mQgisApp->clipboard()->copyOf();
266   QCOMPARE( features.length(), 2 );
267   QVERIFY( !features.at( 0 ).hasGeometry() );
268   QCOMPARE( features.at( 0 ).attributes().count(), 7 );
269   QCOMPARE( features.at( 0 ).attributes().at( 0 ).toString(), QStringLiteral( "MNL" ) );
270   QCOMPARE( features.at( 0 ).attributes().at( 1 ).toString(), QStringLiteral( "11" ) );
271   QCOMPARE( features.at( 0 ).attributes().at( 2 ).toString(), QStringLiteral( "282" ) );
272   QCOMPARE( features.at( 0 ).attributes().at( 3 ).toString(), QStringLiteral( "km" ) );
273   QVERIFY( features.at( 0 ).attributes().at( 4 ).toString().isEmpty() );
274   QVERIFY( features.at( 0 ).attributes().at( 5 ).toString().isEmpty() );
275   QVERIFY( features.at( 0 ).attributes().at( 6 ).toString().isEmpty() );
276   QVERIFY( !features.at( 1 ).hasGeometry() );
277   QCOMPARE( features.at( 1 ).attributes().count(), 7 );
278   QCOMPARE( features.at( 1 ).attributes().at( 0 ).toString(), QStringLiteral( "MNL" ) );
279   QCOMPARE( features.at( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "11" ) );
280   QCOMPARE( features.at( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "347.80000000000001" ) );
281   QCOMPARE( features.at( 1 ).attributes().at( 3 ).toString(), QStringLiteral( "km" ) );
282   QVERIFY( features.at( 1 ).attributes().at( 4 ).toString().isEmpty() );
283   QVERIFY( features.at( 1 ).attributes().at( 5 ).toString().isEmpty() );
284   QVERIFY( features.at( 1 ).attributes().at( 6 ).toString().isEmpty() );
285 
286   mQgisApp->clipboard()->setText( QStringLiteral( "wkt_geom\ta\tb\tc\n\tMNL\t11\t282\tkm\t\t\t\n\tMNL\t11\t347.80000000000001\tkm\t\t\t" ) );
287   features = mQgisApp->clipboard()->copyOf();
288   QCOMPARE( features.length(), 2 );
289   QVERIFY( !features.at( 0 ).hasGeometry() );
290   QCOMPARE( features.at( 0 ).fields().count(), 3 );
291   QCOMPARE( features.at( 0 ).fields().at( 0 ).name(), QStringLiteral( "a" ) );
292   QCOMPARE( features.at( 0 ).fields().at( 1 ).name(), QStringLiteral( "b" ) );
293   QCOMPARE( features.at( 0 ).fields().at( 2 ).name(), QStringLiteral( "c" ) );
294   QCOMPARE( features.at( 0 ).attributes().count(), 7 );
295   QCOMPARE( features.at( 0 ).attributes().at( 0 ).toString(), QStringLiteral( "MNL" ) );
296   QCOMPARE( features.at( 0 ).attributes().at( 1 ).toString(), QStringLiteral( "11" ) );
297   QCOMPARE( features.at( 0 ).attributes().at( 2 ).toString(), QStringLiteral( "282" ) );
298   QCOMPARE( features.at( 0 ).attributes().at( 3 ).toString(), QStringLiteral( "km" ) );
299   QVERIFY( features.at( 0 ).attributes().at( 4 ).toString().isEmpty() );
300   QVERIFY( features.at( 0 ).attributes().at( 5 ).toString().isEmpty() );
301   QVERIFY( features.at( 0 ).attributes().at( 6 ).toString().isEmpty() );
302   QVERIFY( !features.at( 1 ).hasGeometry() );
303   QCOMPARE( features.at( 1 ).attributes().count(), 7 );
304   QCOMPARE( features.at( 1 ).attributes().at( 0 ).toString(), QStringLiteral( "MNL" ) );
305   QCOMPARE( features.at( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "11" ) );
306   QCOMPARE( features.at( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "347.80000000000001" ) );
307   QCOMPARE( features.at( 1 ).attributes().at( 3 ).toString(), QStringLiteral( "km" ) );
308   QVERIFY( features.at( 1 ).attributes().at( 4 ).toString().isEmpty() );
309   QVERIFY( features.at( 1 ).attributes().at( 5 ).toString().isEmpty() );
310   QVERIFY( features.at( 1 ).attributes().at( 6 ).toString().isEmpty() );
311 
312   mQgisApp->clipboard()->setText( QStringLiteral( "wkt_geom\ta\tb\tc\nNULL\t1\tb\t2\nNULL\t3\tc3\t4\nPoint (5 4)\t2\tb2\t3" ) );
313   features = mQgisApp->clipboard()->copyOf();
314   QCOMPARE( features.length(), 3 );
315   QCOMPARE( features.at( 0 ).fields().count(), 3 );
316   QCOMPARE( features.at( 0 ).fields().at( 0 ).name(), QStringLiteral( "a" ) );
317   QCOMPARE( features.at( 0 ).fields().at( 1 ).name(), QStringLiteral( "b" ) );
318   QCOMPARE( features.at( 0 ).fields().at( 2 ).name(), QStringLiteral( "c" ) );
319   QVERIFY( !features.at( 0 ).hasGeometry() );
320   QCOMPARE( features.at( 0 ).attributes().count(), 3 );
321   QCOMPARE( features.at( 0 ).attributes().at( 0 ).toString(), QStringLiteral( "1" ) );
322   QCOMPARE( features.at( 0 ).attributes().at( 1 ).toString(), QStringLiteral( "b" ) );
323   QCOMPARE( features.at( 0 ).attributes().at( 2 ).toString(), QStringLiteral( "2" ) );
324   QVERIFY( !features.at( 1 ).hasGeometry() );
325   QCOMPARE( features.at( 1 ).attributes().count(), 3 );
326   QCOMPARE( features.at( 1 ).attributes().at( 0 ).toString(), QStringLiteral( "3" ) );
327   QCOMPARE( features.at( 1 ).attributes().at( 1 ).toString(), QStringLiteral( "c3" ) );
328   QCOMPARE( features.at( 1 ).attributes().at( 2 ).toString(), QStringLiteral( "4" ) );
329   QCOMPARE( features.at( 2 ).geometry().asWkt(), QStringLiteral( "Point (5 4)" ) );
330   QCOMPARE( features.at( 2 ).attributes().count(), 3 );
331   QCOMPARE( features.at( 2 ).attributes().at( 0 ).toString(), QStringLiteral( "2" ) );
332   QCOMPARE( features.at( 2 ).attributes().at( 1 ).toString(), QStringLiteral( "b2" ) );
333   QCOMPARE( features.at( 2 ).attributes().at( 2 ).toString(), QStringLiteral( "3" ) );
334 
335   // when a set of features is built outside of QGIS, last one might be terminated by newline
336   // https://github.com/qgis/QGIS/issues/33617
337   mQgisApp->clipboard()->setText( QStringLiteral( "POINT (125 10)\nPOINT (111 30)\n" ) );
338   features = mQgisApp->clipboard()->copyOf();
339   QCOMPARE( features.length(), 2 );
340   QVERIFY( features.at( 0 ).hasGeometry() && !features.at( 0 ).geometry().isNull() );
341   QCOMPARE( features.at( 0 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
342   featureGeom = features.at( 0 ).geometry();
343   point = dynamic_cast< const QgsPoint * >( featureGeom.constGet() );
344   QCOMPARE( point->x(), 125.0 );
345   QCOMPARE( point->y(), 10.0 );
346   QVERIFY( features.at( 1 ).hasGeometry() && !features.at( 1 ).geometry().isNull() );
347   QCOMPARE( features.at( 1 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
348   point = dynamic_cast< const QgsPoint * >( features.at( 1 ).geometry().constGet() );
349   QCOMPARE( point->x(), 111.0 );
350   QCOMPARE( point->y(), 30.0 );
351 
352   // on MS Windows, the <EOL> marker is CRLF
353   // https://github.com/qgis/QGIS/pull/33618#discussion_r363147854
354   mQgisApp->clipboard()->setText( QStringLiteral( "POINT (125 10)\r\nPOINT (111 30)\r\n" ) );
355   features = mQgisApp->clipboard()->copyOf();
356   QCOMPARE( features.length(), 2 );
357   QVERIFY( features.at( 0 ).hasGeometry() && !features.at( 0 ).geometry().isNull() );
358   QCOMPARE( features.at( 0 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
359   featureGeom = features.at( 0 ).geometry();
360   point = dynamic_cast< const QgsPoint * >( featureGeom.constGet() );
361   QCOMPARE( point->x(), 125.0 );
362   QCOMPARE( point->y(), 10.0 );
363   QVERIFY( features.at( 1 ).hasGeometry() && !features.at( 1 ).geometry().isNull() );
364   QCOMPARE( features.at( 1 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
365   point = dynamic_cast< const QgsPoint * >( features.at( 1 ).geometry().constGet() );
366   QCOMPARE( point->x(), 111.0 );
367   QCOMPARE( point->y(), 30.0 );
368 }
369 
pasteGeoJson()370 void TestQgisAppClipboard::pasteGeoJson()
371 {
372   QgsFields fields;
373   fields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
374   mQgisApp->clipboard()->setText( QStringLiteral( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\"}}" ) );
375 
376   QgsFeatureList features = mQgisApp->clipboard()->copyOf( fields );
377   QCOMPARE( features.length(), 1 );
378   QVERIFY( features.at( 0 ).hasGeometry() && !features.at( 0 ).geometry().isNull() );
379   QCOMPARE( features.at( 0 ).geometry().constGet()->wkbType(), QgsWkbTypes::Point );
380   QgsGeometry featureGeom = features.at( 0 ).geometry();
381   const QgsPoint *point = dynamic_cast< const QgsPoint * >( featureGeom.constGet() );
382   QCOMPARE( point->x(), 125.0 );
383   QCOMPARE( point->y(), 10.0 );
384   QCOMPARE( features.at( 0 ).attribute( "name" ).toString(), QString( "Dinagat Islands" ) );
385 }
386 
retrieveFields()387 void TestQgisAppClipboard::retrieveFields()
388 {
389   //empty string
390   mQgisApp->clipboard()->setText( QString() );
391 
392   QgsFields fields = mQgisApp->clipboard()->fields();
393   QCOMPARE( fields.count(), 0 );
394 
395   // bad string
396   mQgisApp->clipboard()->setText( QStringLiteral( "asdasdas" ) );
397   fields = mQgisApp->clipboard()->fields();
398   QCOMPARE( fields.count(), 0 );
399 
400   // geojson string
401   mQgisApp->clipboard()->setText( QStringLiteral( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\",\"height\":5.5}}" ) );
402   fields = mQgisApp->clipboard()->fields();
403   QCOMPARE( fields.count(), 2 );
404   QCOMPARE( fields.at( 0 ).name(), QString( "name" ) );
405   QCOMPARE( fields.at( 0 ).type(), QVariant::String );
406   QCOMPARE( fields.at( 1 ).name(), QString( "height" ) );
407   QCOMPARE( fields.at( 1 ).type(), QVariant::Double );
408 }
409 
clipboardLogic()410 void TestQgisAppClipboard::clipboardLogic()
411 {
412   //start by setting clipboard contents as text
413   mQgisApp->clipboard()->setText( QStringLiteral( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\"}}" ) );
414   QgsFields fields = mQgisApp->clipboard()->fields();
415   QCOMPARE( fields.count(), 1 );
416   QCOMPARE( fields.at( 0 ).name(), QString( "name" ) );
417   QCOMPARE( fields.at( 0 ).type(), QVariant::String );
418   QgsFeatureList features = mQgisApp->clipboard()->copyOf( mQgisApp->clipboard()->fields() );
419   QCOMPARE( features.length(), 1 );
420   QCOMPARE( features.at( 0 ).attribute( "name" ).toString(), QString( "Dinagat Islands" ) );
421 
422   //set clipboard to some QgsFeatures
423   fields = QgsFields();
424   fields.append( QgsField( QStringLiteral( "int_field" ), QVariant::Int ) );
425   fields.append( QgsField( QStringLiteral( "date_field" ), QVariant::Date ) );
426   QgsFeature feat( fields, 5 );
427   feat.setAttribute( QStringLiteral( "int_field" ), 9 );
428   feat.setAttribute( QStringLiteral( "date_field" ), QVariant( QDate( 2010, 9, 5 ) ) );
429   QgsFeature feat2( fields, 6 );
430   feat2.setAttribute( QStringLiteral( "int_field" ), 19 );
431   feat2.setAttribute( QStringLiteral( "date_field" ), QVariant( QDate( 2011, 9, 5 ) ) );
432   QgsFeatureStore feats;
433   feats.addFeature( feat );
434   feats.addFeature( feat2 );
435   feats.setFields( fields );
436   QgsCoordinateReferenceSystem crs( QStringLiteral( "EPSG:4326" ) );
437   feats.setCrs( crs );
438   mQgisApp->clipboard()->replaceWithCopyOf( feats );
439 
440   //test result
441   fields = mQgisApp->clipboard()->fields();
442   QCOMPARE( fields.count(), 2 );
443   QCOMPARE( fields.at( 0 ).name(), QString( "int_field" ) );
444   QCOMPARE( fields.at( 0 ).type(), QVariant::Int );
445   QCOMPARE( fields.at( 1 ).name(), QString( "date_field" ) );
446   QCOMPARE( fields.at( 1 ).type(), QVariant::Date );
447   features = mQgisApp->clipboard()->copyOf( mQgisApp->clipboard()->fields() );
448   QCOMPARE( features.length(), 2 );
449   QCOMPARE( features.at( 0 ).id(), 5LL );
450   QCOMPARE( features.at( 0 ).attribute( "int_field" ).toInt(), 9 );
451   QCOMPARE( features.at( 0 ).attribute( "date_field" ).toDate(), QDate( 2010, 9, 5 ) );
452   QCOMPARE( features.at( 1 ).id(), 6LL );
453   QCOMPARE( features.at( 1 ).attribute( "int_field" ).toInt(), 19 );
454   QCOMPARE( features.at( 1 ).attribute( "date_field" ).toDate(), QDate( 2011, 9, 5 ) );
455 
456   //replace with text again, make sure system clipboard is used rather than internal clipboard
457   mQgisApp->clipboard()->setText( QStringLiteral( "{\n\"type\": \"Feature\",\"geometry\": {\"type\": \"Point\",\"coordinates\": [125, 10]},\"properties\": {\"name\": \"Dinagat Islands\"}}" ) );
458   fields = mQgisApp->clipboard()->fields();
459   QCOMPARE( fields.count(), 1 );
460   QCOMPARE( fields.at( 0 ).name(), QString( "name" ) );
461   QCOMPARE( fields.at( 0 ).type(), QVariant::String );
462   features = mQgisApp->clipboard()->copyOf( mQgisApp->clipboard()->fields() );
463   QCOMPARE( features.length(), 1 );
464   QCOMPARE( features.at( 0 ).attribute( "name" ).toString(), QString( "Dinagat Islands" ) );
465 }
466 
467 QGSTEST_MAIN( TestQgisAppClipboard )
468 #include "testqgisappclipboard.moc"
469