1 /***************************************************************************
2 testqgsdualview.cpp
3 --------------------------------------
4 Date : 14.2.2013
5 Copyright : (C) 2013 Matthias Kuhn
6 Email : matthias at opengis dot ch
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
16
17 #include "qgstest.h"
18
19 #include <editorwidgets/core/qgseditorwidgetregistry.h>
20 #include <attributetable/qgsattributetableview.h>
21 #include <attributetable/qgsdualview.h>
22 #include <editform/qgsattributeeditorhtmlelement.h>
23 #include "qgsattributeform.h"
24 #include <qgsapplication.h>
25 #include "qgsfeatureiterator.h"
26 #include <qgsvectorlayer.h>
27 #include "qgsvectordataprovider.h"
28 #include <qgsmapcanvas.h>
29 #include <qgsfeature.h>
30 #include "qgsgui.h"
31 #include "qgsvectorlayercache.h"
32 #include "qgstest.h"
33
34 class TestQgsDualView : public QObject
35 {
36 Q_OBJECT
37 public:
38 TestQgsDualView() = default;
39
40 private slots:
41 void initTestCase(); // will be called before the first testfunction is executed.
42 void cleanupTestCase(); // will be called after the last testfunction was executed.
43 void init(); // will be called before each testfunction is executed.
44 void cleanup(); // will be called after every testfunction.
45
46 void testColumnCount();
47
48 void testColumnHeaders();
49
50 void testData();
51 void testAttributeTableConfig();
52 void testFilterSelected();
53
54 void testSelectAll();
55
56 void testSort();
57
58 void testAttributeFormSharedValueScanning();
59 void testNoGeom();
60
61 void testHtmlWidget_data();
62 void testHtmlWidget();
63
64 private:
65 QgsMapCanvas *mCanvas = nullptr;
66 QgsVectorLayer *mPointsLayer = nullptr;
67 QString mTestDataDir;
68 QgsDualView *mDualView = nullptr;
69 };
70
initTestCase()71 void TestQgsDualView::initTestCase()
72 {
73 QgsApplication::init();
74 QgsApplication::initQgis();
75 QgsApplication::showSettings();
76
77 QgsGui::editorWidgetRegistry()->initEditors();
78
79 // Setup a map canvas with a vector layer loaded...
80 const QString myDataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
81 mTestDataDir = myDataDir + '/';
82
83 //
84 // load a vector layer
85 //
86 const QString myPointsFileName = mTestDataDir + "points.shp";
87 const QFileInfo myPointFileInfo( myPointsFileName );
88 mPointsLayer = new QgsVectorLayer( myPointFileInfo.filePath(),
89 myPointFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
90
91 mCanvas = new QgsMapCanvas();
92 }
93
cleanupTestCase()94 void TestQgsDualView::cleanupTestCase()
95 {
96 delete mPointsLayer;
97 delete mCanvas;
98 QgsApplication::exitQgis();
99 }
100
init()101 void TestQgsDualView::init()
102 {
103 mDualView = new QgsDualView();
104 mDualView->init( mPointsLayer, mCanvas );
105 }
106
cleanup()107 void TestQgsDualView::cleanup()
108 {
109 delete mDualView;
110 }
111
testColumnCount()112 void TestQgsDualView::testColumnCount()
113 {
114 QCOMPARE( mDualView->tableView()->model()->columnCount(), mPointsLayer->fields().count() );
115 }
116
testColumnHeaders()117 void TestQgsDualView::testColumnHeaders()
118 {
119 for ( int i = 0; i < mPointsLayer->fields().count(); ++i )
120 {
121 const QgsField fld = mPointsLayer->fields().at( i );
122 QCOMPARE( mDualView->tableView()->model()->headerData( i, Qt::Horizontal ).toString(), fld.name() );
123 }
124 }
125
testData()126 void TestQgsDualView::testData()
127 {
128 QgsFeature feature;
129 mPointsLayer->getFeatures( QgsFeatureRequest().setFilterFid( 0 ) ).nextFeature( feature );
130
131 for ( int i = 0; i < mPointsLayer->fields().count(); ++i )
132 {
133 const QgsField fld = mPointsLayer->fields().at( i );
134
135 const QModelIndex index = mDualView->tableView()->model()->index( 0, i );
136 QCOMPARE( mDualView->tableView()->model()->data( index ).toString(), fld.displayString( feature.attribute( i ) ) );
137 }
138 }
139
testAttributeTableConfig()140 void TestQgsDualView::testAttributeTableConfig()
141 {
142 QCOMPARE( mDualView->attributeTableConfig().columns().count(), mPointsLayer->attributeTableConfig().columns().count() );
143 }
144
testFilterSelected()145 void TestQgsDualView::testFilterSelected()
146 {
147 QgsFeature feature;
148 QList< QgsFeatureId > ids;
149 QgsFeatureIterator it = mPointsLayer->getFeatures( QgsFeatureRequest().setOrderBy( QgsFeatureRequest::OrderBy() << QgsFeatureRequest::OrderByClause( QStringLiteral( "Heading" ) ) ) );
150 while ( it.nextFeature( feature ) )
151 ids << feature.id();
152
153 // select some features
154 QList< QgsFeatureId > selected;
155 selected << ids.at( 1 ) << ids.at( 3 );
156 mPointsLayer->selectByIds( qgis::listToSet( selected ) );
157
158 mDualView->setFilterMode( QgsAttributeTableFilterModel::ShowSelected );
159 QCOMPARE( mDualView->tableView()->model()->rowCount(), 2 );
160
161 const int headingIdx = mPointsLayer->fields().lookupField( QStringLiteral( "Heading" ) );
162 const QgsField fld = mPointsLayer->fields().at( headingIdx );
163 for ( int i = 0; i < selected.count(); ++i )
164 {
165 mPointsLayer->getFeatures( QgsFeatureRequest().setFilterFid( selected.at( i ) ) ).nextFeature( feature );
166 const QModelIndex index = mDualView->tableView()->model()->index( i, headingIdx );
167 QCOMPARE( mDualView->tableView()->model()->data( index ).toString(), fld.displayString( feature.attribute( headingIdx ) ) );
168 }
169
170 // select none
171 mPointsLayer->removeSelection();
172 QCOMPARE( mDualView->tableView()->model()->rowCount(), 0 );
173 }
174
testSelectAll()175 void TestQgsDualView::testSelectAll()
176 {
177
178 QEventLoop loop;
179 connect( qobject_cast<QgsAttributeTableFilterModel *>( mDualView->mFilterModel ), &QgsAttributeTableFilterModel::visibleReloaded, &loop, &QEventLoop::quit );
180 mDualView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
181 // Only show parts of the canvas, so only one selected feature is visible
182 mCanvas->setExtent( QgsRectangle( -139, 23, -100, 48 ) );
183 loop.exec();
184 mDualView->mTableView->selectAll();
185 QCOMPARE( mPointsLayer->selectedFeatureCount(), 10 );
186
187 mPointsLayer->selectByIds( QgsFeatureIds() );
188 mCanvas->setExtent( QgsRectangle( -110, 40, -100, 48 ) );
189 loop.exec();
190 mDualView->mTableView->selectAll();
191 QCOMPARE( mPointsLayer->selectedFeatureCount(), 1 );
192 }
193
testSort()194 void TestQgsDualView::testSort()
195 {
196 mDualView->setSortExpression( QStringLiteral( "Class" ) );
197
198 QStringList classes;
199 classes << QStringLiteral( "B52" )
200 << QStringLiteral( "B52" )
201 << QStringLiteral( "B52" )
202 << QStringLiteral( "B52" )
203 << QStringLiteral( "Biplane" )
204 << QStringLiteral( "Biplane" )
205 << QStringLiteral( "Biplane" )
206 << QStringLiteral( "Biplane" )
207 << QStringLiteral( "Biplane" )
208 << QStringLiteral( "Jet" )
209 << QStringLiteral( "Jet" )
210 << QStringLiteral( "Jet" )
211 << QStringLiteral( "Jet" )
212 << QStringLiteral( "Jet" )
213 << QStringLiteral( "Jet" )
214 << QStringLiteral( "Jet" )
215 << QStringLiteral( "Jet" );
216
217 for ( int i = 0; i < classes.length(); ++i )
218 {
219 const QModelIndex index = mDualView->tableView()->model()->index( i, 0 );
220 QCOMPARE( mDualView->tableView()->model()->data( index ).toString(), classes.at( i ) );
221 }
222
223 QStringList headings;
224 headings << QStringLiteral( "0" )
225 << QStringLiteral( "0" )
226 << QStringLiteral( "12" )
227 << QStringLiteral( "34" )
228 << QStringLiteral( "80" )
229 << QStringLiteral( "85" )
230 << QStringLiteral( "90" )
231 << QStringLiteral( "90" )
232 << QStringLiteral( "95" )
233 << QStringLiteral( "100" )
234 << QStringLiteral( "140" )
235 << QStringLiteral( "160" )
236 << QStringLiteral( "180" )
237 << QStringLiteral( "240" )
238 << QStringLiteral( "270" )
239 << QStringLiteral( "300" )
240 << QStringLiteral( "340" );
241
242 mDualView->setSortExpression( QStringLiteral( "Heading" ) );
243
244 for ( int i = 0; i < headings.length(); ++i )
245 {
246 const QModelIndex index = mDualView->tableView()->model()->index( i, 1 );
247 QCOMPARE( mDualView->tableView()->model()->data( index ).toString(), headings.at( i ) );
248 }
249 }
250
testAttributeFormSharedValueScanning()251 void TestQgsDualView::testAttributeFormSharedValueScanning()
252 {
253 // test QgsAttributeForm::scanForEqualAttributes
254
255 QSet< int > mixedValueFields;
256 QHash< int, QVariant > fieldSharedValues;
257
258 // make a temporary layer to check through
259 QgsVectorLayer *layer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:integer&field=col3:integer&field=col4:integer" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
260 QVERIFY( layer->isValid() );
261 QgsFeature f1( layer->dataProvider()->fields(), 1 );
262 f1.setAttribute( QStringLiteral( "col1" ), 1 );
263 f1.setAttribute( QStringLiteral( "col2" ), 1 );
264 f1.setAttribute( QStringLiteral( "col3" ), 3 );
265 f1.setAttribute( QStringLiteral( "col4" ), 1 );
266 QgsFeature f2( layer->dataProvider()->fields(), 2 );
267 f2.setAttribute( QStringLiteral( "col1" ), 1 );
268 f2.setAttribute( QStringLiteral( "col2" ), 2 );
269 f2.setAttribute( QStringLiteral( "col3" ), 3 );
270 f2.setAttribute( QStringLiteral( "col4" ), 2 );
271 QgsFeature f3( layer->dataProvider()->fields(), 3 );
272 f3.setAttribute( QStringLiteral( "col1" ), 1 );
273 f3.setAttribute( QStringLiteral( "col2" ), 2 );
274 f3.setAttribute( QStringLiteral( "col3" ), 3 );
275 f3.setAttribute( QStringLiteral( "col4" ), 2 );
276 QgsFeature f4( layer->dataProvider()->fields(), 4 );
277 f4.setAttribute( QStringLiteral( "col1" ), 1 );
278 f4.setAttribute( QStringLiteral( "col2" ), 1 );
279 f4.setAttribute( QStringLiteral( "col3" ), 3 );
280 f4.setAttribute( QStringLiteral( "col4" ), 2 );
281 layer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 );
282
283 const QgsAttributeForm form( layer );
284
285 QgsFeatureIterator it = layer->getFeatures();
286
287 form.scanForEqualAttributes( it, mixedValueFields, fieldSharedValues );
288
289 QCOMPARE( mixedValueFields, QSet< int >() << 1 << 3 );
290 QCOMPARE( fieldSharedValues.value( 0 ).toInt(), 1 );
291 QCOMPARE( fieldSharedValues.value( 2 ).toInt(), 3 );
292
293 // add another feature so all attributes are different
294 QgsFeature f5( layer->dataProvider()->fields(), 5 );
295 f5.setAttribute( QStringLiteral( "col1" ), 11 );
296 f5.setAttribute( QStringLiteral( "col2" ), 11 );
297 f5.setAttribute( QStringLiteral( "col3" ), 13 );
298 f5.setAttribute( QStringLiteral( "col4" ), 12 );
299 layer->dataProvider()->addFeatures( QgsFeatureList() << f5 );
300
301 it = layer->getFeatures();
302
303 form.scanForEqualAttributes( it, mixedValueFields, fieldSharedValues );
304 QCOMPARE( mixedValueFields, QSet< int >() << 0 << 1 << 2 << 3 );
305 QVERIFY( fieldSharedValues.isEmpty() );
306
307 // single feature, all attributes should be shared
308 it = layer->getFeatures( QgsFeatureRequest().setFilterFid( 4 ) );
309 form.scanForEqualAttributes( it, mixedValueFields, fieldSharedValues );
310 QCOMPARE( fieldSharedValues.value( 0 ).toInt(), 1 );
311 QCOMPARE( fieldSharedValues.value( 1 ).toInt(), 1 );
312 QCOMPARE( fieldSharedValues.value( 2 ).toInt(), 3 );
313 QCOMPARE( fieldSharedValues.value( 3 ).toInt(), 2 );
314 QVERIFY( mixedValueFields.isEmpty() );
315 }
316
testNoGeom()317 void TestQgsDualView::testNoGeom()
318 {
319 //test that both the master model and cache for the dual view either both request geom or both don't request geom
320 std::unique_ptr< QgsDualView > dv( new QgsDualView() );
321
322 // request with geometry
323 QgsFeatureRequest req;
324 dv->init( mPointsLayer, mCanvas, req );
325 // check that both master model AND cache are using geometry
326 QgsAttributeTableModel *model = dv->masterModel();
327 QVERIFY( model->layerCache()->cacheGeometry() );
328 QVERIFY( !( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
329
330 // request with NO geometry, but using filter rect (which should override and request geom)
331 req = QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) );
332 dv.reset( new QgsDualView() );
333 dv->init( mPointsLayer, mCanvas, req );
334 model = dv->masterModel();
335 QVERIFY( model->layerCache()->cacheGeometry() );
336 QVERIFY( !( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
337
338 // request with NO geometry
339 req = QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry );
340 dv.reset( new QgsDualView() );
341 dv->init( mPointsLayer, mCanvas, req );
342 model = dv->masterModel();
343 QVERIFY( !model->layerCache()->cacheGeometry() );
344 QVERIFY( ( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
345 }
346
testHtmlWidget_data()347 void TestQgsDualView::testHtmlWidget_data()
348 {
349 QTest::addColumn<QString>( "expression" );
350 QTest::addColumn<bool>( "expectedCacheGeometry" );
351
352 QTest::newRow( "with-geometry" ) << "geom_to_wkt($geometry)" << true;
353 QTest::newRow( "without-geometry" ) << "2+pk" << false;
354 }
355
testHtmlWidget()356 void TestQgsDualView::testHtmlWidget()
357 {
358 // check that HTML widget set cache geometry when needed
359
360 QFETCH( QString, expression );
361 QFETCH( bool, expectedCacheGeometry );
362
363 QgsVectorLayer layer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int" ), QStringLiteral( "layer" ), QStringLiteral( "memory" ) );
364 QgsProject::instance()->addMapLayer( &layer, false, false );
365 QgsFeature f( layer.fields() );
366 f.setAttribute( QStringLiteral( "pk" ), 1 );
367 f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT(0.5 0.5)" ) ) );
368 QVERIFY( f.isValid() );
369 QVERIFY( f.geometry().isGeosValid() );
370 QVERIFY( layer.dataProvider()->addFeature( f ) );
371
372 QgsEditFormConfig editFormConfig = layer.editFormConfig();
373 editFormConfig.clearTabs();
374 QgsAttributeEditorHtmlElement *htmlElement = new QgsAttributeEditorHtmlElement( "HtmlWidget", nullptr );
375 htmlElement->setHtmlCode( QStringLiteral( "The text is '<script>document.write(expression.evaluate(\"%1\"));</script>'" ).arg( expression ) );
376 editFormConfig.addTab( htmlElement );
377 editFormConfig.setLayout( QgsEditFormConfig::TabLayout );
378 layer.setEditFormConfig( editFormConfig );
379
380 QgsFeatureRequest request;
381 request.setFlags( QgsFeatureRequest::NoGeometry );
382
383 QgsDualView dualView;
384 dualView.setView( QgsDualView::AttributeEditor );
385 dualView.init( &layer, mCanvas, request );
386 QCOMPARE( dualView.mLayerCache->cacheGeometry(), expectedCacheGeometry );
387
388 QgsProject::instance()->removeMapLayer( &layer );
389 }
390
391 QGSTEST_MAIN( TestQgsDualView )
392 #include "testqgsdualview.moc"
393