1 /***************************************************************************
2 testqgsattributetable.cpp
3 -------------------------
4 Date : 2016-02-14
5 Copyright : (C) 2016 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 "qgsapplication.h"
18 #include "qgsfeatureiterator.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsfeature.h"
21 #include "qgsgeometry.h"
22 #include "qgsvectordataprovider.h"
23 #include "qgsvectorlayertemporalproperties.h"
24 #include "qgsattributetabledialog.h"
25 #include "qgsproject.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsunittypes.h"
28 #include "qgssettings.h"
29 #include "qgsvectorfilewriter.h"
30 #include "qgsfeaturelistmodel.h"
31 #include "qgsclipboard.h"
32
33 #include "qgstest.h"
34
35 /**
36 * \ingroup UnitTests
37 * This is a unit test for the attribute table dialog
38 */
39 class TestQgsAttributeTable : public QObject
40 {
41 Q_OBJECT
42 public:
43 TestQgsAttributeTable();
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 void testRegression15974();
51 void testFieldCalculation();
52 void testFieldCalculationArea();
53 void testNoGeom();
54 void testSelected();
55 void testSelectedOnTop();
56 void testSortByDisplayExpression();
57 void testOrderColumn();
58 void testFilteredFeatures();
59 void testVisibleTemporal();
60 void testCopySelectedRows();
61
62
63 private:
64 QgisApp *mQgisApp = nullptr;
65 };
66
67 TestQgsAttributeTable::TestQgsAttributeTable() = default;
68
69 //runs before all tests
initTestCase()70 void TestQgsAttributeTable::initTestCase()
71 {
72 qDebug() << "TestQgisAppClipboard::initTestCase()";
73 // init QGIS's paths - true means that all path will be inited from prefix
74 QgsApplication::init();
75 QgsApplication::initQgis();
76 mQgisApp = new QgisApp();
77
78 // setup the test QSettings environment
79 QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
80 QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
81 QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
82 }
83
84 //runs after all tests
cleanupTestCase()85 void TestQgsAttributeTable::cleanupTestCase()
86 {
87 QgsApplication::exitQgis();
88 }
89
testFieldCalculation()90 void TestQgsAttributeTable::testFieldCalculation()
91 {
92 //test field calculation
93
94 //create a temporary layer
95 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
96 QVERIFY( tempLayer->isValid() );
97 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
98 f1.setAttribute( QStringLiteral( "pk" ), 1 );
99 f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
100 QgsPolylineXY line3111;
101 line3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 );
102 QgsGeometry line3111G = QgsGeometry::fromPolylineXY( line3111 ) ;
103 f1.setGeometry( line3111G );
104 tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
105
106 // set project CRS and ellipsoid
107 QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
108 QgsProject::instance()->setCrs( srs );
109 QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
110 QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceMeters );
111
112 // run length calculation
113 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
114 tempLayer->startEditing();
115 dlg->runFieldCalculation( tempLayer.get(), QStringLiteral( "col1" ), QStringLiteral( "$length" ) );
116 tempLayer->commitChanges();
117 // check result
118 QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
119 QgsFeature f;
120 QVERIFY( fit.nextFeature( f ) );
121 double expected = 26932.156;
122 QGSCOMPARENEAR( f.attribute( "col1" ).toDouble(), expected, 0.001 );
123
124 // change project length unit, check calculation respects unit
125 QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceFeet );
126 std::unique_ptr< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.get() ) );
127 tempLayer->startEditing();
128 dlg2->runFieldCalculation( tempLayer.get(), QStringLiteral( "col1" ), QStringLiteral( "$length" ) );
129 tempLayer->commitChanges();
130 // check result
131 fit = tempLayer->dataProvider()->getFeatures();
132 QVERIFY( fit.nextFeature( f ) );
133 expected = 88360.0918635;
134 QGSCOMPARENEAR( f.attribute( "col1" ).toDouble(), expected, 0.001 );
135 }
136
testFieldCalculationArea()137 void TestQgsAttributeTable::testFieldCalculationArea()
138 {
139 //test $area field calculation
140
141 //create a temporary layer
142 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
143 QVERIFY( tempLayer->isValid() );
144 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
145 f1.setAttribute( QStringLiteral( "pk" ), 1 );
146 f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
147
148 QgsPolylineXY polygonRing3111;
149 polygonRing3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 ) << QgsPointXY( 2520109, 2397715 ) << QgsPointXY( 2520792, 2425494 ) << QgsPointXY( 2484588, 2425722 );
150 QgsPolygonXY polygon3111;
151 polygon3111 << polygonRing3111;
152 QgsGeometry polygon3111G = QgsGeometry::fromPolygonXY( polygon3111 );
153 f1.setGeometry( polygon3111G );
154 tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
155
156 // set project CRS and ellipsoid
157 QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
158 QgsProject::instance()->setCrs( srs );
159 QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
160 QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareMeters );
161
162 // run area calculation
163 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
164 tempLayer->startEditing();
165 dlg->runFieldCalculation( tempLayer.get(), QStringLiteral( "col1" ), QStringLiteral( "$area" ) );
166 tempLayer->commitChanges();
167 // check result
168 QgsFeatureIterator fit = tempLayer->dataProvider()->getFeatures();
169 QgsFeature f;
170 QVERIFY( fit.nextFeature( f ) );
171 double expected = 1005721496.78008;
172 QGSCOMPARENEAR( f.attribute( "col1" ).toDouble(), expected, 1.0 );
173
174 // change project area unit, check calculation respects unit
175 QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareMiles );
176 std::unique_ptr< QgsAttributeTableDialog > dlg2( new QgsAttributeTableDialog( tempLayer.get() ) );
177 tempLayer->startEditing();
178 dlg2->runFieldCalculation( tempLayer.get(), QStringLiteral( "col1" ), QStringLiteral( "$area" ) );
179 tempLayer->commitChanges();
180 // check result
181 fit = tempLayer->dataProvider()->getFeatures();
182 QVERIFY( fit.nextFeature( f ) );
183 expected = 388.311240;
184 QGSCOMPARENEAR( f.attribute( "col1" ).toDouble(), expected, 0.001 );
185 }
186
testNoGeom()187 void TestQgsAttributeTable::testNoGeom()
188 {
189 QgsSettings s;
190
191 //test that by default the attribute table DOESN'T fetch geometries (because performance)
192 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
193 QVERIFY( tempLayer->isValid() );
194
195 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowAll ) );
196
197 QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
198 QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );
199
200 // but if we are requesting only visible features, then geometry must be fetched...
201
202 dlg.reset( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowVisible ) );
203 QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
204 QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );
205
206 // try changing existing dialog to no geometry mode
207 dlg->mFeatureFilterWidget->filterShowAll();
208 QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
209 QVERIFY( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry );
210
211 // and back to a geometry mode
212 dlg->mFeatureFilterWidget->filterVisible();
213 QVERIFY( dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
214 QVERIFY( !( dlg->mMainView->masterModel()->request().flags() & QgsFeatureRequest::NoGeometry ) );
215
216 }
217
testVisibleTemporal()218 void TestQgsAttributeTable::testVisibleTemporal()
219 {
220 // test attribute table opening in show feature visible mode
221 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:date" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
222 QVERIFY( tempLayer->isValid() );
223
224 QgsPolylineXY line;
225 line << QgsPointXY( 0, 0 ) << QgsPointXY( 1, 1 );
226 QgsGeometry geometry = QgsGeometry::fromPolylineXY( line ) ;
227 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
228 f1.setGeometry( geometry );
229 f1.setAttributes( QgsAttributes() << 1 << QDate( 2020, 1, 1 ) );
230 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
231 f2.setGeometry( geometry );
232 f2.setAttributes( QgsAttributes() << 2 << QDate( 2020, 3, 1 ) );
233 QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
234 line.clear();
235 line << QgsPointXY( -3, -3 ) << QgsPointXY( -2, -2 );
236 geometry = QgsGeometry::fromPolylineXY( line );
237 f3.setGeometry( geometry );
238 f3.setAttributes( QgsAttributes() << 3 << QDate( 2020, 1, 1 ) );
239 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
240
241 QgsVectorLayerTemporalProperties *temporalProperties = qobject_cast< QgsVectorLayerTemporalProperties *>( tempLayer->temporalProperties() );
242 temporalProperties->setIsActive( true );
243 temporalProperties->setMode( QgsVectorLayerTemporalProperties::ModeFeatureDateTimeStartAndEndFromFields );
244 temporalProperties->setStartField( QStringLiteral( "col1" ) );
245
246 mQgisApp->mapCanvas()->setDestinationCrs( QgsCoordinateReferenceSystem( "EPSG:4326" ) );
247 mQgisApp->mapCanvas()->resize( 500, 500 );
248 mQgisApp->mapCanvas()->setLayers( QList< QgsMapLayer *>() << tempLayer.get() );
249 mQgisApp->mapCanvas()->setExtent( QgsRectangle( -1, -1, 1, 1 ) );
250 mQgisApp->mapCanvas()->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 2020, 1, 1 ) ), QDateTime( QDate( 2020, 2, 1 ) ) ) );
251
252 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowVisible ) );
253
254 // feature id 2 is filtered out due to being out of temporal range
255 // feature id 3 is filtered out due to being out of visible extent
256 QCOMPARE( dlg->mMainView->filteredFeatures(), QgsFeatureIds() << 1 );
257 }
258
testSelected()259 void TestQgsAttributeTable::testSelected()
260 {
261 // test attribute table opening in show selected mode
262 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
263 QVERIFY( tempLayer->isValid() );
264
265 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
266 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
267 QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
268 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
269
270 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowSelected ) );
271
272 QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
273 //should be nothing - because no selection!
274 QCOMPARE( dlg->mMainView->masterModel()->request().filterType(), QgsFeatureRequest::FilterFids );
275 QVERIFY( dlg->mMainView->masterModel()->request().filterFids().isEmpty() );
276
277 // make a selection
278 tempLayer->selectByIds( QgsFeatureIds() << 1 << 3 );
279 QCOMPARE( dlg->mMainView->masterModel()->request().filterType(), QgsFeatureRequest::FilterFids );
280 QCOMPARE( dlg->mMainView->masterModel()->request().filterFids(), QgsFeatureIds() << 1 << 3 );
281
282 // another test - start with selection when dialog created
283 dlg.reset( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowSelected ) );
284 QVERIFY( !dlg->mMainView->masterModel()->layerCache()->cacheGeometry() );
285 QCOMPARE( dlg->mMainView->masterModel()->request().filterType(), QgsFeatureRequest::FilterFids );
286 QCOMPARE( dlg->mMainView->masterModel()->request().filterFids(), QgsFeatureIds() << 1 << 3 );
287 // remove selection
288 tempLayer->removeSelection();
289 QCOMPARE( dlg->mMainView->masterModel()->request().filterType(), QgsFeatureRequest::FilterFids );
290 QVERIFY( dlg->mMainView->masterModel()->request().filterFids().isEmpty() );
291 }
292
testSelectedOnTop()293 void TestQgsAttributeTable::testSelectedOnTop()
294 {
295 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
296 QVERIFY( tempLayer->isValid() );
297
298 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
299 f1.setAttribute( 0, 1 );
300 f1.setAttribute( 1, 3.2 );
301 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
302 f2.setAttribute( 0, 2 );
303 f2.setAttribute( 1, 1.8 );
304 QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
305 f3.setAttribute( 0, 3 );
306 f3.setAttribute( 1, 5.0 );
307 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
308
309 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
310
311 dlg->mMainView->setSortExpression( "pk" );
312 QCOMPARE( dlg->mMainView->mFilterModel->index( 0, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 1 ) );
313 QCOMPARE( dlg->mMainView->mFilterModel->index( 1, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 2 ) );
314 QCOMPARE( dlg->mMainView->mFilterModel->index( 2, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 3 ) );
315
316 tempLayer->selectByIds( QgsFeatureIds() << 2 );
317 dlg->mMainView->setSelectedOnTop( true );
318
319 QCOMPARE( dlg->mMainView->mFilterModel->index( 0, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 2 ) );
320 QCOMPARE( dlg->mMainView->mFilterModel->index( 1, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 1 ) );
321 QCOMPARE( dlg->mMainView->mFilterModel->index( 2, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 3 ) );
322
323 dlg->mMainView->setSelectedOnTop( false );
324
325 QCOMPARE( dlg->mMainView->mFilterModel->index( 0, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 1 ) );
326 QCOMPARE( dlg->mMainView->mFilterModel->index( 1, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 2 ) );
327 QCOMPARE( dlg->mMainView->mFilterModel->index( 2, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 3 ) );
328
329 tempLayer->selectByIds( QgsFeatureIds() << 3 );
330
331 QCOMPARE( dlg->mMainView->mFilterModel->index( 0, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 1 ) );
332 QCOMPARE( dlg->mMainView->mFilterModel->index( 1, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 2 ) );
333 QCOMPARE( dlg->mMainView->mFilterModel->index( 2, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 3 ) );
334
335 dlg->mMainView->setSelectedOnTop( true );
336
337 QCOMPARE( dlg->mMainView->mFilterModel->index( 0, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 3 ) );
338 QCOMPARE( dlg->mMainView->mFilterModel->index( 1, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 1 ) );
339 QCOMPARE( dlg->mMainView->mFilterModel->index( 2, 0 ).data( QgsAttributeTableModel::FeatureIdRole ), QVariant( 2 ) );
340
341 }
342
testSortByDisplayExpression()343 void TestQgsAttributeTable::testSortByDisplayExpression()
344 {
345 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
346 QVERIFY( tempLayer->isValid() );
347
348 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
349 f1.setAttribute( 0, 1 );
350 f1.setAttribute( 1, 3.2 );
351 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
352 f2.setAttribute( 0, 2 );
353 f2.setAttribute( 1, 1.8 );
354 QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
355 f3.setAttribute( 0, 3 );
356 f3.setAttribute( 1, 5.0 );
357 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
358
359 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
360
361 dlg->mMainView->mFeatureListView->setDisplayExpression( "pk" );
362 QgsFeatureListModel *listModel = dlg->mMainView->mFeatureListModel;
363 QCOMPARE( listModel->rowCount(), 3 );
364
365 QCOMPARE( listModel->index( 0, 0 ).data( Qt::DisplayRole ), QVariant( 1 ) );
366 QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 2 ) );
367 QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 3 ) );
368
369 dlg->mMainView->mFeatureListView->setDisplayExpression( "col1" );
370 QCOMPARE( listModel->index( 0, 0 ).data( Qt::DisplayRole ), QVariant( 1.8 ) );
371 QCOMPARE( listModel->index( 1, 0 ).data( Qt::DisplayRole ), QVariant( 3.2 ) );
372 QCOMPARE( listModel->index( 2, 0 ).data( Qt::DisplayRole ), QVariant( 5.0 ) );
373 }
374
testRegression15974()375 void TestQgsAttributeTable::testRegression15974()
376 {
377 // Test duplicated rows in attribute table + two crashes.
378 QString path = QDir::tempPath() + "/testshp15974.shp";
379 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "polygon?crs=epsg:4326&field=id:integer" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
380 QVERIFY( tempLayer->isValid() );
381 QgsVectorFileWriter::SaveVectorOptions saveOptions;
382 saveOptions.fileEncoding = QStringLiteral( "system" );
383 saveOptions.driverName = QStringLiteral( "ESRI Shapefile" );
384 QgsVectorFileWriter::writeAsVectorFormatV2( tempLayer.get(), path, tempLayer->transformContext(), saveOptions );
385 std::unique_ptr< QgsVectorLayer> shpLayer( new QgsVectorLayer( path, QStringLiteral( "test" ), QStringLiteral( "ogr" ) ) );
386 QgsFeature f1( shpLayer->dataProvider()->fields(), 1 );
387 QgsGeometry geom;
388 geom = QgsGeometry().fromWkt( QStringLiteral( "polygon((0 0, 0 1, 1 1, 1 0, 0 0))" ) );
389 QVERIFY( geom.isGeosValid() );
390 f1.setGeometry( geom );
391 QgsFeature f2( shpLayer->dataProvider()->fields(), 2 );
392 f2.setGeometry( geom );
393 QgsFeature f3( shpLayer->dataProvider()->fields(), 3 );
394 f3.setGeometry( geom );
395 QVERIFY( shpLayer->startEditing() );
396 QVERIFY( shpLayer->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
397 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( shpLayer.get() ) );
398 QCOMPARE( shpLayer->featureCount(), 3L );
399 mQgisApp->saveEdits( shpLayer.get() );
400 QCOMPARE( shpLayer->featureCount(), 3L );
401 QCOMPARE( dlg->mMainView->masterModel()->rowCount(), 3 );
402 QCOMPARE( dlg->mMainView->mLayerCache->cachedFeatureIds().count(), 3 );
403 QCOMPARE( dlg->mMainView->featureCount(), 3 );
404 // All the following instructions made the test pass, before the connections to invalidate()
405 // were introduced in QgsDualView::initModels
406 // dlg->mMainView->mFilterModel->setSourceModel( dlg->mMainView->masterModel() );
407 // dlg->mMainView->mFilterModel->invalidate();
408 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 3 );
409 }
410
testOrderColumn()411 void TestQgsAttributeTable::testOrderColumn()
412 {
413 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:int&field=col2:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
414 QVERIFY( tempLayer->isValid() );
415
416 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
417 f1.setAttribute( 0, 1 );
418 f1.setAttribute( 1, 13 );
419 f1.setAttribute( 2, 7 );
420 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 ) );
421
422 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get() ) );
423
424 // Issue https://github.com/qgis/QGIS/issues/28493
425 // When we reorder column (last column becomes first column), and we select an entire row
426 // the currentIndex is no longer the first column, and consequently it breaks edition
427
428 QgsAttributeTableConfig config = QgsAttributeTableConfig();
429 config.update( tempLayer->dataProvider()->fields() );
430 QVector<QgsAttributeTableConfig::ColumnConfig> columns = config.columns();
431
432 // move last column in first position
433 columns.move( 2, 0 );
434 config.setColumns( columns );
435
436 dlg->mMainView->setAttributeTableConfig( config );
437
438 QgsAttributeTableFilterModel *filterModel = static_cast<QgsAttributeTableFilterModel *>( dlg->mMainView->mTableView->model() );
439 filterModel->sort( 0, Qt::AscendingOrder );
440
441 QModelIndex index = filterModel->mapToSource( filterModel->sourceModel()->index( 0, 0 ) );
442 QCOMPARE( index.row(), 0 );
443 QCOMPARE( index.column(), 2 );
444
445 index = filterModel->mapFromSource( filterModel->sourceModel()->index( 0, 0 ) );
446 QCOMPARE( index.row(), 0 );
447 QCOMPARE( index.column(), 1 );
448
449 qDebug() << filterModel->mapFromSource( filterModel->sourceModel()->index( 0, 0 ) );
450
451 // column 0 is indeed column 2 since we move it
452 QCOMPARE( filterModel->sortColumn(), 2 );
453 }
454
testFilteredFeatures()455 void TestQgsAttributeTable::testFilteredFeatures()
456 {
457 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:int&field=col2:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
458 QVERIFY( tempLayer->isValid() );
459
460 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
461 f1.setAttribute( 0, 1 );
462 f1.setAttribute( 1, 2 );
463 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
464 f2.setAttribute( 0, 2 );
465 f2.setAttribute( 1, 4 );
466 QgsFeature f3( tempLayer->dataProvider()->fields(), 3 );
467 f3.setAttribute( 0, 3 );
468 f3.setAttribute( 1, 6 );
469 QgsFeature f4( tempLayer->dataProvider()->fields(), 4 );
470 f4.setAttribute( 0, 4 );
471 f4.setAttribute( 1, 8 );
472
473 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 ) );
474
475 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowAll ) );
476
477 QEventLoop loop;
478 connect( qobject_cast<QgsAttributeTableFilterModel *>( dlg->mMainView->mFilterModel ), &QgsAttributeTableFilterModel::featuresFiltered, &loop, &QEventLoop::quit );
479
480 // show all (three features)
481 dlg->mFeatureFilterWidget->filterShowAll();
482 QCOMPARE( dlg->mMainView->featureCount(), 3 );
483 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 3 );
484
485 // add a feature
486 tempLayer->startEditing();
487 QVERIFY( tempLayer->addFeatures( QgsFeatureList() << f4 ) );
488 //still show all (four features)
489 QCOMPARE( tempLayer->featureCount(), 4L );
490 QCOMPARE( dlg->mMainView->featureCount(), 4 );
491 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 4 );
492
493 // bigger 5 (two of four features)
494 dlg->mFeatureFilterWidget->setFilterExpression( QStringLiteral( "col1>5" ), QgsAttributeForm::ReplaceFilter, true );
495 QCOMPARE( dlg->mMainView->featureCount(), 4 );
496 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 2 );
497 // bigger 7 (one of four features)
498 dlg->mFeatureFilterWidget->setFilterExpression( QStringLiteral( "col1>7" ), QgsAttributeForm::ReplaceFilter, true );
499 QCOMPARE( dlg->mMainView->featureCount(), 4 );
500 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 1 );
501 // bigger 9 (no of four features)
502 dlg->mFeatureFilterWidget->setFilterExpression( QStringLiteral( "col1>9" ), QgsAttributeForm::ReplaceFilter, true );
503 QCOMPARE( dlg->mMainView->featureCount(), 4 );
504 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 0 );
505
506 //add two features
507 QgsFeature f5( tempLayer->dataProvider()->fields(), 5 );
508 f5.setAttribute( 0, 5 );
509 f5.setAttribute( 1, 10 );
510 QgsFeature f6( tempLayer->dataProvider()->fields(), 6 );
511 f6.setAttribute( 0, 6 );
512 f6.setAttribute( 1, 12 );
513 QVERIFY( tempLayer->addFeatures( QgsFeatureList() << f5 << f6 ) );
514 tempLayer->commitChanges();
515 loop.exec();
516 //no filter change -> now two of six features
517 QCOMPARE( dlg->mMainView->featureCount(), 6 );
518 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 2 );
519
520 //remove a feature not affecting the filter
521 tempLayer->startEditing();
522 QVERIFY( tempLayer->deleteFeature( f2.id() ) );
523 //no filter change -> now two of five features
524 QCOMPARE( dlg->mMainView->featureCount(), 5 );
525 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 2 );
526
527 //remove a feature affecting the filter
528 QVERIFY( tempLayer->deleteFeature( f5.id() ) );
529 //no filter change -> now one of four features
530 QCOMPARE( dlg->mMainView->featureCount(), 4 );
531 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 1 );
532
533 // smaller 11 (three of four features)
534 dlg->mFeatureFilterWidget->setFilterExpression( QStringLiteral( "col1<11" ), QgsAttributeForm::ReplaceFilter, true );
535 QCOMPARE( dlg->mMainView->filteredFeatureCount(), 3 );
536 }
537
testCopySelectedRows()538 void TestQgsAttributeTable::testCopySelectedRows()
539 {
540 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:int&field=col2:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
541 QVERIFY( tempLayer->isValid() );
542
543 QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
544 f1.setAttribute( 0, 1 );
545 f1.setAttribute( 1, 2 );
546
547 QgsFeature f2( tempLayer->dataProvider()->fields(), 2 );
548 f2.setAttribute( 0, 2 );
549 f2.setAttribute( 1, 4 );
550
551 QVERIFY( tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 ) );
552
553 std::unique_ptr< QgsAttributeTableDialog > dlg( new QgsAttributeTableDialog( tempLayer.get(), QgsAttributeTableFilterModel::ShowAll ) );
554
555 tempLayer->selectByIds( QgsFeatureIds() << 1 << 2 );
556
557 dlg->mActionCopySelectedRows_triggered();
558
559 QgsClipboard *clipboard = QgisApp::instance()->clipboard();
560 QVERIFY( clipboard );
561 QVERIFY( !clipboard->isEmpty() );
562 QCOMPARE( clipboard->fields().names(), QStringList() << "pk" << "col1" << "col2" );
563
564 QgsFeatureList features = clipboard->copyOf();
565 QCOMPARE( features.count(), 2 );
566 QCOMPARE( features.at( 0 ).attribute( 0 ), QVariant( 1 ) );
567 QCOMPARE( features.at( 0 ).attribute( "col1" ), QVariant( 2 ) );
568 QCOMPARE( features.at( 0 ).attribute( "col2" ), QVariant() );
569 QCOMPARE( features.at( 1 ).attribute( "pk" ), QVariant( 2 ) );
570 QCOMPARE( features.at( 1 ).attribute( "col1" ), QVariant( 4 ) );
571 QCOMPARE( features.at( 1 ).attribute( 2 ), QVariant() );
572
573 QCOMPARE( clipboard->crs().authid(), QStringLiteral( "EPSG:3111" ) );
574 }
575
576 QGSTEST_MAIN( TestQgsAttributeTable )
577 #include "testqgsattributetable.moc"
578