1 /***************************************************************************
2     testqgsgpsinformationwidget.cpp
3      --------------------------
4     Date                 : 2019-06-19
5     Copyright            : (C) 2019 by Alessandro Pasotti
6     Email                : elpaso at itopen dot it
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 <QTimeZone>
17 
18 #include "qgisapp.h"
19 #include "qgsapplication.h"
20 #include "qgsvectorlayer.h"
21 #include "qgsproject.h"
22 #include "qgsmapcanvas.h"
23 #include "qgssettingsregistrycore.h"
24 #include "gps/qgsgpsinformationwidget.h"
25 #include "nmeatime.h"
26 
27 /**
28  * \ingroup UnitTests
29  * This is a unit test for the GPS information widget
30  */
31 class TestQgsGpsInformationWidget : public QObject
32 {
33     Q_OBJECT
34   public:
35     TestQgsGpsInformationWidget();
36 
37   private slots:
38     void initTestCase();// will be called before the first testfunction is executed.
39     void cleanupTestCase();// will be called after the last testfunction was executed.
init()40     void init() {} // will be called before each testfunction is executed.
cleanup()41     void cleanup() {} // will be called after every testfunction.
42     void testGuiSignals();
43     void testStorePreferredFields();
44     void testTimestamp();
45     void testTimestampWrite();
46     void testMultiPartLayers();
47 
48   private:
49     std::unique_ptr<QgsGpsInformationWidget> prepareWidget();
50     QDateTime _testWrite( QgsVectorLayer *vlayer, QgsGpsInformationWidget *widget, const QString &fieldName, Qt::TimeSpec timeSpec, bool commit = false );
51     QgsVectorLayer *tempLayer = nullptr;
52     QgsVectorLayer *tempLayerString = nullptr;
53     QgsVectorLayer *tempLayerDateTime = nullptr;
54     QgsVectorLayer *tempLayerLineString = nullptr;
55     QgsVectorLayer *tempGpkgLayerPointString = nullptr;
56     QgisApp *mQgisApp = nullptr;
57 };
58 
59 TestQgsGpsInformationWidget::TestQgsGpsInformationWidget() = default;
60 
61 //runs before all tests
initTestCase()62 void TestQgsGpsInformationWidget::initTestCase()
63 {
64   // setup the test QSettings environment
65 
66   QgsApplication::init();
67   QgsApplication::initQgis();
68   QgsApplication::showSettings();
69 
70   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
71   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
72   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
73 
74   mQgisApp = new QgisApp();
75 
76 
77   tempLayer = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=intf:int" ),
78                                   QStringLiteral( "vl1" ),
79                                   QStringLiteral( "memory" ) );
80   tempLayerString = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=stringf:string&field=intf:int" ),
81                                         QStringLiteral( "vl2" ),
82                                         QStringLiteral( "memory" ) );
83   tempLayerDateTime = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=datetimef:datetime&field=intf:int" ),
84                                           QStringLiteral( "vl3" ),
85                                           QStringLiteral( "memory" ) );
86   tempLayerLineString = new QgsVectorLayer( QStringLiteral( "Linestring?crs=epsg:4326&field=intf:int&field=stringf:string" ),
87       QStringLiteral( "vl4" ),
88       QStringLiteral( "memory" ) );
89 
90   QgsSettingsRegistryCore::settingsDigitizingDisableEnterAttributeValuesDialog.setValue( true );
91 
92   const QString tempPath = QDir::tempPath() + QStringLiteral( "/gps_timestamp.gpkg" );
93   QFile::copy( TEST_DATA_DIR + QStringLiteral( "/gps_timestamp.gpkg" ), tempPath );
94   tempGpkgLayerPointString = new QgsVectorLayer( QStringLiteral( "%1|layername=points" ).arg( tempPath ),
95       QStringLiteral( "vl4" ) );
96   Q_ASSERT( tempGpkgLayerPointString->isValid() );
97   Q_ASSERT( tempLayer->isValid() );
98   Q_ASSERT( tempLayerString->isValid() );
99   Q_ASSERT( tempLayerDateTime->isValid() );
100   Q_ASSERT( tempLayerLineString->isValid() );
101   QgsProject::instance()->addMapLayers( { tempLayer, tempLayerString, tempLayerDateTime, tempGpkgLayerPointString, tempLayerLineString } );
102 }
103 
104 //runs after all tests
cleanupTestCase()105 void TestQgsGpsInformationWidget::cleanupTestCase()
106 {
107   QgsApplication::exitQgis();
108 }
109 
110 
prepareWidget()111 std::unique_ptr<QgsGpsInformationWidget> TestQgsGpsInformationWidget::prepareWidget()
112 {
113   QgsMapCanvas *canvas = mQgisApp->mapCanvas();
114   std::unique_ptr<QgsGpsInformationWidget> widget = std::make_unique<QgsGpsInformationWidget>( canvas );
115   // Widget config and input values
116   // 2019/06/19 12:27:34.543[UTC]
117   widget->mLastNmeaTime = { 119, 5, 19, 12, 27, 34, 543 };
118   canvas->setCurrentLayer( tempLayerString );
119   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "stringf" ) ) );
120   canvas->setCurrentLayer( tempLayerDateTime );
121   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "datetimef" ) ) );
122   canvas->setCurrentLayer( tempLayerLineString );
123   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "stringf" ) ) );
124   canvas->setCurrentLayer( tempGpkgLayerPointString );
125   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "datetimef" ) ) );
126 
127   widget->mCboTimeZones->setCurrentIndex( widget->mCboTimeZones->findText( QStringLiteral( "Asia/Colombo" ) ) );
128   widget->mCbxLeapSeconds->setChecked( false );
129   widget->mLeapSeconds->setValue( 7 );
130   return widget;
131 }
132 
_testWrite(QgsVectorLayer * vlayer,QgsGpsInformationWidget * widget,const QString & fieldName,Qt::TimeSpec timeSpec,bool commit)133 QDateTime TestQgsGpsInformationWidget::_testWrite( QgsVectorLayer *vlayer, QgsGpsInformationWidget *widget, const QString &fieldName, Qt::TimeSpec timeSpec, bool commit )
134 {
135   widget->mMapCanvas->setCurrentLayer( vlayer );
136   vlayer->startEditing();
137   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( fieldName ) );
138   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( timeSpec ) );
139   widget->mBtnCloseFeature_clicked();
140   const auto fids { vlayer->allFeatureIds() };
141   const auto fid { std::min_element( fids.begin(), fids.end() ) };
142   const QgsFeature f { vlayer->getFeature( *fid ) };
143   if ( commit )
144     vlayer->commitChanges();
145   else
146     vlayer->rollBack();
147   return f.attribute( fieldName ).toDateTime();
148 }
149 
testGuiSignals()150 void TestQgsGpsInformationWidget::testGuiSignals()
151 {
152   QgsMapCanvas *canvas = mQgisApp->mapCanvas();
153   std::unique_ptr<QgsGpsInformationWidget> widget = prepareWidget();
154   canvas->setCurrentLayer( tempLayer );
155   QVERIFY( ! widget->mGboxTimestamp->isEnabled() );
156 
157   canvas->setCurrentLayer( tempLayerString );
158   QVERIFY( widget->mGboxTimestamp->isEnabled() );
159   QVERIFY( widget->mCboTimestampField->findText( QStringLiteral( "stringf" ) ) != -1 );
160   QVERIFY( widget->mCboTimestampField->findText( QStringLiteral( "intf" ) ) == -1 );
161 
162   canvas->setCurrentLayer( tempLayerDateTime );
163   QVERIFY( widget->mGboxTimestamp->isEnabled() );
164   QVERIFY( widget->mCboTimestampField->findText( QStringLiteral( "datetimef" ) ) != -1 );
165   QVERIFY( widget->mCboTimestampField->findText( QStringLiteral( "intf" ) ) == -1 );
166 
167   // Check tz combo
168   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::UTC ) );
169   QVERIFY( ! widget->mCboTimeZones->isEnabled() );
170   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::LocalTime ) );
171   QVERIFY( ! widget->mCboTimeZones->isEnabled() );
172   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::TimeZone ) );
173   QVERIFY( widget->mCboTimeZones->isEnabled() );
174 
175   canvas->setCurrentLayer( tempLayer );
176   QVERIFY( ! widget->mGboxTimestamp->isEnabled() );
177 }
178 
testStorePreferredFields()179 void TestQgsGpsInformationWidget::testStorePreferredFields()
180 {
181   std::unique_ptr<QgsGpsInformationWidget> widget = prepareWidget();
182   QgsMapCanvas *canvas = mQgisApp->mapCanvas();
183   canvas->setCurrentLayer( tempLayerDateTime );
184   int fieldIdx = tempLayerDateTime->fields().indexOf( QLatin1String( "datetimef" ) );
185   QVERIFY( fieldIdx != -1 );
186   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "datetimef" ) ) );
187 
188   canvas->setCurrentLayer( tempLayerString );
189   fieldIdx = tempLayerString->fields().indexOf( QLatin1String( "stringf" ) );
190   QVERIFY( fieldIdx != -1 );
191   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "stringf" ) ) );
192 
193   canvas->setCurrentLayer( tempLayer );
194   QVERIFY( widget->mPreferredTimestampFields.contains( tempLayerDateTime->id() ) );
195   QCOMPARE( widget->mPreferredTimestampFields[ tempLayerString->id() ], QStringLiteral( "stringf" ) );
196   QVERIFY( widget->mPreferredTimestampFields.contains( tempLayerDateTime->id() ) );
197   QCOMPARE( widget->mPreferredTimestampFields[ tempLayerDateTime->id() ], QStringLiteral( "datetimef" ) );
198 }
199 
testTimestamp()200 void TestQgsGpsInformationWidget::testTimestamp()
201 {
202   std::unique_ptr<QgsGpsInformationWidget> widget = prepareWidget();
203   QgsMapCanvas *canvas = mQgisApp->mapCanvas();
204 
205   QDateTime dateTime( QDate( 2019, 6, 19 ), QTime( 12, 27, 34, 543 ) );
206   dateTime.setTimeSpec( Qt::TimeSpec::UTC );
207   const QDateTime tzTime( dateTime.toTimeZone( QTimeZone( QStringLiteral( "Asia/Colombo" ).toUtf8() ) ) );
208   const QDateTime localTime( dateTime.toLocalTime() );
209 
210   ///////////////////////////////////////////
211   // Test datetime layer
212   canvas->setCurrentLayer( tempLayerDateTime );
213 
214   int fieldIdx { tempLayerDateTime->fields().indexOf( QLatin1String( "datetimef" ) ) };
215   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "datetimef" ) ) );
216   QVERIFY( fieldIdx != -1 );
217   // UTC
218   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::UTC ) );
219   QVariant dt = widget->timestamp( tempLayerDateTime, fieldIdx );
220   QCOMPARE( dt.toDateTime(), dateTime );
221 
222   // Local time
223   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::LocalTime ) );
224   dt = widget->timestamp( tempLayerDateTime, fieldIdx );
225   QCOMPARE( dt.toDateTime(), dateTime.toLocalTime() );
226 
227   // Leap seconds
228   widget->mCbxLeapSeconds->setChecked( true );
229   dt = widget->timestamp( tempLayerDateTime, fieldIdx );
230   QCOMPARE( dt.toDateTime(), dateTime.addSecs( 7 ) );
231   widget->mCbxLeapSeconds->setChecked( false );
232 
233   ///////////////////////////////////////////
234   // Test string
235   canvas->setCurrentLayer( tempLayerString );
236   fieldIdx = tempLayerString->fields().indexOf( QLatin1String( "stringf" ) );
237   widget->mCboTimestampField->setCurrentIndex( widget->mCboTimestampField->findText( QStringLiteral( "stringf" ) ) );
238 
239   // UTC
240   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::UTC ) );
241   dt = widget->timestamp( tempLayerString, fieldIdx );
242   QCOMPARE( dt.toString(), dateTime.toString( Qt::DateFormat::ISODate ) );
243 
244   // Local Time (not very robust because we cannot change the system timezone and it may be GMT)
245   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::LocalTime ) );
246   dt = widget->timestamp( tempLayerString, fieldIdx );
247   QCOMPARE( dt.toString(), localTime.toString( Qt::DateFormat::ISODate ) );
248 
249   // Timezone
250   widget->mCboTimestampFormat->setCurrentIndex( widget->mCboTimestampFormat->findData( Qt::TimeSpec::TimeZone ) );
251   widget->mCboTimeZones->setCurrentIndex( widget->mCboTimeZones->findText( QStringLiteral( "Asia/Colombo" ) ) ) ;
252   dt = widget->timestamp( tempLayerString, fieldIdx );
253   QCOMPARE( dt.toString(), tzTime.toString( Qt::DateFormat::ISODate ) );
254 
255 }
256 
testTimestampWrite()257 void TestQgsGpsInformationWidget::testTimestampWrite()
258 {
259   std::unique_ptr<QgsGpsInformationWidget> widget = prepareWidget();
260   QDateTime dateTime( QDate( 2019, 6, 19 ), QTime( 12, 27, 34, 543 ) );
261   dateTime.setTimeSpec( Qt::TimeSpec::UTC );
262   const QDateTime tzTime( dateTime.toTimeZone( QTimeZone( QStringLiteral( "Asia/Colombo" ).toUtf8() ) ) );
263   const QDateTime localTime( dateTime.toLocalTime() );
264 
265   // Test write on datetime field
266   QCOMPARE( _testWrite( tempLayerDateTime, widget.get(), QStringLiteral( "datetimef" ),  Qt::TimeSpec::UTC ), dateTime );
267   QCOMPARE( _testWrite( tempLayerDateTime, widget.get(), QStringLiteral( "datetimef" ),  Qt::TimeSpec::LocalTime ), localTime );
268   QCOMPARE( _testWrite( tempLayerDateTime, widget.get(), QStringLiteral( "datetimef" ),  Qt::TimeSpec::TimeZone ),  tzTime );
269 
270   // Test write on string field
271   QCOMPARE( _testWrite( tempLayerString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::UTC ).toString( Qt::DateFormat::ISODate ), dateTime.toString( Qt::DateFormat::ISODate ) );
272   QCOMPARE( _testWrite( tempLayerString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::LocalTime ).toString( Qt::DateFormat::ISODate ), localTime.toString( Qt::DateFormat::ISODate ) );
273   QCOMPARE( _testWrite( tempLayerString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::TimeZone ).toString( Qt::DateFormat::ISODate ),  tzTime.toString( Qt::DateFormat::ISODate ) );
274 
275   // Test write on line string field
276   widget->mCaptureList.push_back( QgsPoint( 1, 2 ) );
277   widget->mCaptureList.push_back( QgsPoint( 3, 4 ) );
278   QCOMPARE( _testWrite( tempLayerLineString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::UTC ).toString( Qt::DateFormat::ISODate ), dateTime.toString( Qt::DateFormat::ISODate ) );
279   widget->mCaptureList.push_back( QgsPoint( 1, 2 ) );
280   widget->mCaptureList.push_back( QgsPoint( 3, 4 ) );
281   QCOMPARE( _testWrite( tempLayerLineString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::LocalTime ).toString( Qt::DateFormat::ISODate ), localTime.toString( Qt::DateFormat::ISODate ) );
282   widget->mCaptureList.push_back( QgsPoint( 1, 2 ) );
283   widget->mCaptureList.push_back( QgsPoint( 3, 4 ) );
284   QCOMPARE( _testWrite( tempLayerLineString, widget.get(), QStringLiteral( "stringf" ),  Qt::TimeSpec::TimeZone ).toString( Qt::DateFormat::ISODate ),  tzTime.toString( Qt::DateFormat::ISODate ) );
285 
286   // Write on GPKG
287   // Test write on datetime field
288   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "datetimef" ), Qt::TimeSpec::UTC, true ), dateTime );
289   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "datetimef" ), Qt::TimeSpec::LocalTime, true ), localTime );
290   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "datetimef" ), Qt::TimeSpec::TimeZone, true ),  tzTime );
291 
292   // Test write on string field
293   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "stringf" ),
294                         Qt::TimeSpec::UTC, true ).toString( Qt::DateFormat::ISODate ), dateTime.toString( Qt::DateFormat::ISODate ) );
295   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "stringf" ),
296                         Qt::TimeSpec::LocalTime, true ).toString( Qt::DateFormat::ISODate ), localTime.toString( Qt::DateFormat::ISODate ) );
297   QCOMPARE( _testWrite( tempGpkgLayerPointString, widget.get(), QStringLiteral( "stringf" ),
298                         Qt::TimeSpec::TimeZone, true ).toString( Qt::DateFormat::ISODate ),  tzTime.toString( Qt::DateFormat::ISODate ) );
299 
300 
301 }
302 
testMultiPartLayers()303 void TestQgsGpsInformationWidget::testMultiPartLayers()
304 {
305   std::unique_ptr< QgsVectorLayer >multiLineString = std::make_unique< QgsVectorLayer >( QStringLiteral( "MultiLinestring?crs=epsg:4326&field=intf:int&field=stringf:string" ),
306       QStringLiteral( "vl4" ),
307       QStringLiteral( "memory" ) );
308 
309   QgsMapCanvas *canvas = mQgisApp->mapCanvas();
310   std::unique_ptr<QgsGpsInformationWidget> widget = std::make_unique<QgsGpsInformationWidget>( canvas );
311   widget->mMapCanvas->setCurrentLayer( multiLineString.get() );
312   multiLineString->startEditing();
313 
314   // not possible, no points
315   widget->mBtnCloseFeature_clicked();
316   QCOMPARE( multiLineString->featureCount(), 0L );
317   // need at least 2 points
318   widget->mBtnAddVertex_clicked();
319   widget->mBtnCloseFeature_clicked();
320   QCOMPARE( multiLineString->featureCount(), 0L );
321 
322   widget->mBtnAddVertex_clicked();
323   widget->mBtnCloseFeature_clicked();
324   QCOMPARE( multiLineString->featureCount(), 1L );
325   QgsFeature f;
326   QVERIFY( multiLineString->getFeatures().nextFeature( f ) );
327   QCOMPARE( f.geometry().wkbType(), QgsWkbTypes::MultiLineString );
328   multiLineString->rollBack();
329 
330   // multipolygon
331   std::unique_ptr< QgsVectorLayer >multiPolygon = std::make_unique< QgsVectorLayer >( QStringLiteral( "MultiPolygon?crs=epsg:4326&field=intf:int&field=stringf:string" ),
332       QStringLiteral( "vl4" ),
333       QStringLiteral( "memory" ) );
334 
335   widget = std::make_unique<QgsGpsInformationWidget>( canvas );
336   widget->mMapCanvas->setCurrentLayer( multiPolygon.get() );
337   multiPolygon->startEditing();
338 
339   // not possible, no points
340   widget->mBtnCloseFeature_clicked();
341   QCOMPARE( multiPolygon->featureCount(), 0L );
342 
343   // need at least 3 points
344   widget->mBtnAddVertex_clicked();
345   widget->mBtnCloseFeature_clicked();
346   QCOMPARE( multiPolygon->featureCount(), 0L );
347   widget->mBtnAddVertex_clicked();
348   widget->mBtnCloseFeature_clicked();
349   QCOMPARE( multiPolygon->featureCount(), 0L );
350 
351   widget->mBtnAddVertex_clicked();
352   widget->mBtnCloseFeature_clicked();
353   QCOMPARE( multiPolygon->featureCount(), 1L );
354   QVERIFY( multiPolygon->getFeatures().nextFeature( f ) );
355   QCOMPARE( f.geometry().wkbType(), QgsWkbTypes::MultiPolygon );
356   multiPolygon->rollBack();
357 }
358 
359 
360 
361 QGSTEST_MAIN( TestQgsGpsInformationWidget )
362 #include "testqgsgpsinformationwidget.moc"
363