1 /***************************************************************************
2 testqgsattributeform.cpp
3 --------------------------------------
4 Date : 13 05 2016
5 Copyright : (C) 2016 Paul Blottiere
6 Email : paul dot blottiere at oslandia 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
16
17 #include "qgstest.h"
18 #include <QPushButton>
19 #include <QLineEdit>
20
21 #include <editorwidgets/core/qgseditorwidgetregistry.h>
22 #include "qgsattributeform.h"
23 #include <qgsapplication.h>
24 #include "qgseditorwidgetwrapper.h"
25 #include <qgsvectorlayer.h>
26 #include "qgsvectordataprovider.h"
27 #include <qgsfeature.h>
28 #include <qgsvectorlayerjoininfo.h>
29 #include "qgsgui.h"
30 #include "qgsattributeformeditorwidget.h"
31 #include "qgsattributeforminterface.h"
32 #include "qgsmultiedittoolbutton.h"
33 #include "qgsattributeeditorfield.h"
34 #include "qgsattributeeditorcontainer.h"
35 #include <QSignalSpy>
36
37 class TestQgsAttributeForm : public QObject
38 {
39 Q_OBJECT
40 public:
41 TestQgsAttributeForm() = default;
42
43 private slots:
44 void initTestCase(); // will be called before the first testfunction is executed.
45 void cleanupTestCase(); // will be called after the last testfunction was executed.
46 void init(); // will be called before each testfunction is executed.
47 void cleanup(); // will be called after every testfunction.
48
49 void testFieldConstraint();
50 void testFieldMultiConstraints();
51 void testOKButtonStatus();
52 void testDynamicForm();
53 void testConstraintsOnJoinedFields();
54 void testEditableJoin();
55 void testUpsertOnEdit();
56 void testFixAttributeForm();
57 void testAttributeFormInterface();
58 void testDefaultValueUpdate();
59 void testDefaultValueUpdateRecursion();
60 void testSameFieldSync();
61 void testZeroDoubles();
62
63 private:
constraintsLabel(QgsAttributeForm * form,QgsEditorWidgetWrapper * ww)64 QLabel *constraintsLabel( QgsAttributeForm *form, QgsEditorWidgetWrapper *ww )
65 {
66 QgsAttributeFormEditorWidget *formEditorWidget = form->mFormEditorWidgets.value( ww->fieldIdx() );
67 return formEditorWidget->findChild<QLabel *>( QStringLiteral( "ConstraintStatus" ) );
68 }
69 };
70
initTestCase()71 void TestQgsAttributeForm::initTestCase()
72 {
73 QgsApplication::init();
74 QgsApplication::initQgis();
75 QgsGui::editorWidgetRegistry()->initEditors();
76 }
77
cleanupTestCase()78 void TestQgsAttributeForm::cleanupTestCase()
79 {
80 QgsApplication::exitQgis();
81 }
82
init()83 void TestQgsAttributeForm::init()
84 {
85 }
86
cleanup()87 void TestQgsAttributeForm::cleanup()
88 {
89 }
90
testFieldConstraint()91 void TestQgsAttributeForm::testFieldConstraint()
92 {
93 // make a temporary vector layer
94 const QString def = QStringLiteral( "Point?field=col0:integer" );
95 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
96 layer->setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() ) );
97
98 // add a feature to the vector layer
99 QgsFeature ft( layer->dataProvider()->fields(), 1 );
100 ft.setAttribute( QStringLiteral( "col0" ), 0 );
101
102 // build a form for this feature
103 QgsAttributeForm form( layer );
104 form.setFeature( ft );
105
106 // testing stuff
107 const QString validLabel = QStringLiteral( "<font color=\"#259B24\">%1</font>" ).arg( QChar( 0x2714 ) );
108 const QString invalidLabel = QStringLiteral( "<font color=\"#FF9800\">%1</font>" ).arg( QChar( 0x2718 ) );
109 const QString warningLabel = QStringLiteral( "<font color=\"#FFC107\">%1</font>" ).arg( QChar( 0x2718 ) );
110
111 // set constraint
112 layer->setConstraintExpression( 0, QString() );
113
114 // get wrapper
115 QgsEditorWidgetWrapper *ww = nullptr;
116 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
117
118 // no constraint so we expect an empty label
119 QCOMPARE( constraintsLabel( &form, ww )->text(), QString() );
120
121 // set a not null constraint
122 layer->setConstraintExpression( 0, QStringLiteral( "col0 is not null" ) );
123 // build a form for this feature
124 QgsAttributeForm form2( layer );
125 form2.setFeature( ft );
126 QSignalSpy spy( &form2, SIGNAL( widgetValueChanged( QString, QVariant, bool ) ) );
127 ww = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[0] );
128
129 // set value to 1
130 ww->setValues( 1, QVariantList() );
131 QCOMPARE( spy.count(), 1 );
132 QCOMPARE( constraintsLabel( &form2, ww )->text(), validLabel );
133
134 // set value to null
135 spy.clear();
136 ww->setValues( QVariant(), QVariantList() );
137 QCOMPARE( spy.count(), 1 );
138 QCOMPARE( constraintsLabel( &form2, ww )->text(), invalidLabel );
139
140 // set value to 1
141 spy.clear();
142 ww->setValues( 1, QVariantList() );
143 QCOMPARE( spy.count(), 1 );
144 QCOMPARE( constraintsLabel( &form2, ww )->text(), validLabel );
145
146 // set a soft constraint
147 layer->setConstraintExpression( 0, QStringLiteral( "col0 is not null" ) );
148 layer->setFieldConstraint( 0, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft );
149 // build a form for this feature
150 QgsAttributeForm form3( layer );
151 form3.setFeature( ft );
152 ww = qobject_cast<QgsEditorWidgetWrapper *>( form3.mWidgets[0] );
153
154 // set value to 1
155 ww->setValues( 1, QVariantList() );
156 QCOMPARE( constraintsLabel( &form3, ww )->text(), validLabel );
157
158 // set value to null
159 ww->setValues( QVariant(), QVariantList() );
160 QCOMPARE( constraintsLabel( &form3, ww )->text(), warningLabel );
161
162 // set value to 1
163 ww->setValues( 1, QVariantList() );
164 QCOMPARE( constraintsLabel( &form3, ww )->text(), validLabel );
165 }
166
testFieldMultiConstraints()167 void TestQgsAttributeForm::testFieldMultiConstraints()
168 {
169 // make a temporary layer to check through
170 const QString def = QStringLiteral( "Point?field=col0:integer&field=col1:integer&field=col2:integer&field=col3:integer" );
171 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
172
173 // add features to the vector layer
174 QgsFeature ft( layer->dataProvider()->fields(), 1 );
175 ft.setAttribute( QStringLiteral( "col0" ), 0 );
176 ft.setAttribute( QStringLiteral( "col1" ), 1 );
177 ft.setAttribute( QStringLiteral( "col2" ), 2 );
178 ft.setAttribute( QStringLiteral( "col3" ), 3 );
179
180 // set constraints for each field
181 layer->setConstraintExpression( 0, QString() );
182 layer->setConstraintExpression( 1, QString() );
183 layer->setConstraintExpression( 2, QString() );
184 layer->setConstraintExpression( 3, QString() );
185
186 // build a form for this feature
187 QgsAttributeForm form( layer );
188 form.setFeature( ft );
189
190 // testing stuff
191 const QSignalSpy spy( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
192 const QString val = QStringLiteral( "<font color=\"#259B24\">%1</font>" ).arg( QChar( 0x2714 ) );
193 const QString inv = QStringLiteral( "<font color=\"#FF9800\">%1</font>" ).arg( QChar( 0x2718 ) );
194
195 // get wrappers for each widget
196 QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3;
197 ww0 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
198 ww1 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
199 ww2 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
200 ww3 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[3] );
201
202 // no constraint so we expect an empty label
203 QVERIFY( constraintsLabel( &form, ww0 )->text().isEmpty() );
204 QVERIFY( constraintsLabel( &form, ww1 )->text().isEmpty() );
205 QVERIFY( constraintsLabel( &form, ww2 )->text().isEmpty() );
206 QVERIFY( constraintsLabel( &form, ww3 )->text().isEmpty() );
207
208 // update constraint
209 layer->setConstraintExpression( 0, QStringLiteral( "col0 < (col1 * col2)" ) );
210 layer->setConstraintExpression( 1, QString() );
211 layer->setConstraintExpression( 2, QString() );
212 layer->setConstraintExpression( 3, QStringLiteral( "col0 = 2" ) );
213
214 QgsAttributeForm form2( layer );
215 form2.setFeature( ft );
216 ww0 = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[0] );
217 ww1 = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[1] );
218 ww2 = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[2] );
219 ww3 = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[3] );
220 QSignalSpy spy2( &form2, SIGNAL( widgetValueChanged( QString, QVariant, bool ) ) );
221
222 // change value
223 ww0->setValues( 2, QVariantList() ); // update col0
224 QCOMPARE( spy2.count(), 1 );
225
226 QCOMPARE( constraintsLabel( &form2, ww0 )->text(), inv ); // 2 < ( 1 + 2 )
227 QCOMPARE( constraintsLabel( &form2, ww1 )->text(), QString() );
228 QCOMPARE( constraintsLabel( &form2, ww2 )->text(), QString() );
229 QCOMPARE( constraintsLabel( &form2, ww3 )->text(), val ); // 2 = 2
230
231 // change value
232 spy2.clear();
233 ww0->setValues( 1, QVariantList() ); // update col0
234 QCOMPARE( spy2.count(), 1 );
235
236 QCOMPARE( constraintsLabel( &form2, ww0 )->text(), val ); // 1 < ( 1 + 2 )
237 QCOMPARE( constraintsLabel( &form2, ww1 )->text(), QString() );
238 QCOMPARE( constraintsLabel( &form2, ww2 )->text(), QString() );
239 QCOMPARE( constraintsLabel( &form2, ww3 )->text(), inv ); // 2 = 1
240 }
241
testOKButtonStatus()242 void TestQgsAttributeForm::testOKButtonStatus()
243 {
244 // make a temporary vector layer
245 const QString def = QStringLiteral( "Point?field=col0:integer" );
246 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
247
248 // add a feature to the vector layer
249 QgsFeature ft( layer->dataProvider()->fields(), 1 );
250 ft.setAttribute( QStringLiteral( "col0" ), 0 );
251 ft.setValid( true );
252
253 // set constraint
254 layer->setConstraintExpression( 0, QString() );
255
256 // build a form for this feature
257 QgsAttributeForm form( layer );
258 form.setFeature( ft );
259
260 QPushButton *okButton = form.mButtonBox->button( QDialogButtonBox::Ok );
261
262 // get wrapper
263 QgsEditorWidgetWrapper *ww = nullptr;
264 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
265
266 // testing stuff
267 const QSignalSpy spy1( &form, SIGNAL( attributeChanged( QString, QVariant ) ) );
268 const QSignalSpy spy2( layer, SIGNAL( editingStarted() ) );
269 const QSignalSpy spy3( layer, SIGNAL( editingStopped() ) );
270
271 // no constraint but layer not editable : OK button disabled
272 QCOMPARE( layer->isEditable(), false );
273 QCOMPARE( okButton->isEnabled(), false );
274
275 // no constraint and editable layer : OK button enabled
276 layer->startEditing();
277 QCOMPARE( spy2.count(), 1 );
278 QCOMPARE( layer->isEditable(), true );
279 QCOMPARE( okButton->isEnabled(), true );
280
281 // invalid constraint and editable layer : OK button disabled
282 layer->setConstraintExpression( 0, QStringLiteral( "col0 = 0" ) );
283 QgsAttributeForm form2( layer );
284 form2.setFeature( ft );
285 ww = qobject_cast<QgsEditorWidgetWrapper *>( form2.mWidgets[0] );
286 okButton = form2.mButtonBox->button( QDialogButtonBox::Ok );
287 ww->setValues( 1, QVariantList() );
288 QCOMPARE( okButton->isEnabled(), false );
289
290 // valid constraint and editable layer : OK button enabled
291 layer->setConstraintExpression( 0, QStringLiteral( "col0 = 2" ) );
292 QgsAttributeForm form3( layer );
293 form3.setFeature( ft );
294 ww = qobject_cast<QgsEditorWidgetWrapper *>( form3.mWidgets[0] );
295 okButton = form3.mButtonBox->button( QDialogButtonBox::Ok );
296
297 ww->setValues( 2, QVariantList() );
298 QCOMPARE( okButton->isEnabled(), true );
299
300 // valid constraint and not editable layer : OK button disabled
301 layer->rollBack();
302 QCOMPARE( spy3.count(), 1 );
303 QCOMPARE( layer->isEditable(), false );
304 QCOMPARE( okButton->isEnabled(), false );
305
306 // set soft constraint
307 layer->setFieldConstraint( 0, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft );
308 QgsAttributeForm form4( layer );
309 form4.setFeature( ft );
310 ww = qobject_cast<QgsEditorWidgetWrapper *>( form4.mWidgets[0] );
311 okButton = form4.mButtonBox->button( QDialogButtonBox::Ok );
312 ww->setValues( 1, QVariantList() );
313 QVERIFY( !okButton->isEnabled() );
314 layer->startEditing();
315 // just a soft constraint, so OK should be enabled
316 QVERIFY( okButton->isEnabled() );
317 layer->rollBack();
318 QVERIFY( !okButton->isEnabled() );
319 }
320
testDynamicForm()321 void TestQgsAttributeForm::testDynamicForm()
322 {
323 // make temporary layers
324 const QString defA = QStringLiteral( "Point?field=id_a:integer" );
325 QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
326
327 const QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
328 QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
329
330 const QString defC = QStringLiteral( "Point?field=id_c:integer&field=col0:integer" );
331 QgsVectorLayer *layerC = new QgsVectorLayer( defC, QStringLiteral( "layerC" ), QStringLiteral( "memory" ) );
332
333 // join configuration
334 QgsVectorLayerJoinInfo infoJoinAB;
335 infoJoinAB.setTargetFieldName( QStringLiteral( "id_a" ) );
336 infoJoinAB.setJoinLayer( layerB );
337 infoJoinAB.setJoinFieldName( QStringLiteral( "id_b" ) );
338 infoJoinAB.setDynamicFormEnabled( true );
339
340 layerA->addJoin( infoJoinAB );
341
342 QgsVectorLayerJoinInfo infoJoinAC;
343 infoJoinAC.setTargetFieldName( QStringLiteral( "id_a" ) );
344 infoJoinAC.setJoinLayer( layerC );
345 infoJoinAC.setJoinFieldName( QStringLiteral( "id_c" ) );
346 infoJoinAC.setDynamicFormEnabled( true );
347
348 layerA->addJoin( infoJoinAC );
349
350 // add features for main layer
351 QgsFeature ftA( layerA->fields() );
352 ftA.setAttribute( QStringLiteral( "id_a" ), 0 );
353 layerA->startEditing();
354 layerA->addFeature( ftA );
355 layerA->commitChanges();
356
357 // add features for joined layers
358 QgsFeature ft0B( layerB->fields() );
359 ft0B.setAttribute( QStringLiteral( "id_b" ), 30 );
360 ft0B.setAttribute( QStringLiteral( "col0" ), 10 );
361 layerB->startEditing();
362 layerB->addFeature( ft0B );
363 layerB->commitChanges();
364
365 QgsFeature ft1B( layerB->fields() );
366 ft1B.setAttribute( QStringLiteral( "id_b" ), 31 );
367 ft1B.setAttribute( QStringLiteral( "col0" ), 11 );
368 layerB->startEditing();
369 layerB->addFeature( ft1B );
370 layerB->commitChanges();
371
372 QgsFeature ft0C( layerC->fields() );
373 ft0C.setAttribute( QStringLiteral( "id_c" ), 32 );
374 ft0C.setAttribute( QStringLiteral( "col0" ), 12 );
375 layerC->startEditing();
376 layerC->addFeature( ft0C );
377 layerC->commitChanges();
378
379 QgsFeature ft1C( layerC->fields() );
380 ft1C.setAttribute( QStringLiteral( "id_c" ), 31 );
381 ft1C.setAttribute( QStringLiteral( "col0" ), 13 );
382 layerC->startEditing();
383 layerC->addFeature( ft1C );
384 layerC->commitChanges();
385
386 // build a form with feature A
387 QgsAttributeForm form( layerA );
388 form.setMode( QgsAttributeEditorContext::AddFeatureMode );
389 form.setFeature( ftA );
390
391 // test that there's no joined feature by default
392 QgsEditorWidgetWrapper *ww = nullptr;
393
394 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
395 QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
396 QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
397
398 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
399 QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
400 QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
401
402 // change layerA join id field to join with layerB
403 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 30 ) );
404
405 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
406 QCOMPARE( ww->field().name(), QString( "id_a" ) );
407 QCOMPARE( ww->value(), QVariant( 30 ) );
408
409 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
410 QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
411 QCOMPARE( ww->value(), QVariant( 10 ) );
412
413 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
414 QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
415 QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
416
417 // change layerA join id field to join with layerC
418 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 32 ) );
419
420 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
421 QCOMPARE( ww->field().name(), QString( "id_a" ) );
422 QCOMPARE( ww->value(), QVariant( 32 ) );
423
424 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
425 QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
426 QCOMPARE( ww->value(), QVariant( QVariant::Int ) );
427
428 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
429 QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
430 QCOMPARE( ww->value(), QVariant( 12 ) );
431
432 // change layerA join id field to join with layerA and layerC
433 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 31 ) );
434
435 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
436 QCOMPARE( ww->field().name(), QString( "id_a" ) );
437 QCOMPARE( ww->value(), QVariant( 31 ) );
438
439 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
440 QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
441 QCOMPARE( ww->value(), QVariant( 11 ) );
442
443 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
444 QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
445 QCOMPARE( ww->value(), QVariant( 13 ) );
446
447 // clean
448 delete layerA;
449 delete layerB;
450 delete layerC;
451 }
452
testConstraintsOnJoinedFields()453 void TestQgsAttributeForm::testConstraintsOnJoinedFields()
454 {
455 const QString validLabel = QStringLiteral( "<font color=\"#259B24\">%1</font>" ).arg( QChar( 0x2714 ) );
456 const QString warningLabel = QStringLiteral( "<font color=\"#FFC107\">%1</font>" ).arg( QChar( 0x2718 ) );
457
458 // make temporary layers
459 const QString defA = QStringLiteral( "Point?field=id_a:integer" );
460 QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
461
462 const QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
463 QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
464
465 // set constraints on joined layer
466 layerB->setConstraintExpression( 1, QStringLiteral( "col0 < 10" ) );
467 layerB->setFieldConstraint( 1, QgsFieldConstraints::ConstraintExpression, QgsFieldConstraints::ConstraintStrengthSoft );
468
469 // join configuration
470 QgsVectorLayerJoinInfo infoJoinAB;
471 infoJoinAB.setTargetFieldName( QStringLiteral( "id_a" ) );
472 infoJoinAB.setJoinLayer( layerB );
473 infoJoinAB.setJoinFieldName( QStringLiteral( "id_b" ) );
474 infoJoinAB.setDynamicFormEnabled( true );
475
476 layerA->addJoin( infoJoinAB );
477
478 // add features for main layer
479 QgsFeature ftA( layerA->fields() );
480 ftA.setAttribute( QStringLiteral( "id_a" ), 1 );
481 layerA->startEditing();
482 layerA->addFeature( ftA );
483 layerA->commitChanges();
484
485 // add features for joined layer
486 QgsFeature ft0B( layerB->fields() );
487 ft0B.setAttribute( QStringLiteral( "id_b" ), 30 );
488 ft0B.setAttribute( QStringLiteral( "col0" ), 9 );
489 layerB->startEditing();
490 layerB->addFeature( ft0B );
491 layerB->commitChanges();
492
493 QgsFeature ft1B( layerB->fields() );
494 ft1B.setAttribute( QStringLiteral( "id_b" ), 31 );
495 ft1B.setAttribute( QStringLiteral( "col0" ), 11 );
496 layerB->startEditing();
497 layerB->addFeature( ft1B );
498 layerB->commitChanges();
499
500 // build a form for this feature
501 QgsAttributeForm form( layerA );
502 form.setMode( QgsAttributeEditorContext::AddFeatureMode );
503 form.setFeature( ftA );
504
505 // change layerA join id field
506 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 30 ) );
507
508 // compare
509 QgsEditorWidgetWrapper *ww = nullptr;
510 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
511 QCOMPARE( constraintsLabel( &form, ww )->text(), validLabel );
512
513 // change layerA join id field
514 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 31 ) );
515
516 // compare
517 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
518 QCOMPARE( constraintsLabel( &form, ww )->text(), warningLabel );
519 }
520
testEditableJoin()521 void TestQgsAttributeForm::testEditableJoin()
522 {
523 // make temporary layers
524 const QString defA = QStringLiteral( "Point?field=id_a:integer" );
525 QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
526
527 const QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
528 QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
529
530 const QString defC = QStringLiteral( "Point?field=id_c:integer&field=col0:integer" );
531 QgsVectorLayer *layerC = new QgsVectorLayer( defC, QStringLiteral( "layerC" ), QStringLiteral( "memory" ) );
532
533 // join configuration
534 QgsVectorLayerJoinInfo infoJoinAB;
535 infoJoinAB.setTargetFieldName( QStringLiteral( "id_a" ) );
536 infoJoinAB.setJoinLayer( layerB );
537 infoJoinAB.setJoinFieldName( QStringLiteral( "id_b" ) );
538 infoJoinAB.setDynamicFormEnabled( true );
539 infoJoinAB.setEditable( true );
540
541 layerA->addJoin( infoJoinAB );
542
543 QgsVectorLayerJoinInfo infoJoinAC;
544 infoJoinAC.setTargetFieldName( QStringLiteral( "id_a" ) );
545 infoJoinAC.setJoinLayer( layerC );
546 infoJoinAC.setJoinFieldName( QStringLiteral( "id_c" ) );
547 infoJoinAC.setDynamicFormEnabled( true );
548 infoJoinAC.setEditable( false );
549
550 layerA->addJoin( infoJoinAC );
551
552 // add features for main layer
553 QgsFeature ftA( layerA->fields() );
554 ftA.setAttribute( QStringLiteral( "id_a" ), 31 );
555 layerA->startEditing();
556 layerA->addFeature( ftA );
557 layerA->commitChanges();
558
559 // add features for joined layers
560 QgsFeature ft0B( layerB->fields() );
561 ft0B.setAttribute( QStringLiteral( "id_b" ), 31 );
562 ft0B.setAttribute( QStringLiteral( "col0" ), 11 );
563 layerB->startEditing();
564 layerB->addFeature( ft0B );
565 layerB->commitChanges();
566
567 QgsFeature ft0C( layerC->fields() );
568 ft0C.setAttribute( QStringLiteral( "id_c" ), 31 );
569 ft0C.setAttribute( QStringLiteral( "col0" ), 13 );
570 layerC->startEditing();
571 layerC->addFeature( ft0C );
572 layerC->commitChanges();
573
574 // start editing layers
575 layerA->startEditing();
576 layerB->startEditing();
577 layerC->startEditing();
578
579 // build a form with feature A
580 ftA = layerA->getFeature( 1 );
581
582 QgsAttributeForm form( layerA );
583 form.setMode( QgsAttributeEditorContext::SingleEditMode );
584 form.setFeature( ftA );
585
586 // change layerA join id field to join with layerB and layerC
587 QgsEditorWidgetWrapper *ww = nullptr;
588
589 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
590 QCOMPARE( ww->field().name(), QString( "id_a" ) );
591 QCOMPARE( ww->value(), QVariant( 31 ) );
592
593 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
594 QCOMPARE( ww->field().name(), QString( "layerB_col0" ) );
595 QCOMPARE( ww->value(), QVariant( 11 ) );
596
597 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
598 QCOMPARE( ww->field().name(), QString( "layerC_col0" ) );
599 QCOMPARE( ww->value(), QVariant( 13 ) );
600
601 // test if widget is enabled for layerA
602 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
603 QCOMPARE( ww->widget()->isEnabled(), true );
604
605 // test if widget is enabled for layerB
606 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
607 QCOMPARE( ww->widget()->isEnabled(), true );
608
609 // test if widget is disabled for layerC
610 ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
611 QCOMPARE( ww->widget()->isEnabled(), false );
612
613 // change attributes
614 form.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant( 333 ) );
615 form.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant( 444 ) );
616 form.save();
617
618 // commit changes
619 layerA->commitChanges();
620 layerB->commitChanges();
621 layerC->commitChanges();
622
623 // check attributes
624 ft0B = layerB->getFeature( 1 );
625 QCOMPARE( ft0B.attribute( "col0" ), QVariant( 333 ) );
626
627 ft0C = layerC->getFeature( 1 );
628 QCOMPARE( ft0C.attribute( "col0" ), QVariant( 13 ) );
629
630 // all editor widget must have a multi edit button
631 layerA->startEditing();
632 layerB->startEditing();
633 layerC->startEditing();
634 layerA->select( ftA.id() );
635 form.setMode( QgsAttributeEditorContext::MultiEditMode );
636
637 // multi edit button must be displayed for A
638 QgsAttributeFormEditorWidget *formWidget = qobject_cast<QgsAttributeFormEditorWidget *>( form.mFormWidgets[1] );
639 QVERIFY( formWidget->mMultiEditButton->parent() );
640
641 // multi edit button must be displayed for B (join is editable)
642 formWidget = qobject_cast<QgsAttributeFormEditorWidget *>( form.mFormWidgets[1] );
643 QVERIFY( formWidget->mMultiEditButton->parent() );
644
645 // multi edit button must not be displayed for C (join is not editable)
646 formWidget = qobject_cast<QgsAttributeFormEditorWidget *>( form.mFormWidgets[2] );
647 QVERIFY( !formWidget->mMultiEditButton->parent() );
648
649 // clean
650 delete layerA;
651 delete layerB;
652 delete layerC;
653 }
654
testUpsertOnEdit()655 void TestQgsAttributeForm::testUpsertOnEdit()
656 {
657 // make temporary layers
658 const QString defA = QStringLiteral( "Point?field=id_a:integer" );
659 QgsVectorLayer *layerA = new QgsVectorLayer( defA, QStringLiteral( "layerA" ), QStringLiteral( "memory" ) );
660
661 const QString defB = QStringLiteral( "Point?field=id_b:integer&field=col0:integer" );
662 QgsVectorLayer *layerB = new QgsVectorLayer( defB, QStringLiteral( "layerB" ), QStringLiteral( "memory" ) );
663
664 const QString defC = QStringLiteral( "Point?field=id_c:integer&field=col0:integer" );
665 QgsVectorLayer *layerC = new QgsVectorLayer( defC, QStringLiteral( "layerC" ), QStringLiteral( "memory" ) );
666
667 // join configuration
668 QgsVectorLayerJoinInfo infoJoinAB;
669 infoJoinAB.setTargetFieldName( QStringLiteral( "id_a" ) );
670 infoJoinAB.setJoinLayer( layerB );
671 infoJoinAB.setJoinFieldName( QStringLiteral( "id_b" ) );
672 infoJoinAB.setDynamicFormEnabled( true );
673 infoJoinAB.setEditable( true );
674 infoJoinAB.setUpsertOnEdit( true );
675
676 layerA->addJoin( infoJoinAB );
677
678 QgsVectorLayerJoinInfo infoJoinAC;
679 infoJoinAC.setTargetFieldName( QStringLiteral( "id_a" ) );
680 infoJoinAC.setJoinLayer( layerC );
681 infoJoinAC.setJoinFieldName( QStringLiteral( "id_c" ) );
682 infoJoinAC.setDynamicFormEnabled( true );
683 infoJoinAC.setEditable( true );
684 infoJoinAC.setUpsertOnEdit( false );
685
686 layerA->addJoin( infoJoinAC );
687
688 // add features for main layer
689 QgsFeature ft0A( layerA->fields() );
690 ft0A.setAttribute( QStringLiteral( "id_a" ), 31 );
691 layerA->startEditing();
692 layerA->addFeature( ft0A );
693 layerA->commitChanges();
694
695 // add features for joined layers
696 QgsFeature ft0B( layerB->fields() );
697 ft0B.setAttribute( QStringLiteral( "id_b" ), 33 );
698 ft0B.setAttribute( QStringLiteral( "col0" ), 11 );
699 layerB->startEditing();
700 layerB->addFeature( ft0B );
701 layerB->commitChanges();
702
703 QgsFeature ft0C( layerC->fields() );
704 ft0C.setAttribute( QStringLiteral( "id_c" ), 31 );
705 ft0C.setAttribute( QStringLiteral( "col0" ), 13 );
706 layerC->startEditing();
707 layerC->addFeature( ft0C );
708 layerC->commitChanges();
709
710 // get committed feature from layerA
711 QgsFeature feature;
712 QString filter = QgsExpression::createFieldEqualityExpression( QStringLiteral( "id_a" ), 31 );
713
714 QgsFeatureRequest request;
715 request.setFilterExpression( filter );
716 request.setLimit( 1 );
717 layerA->getFeatures( request ).nextFeature( ft0A );
718
719 // start editing layers
720 layerA->startEditing();
721 layerB->startEditing();
722 layerC->startEditing();
723
724 // build a form with feature A
725 QgsAttributeForm form( layerA );
726 form.setMode( QgsAttributeEditorContext::AddFeatureMode );
727 form.setFeature( ft0A );
728
729 // count features
730 QCOMPARE( ( int )layerA->featureCount(), 1 );
731 QCOMPARE( ( int )layerB->featureCount(), 1 );
732 QCOMPARE( ( int )layerC->featureCount(), 1 );
733
734 // add a new feature with null joined fields. Joined feature should not be
735 // added
736 form.changeAttribute( QStringLiteral( "id_a" ), QVariant( 32 ) );
737 form.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant() );
738 form.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant() );
739 form.save();
740
741 // commit
742 layerA->commitChanges();
743 layerB->commitChanges();
744 layerC->commitChanges();
745
746 // count features
747 QCOMPARE( ( int )layerA->featureCount(), 2 );
748 QCOMPARE( ( int )layerB->featureCount(), 1 );
749 QCOMPARE( ( int )layerC->featureCount(), 1 );
750
751 // start editing layers
752 layerA->startEditing();
753 layerB->startEditing();
754 layerC->startEditing();
755
756 // add a new feature with not null joined fields. Joined feature should be
757 // added
758 QgsAttributeForm form1( layerA );
759 form1.setMode( QgsAttributeEditorContext::AddFeatureMode );
760 form1.setFeature( ft0A );
761
762 form1.changeAttribute( QStringLiteral( "id_a" ), QVariant( 34 ) );
763 form1.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant( 3434 ) );
764 form1.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant( 343434 ) );
765 form1.save();
766
767 // commit
768 layerA->commitChanges();
769 layerB->commitChanges();
770 layerC->commitChanges();
771
772 // count features
773 QCOMPARE( ( int )layerA->featureCount(), 3 );
774 QCOMPARE( ( int )layerB->featureCount(), 2 );
775 QCOMPARE( ( int )layerC->featureCount(), 1 );
776
777 // check joined feature value
778 filter = QgsExpression::createFieldEqualityExpression( QStringLiteral( "id_a" ), 34 );
779
780 request.setFilterExpression( filter );
781 request.setLimit( 1 );
782 layerA->getFeatures( request ).nextFeature( feature );
783
784 QCOMPARE( feature.attribute( "layerB_col0" ), QVariant( 3434 ) );
785
786 // start editing layers
787 layerA->startEditing();
788 layerB->startEditing();
789 layerC->startEditing();
790
791 // create a target feature but update a joined feature. A new feature should
792 // be added in layerA and values in layerB should be updated
793 QgsAttributeForm form2( layerA );
794 form2.setMode( QgsAttributeEditorContext::AddFeatureMode );
795 form2.setFeature( ft0A );
796 form2.changeAttribute( QStringLiteral( "id_a" ), QVariant( 33 ) );
797 form2.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant( 3333 ) );
798 form2.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant( 323232 ) );
799 form2.save();
800
801 // commit
802 layerA->commitChanges();
803 layerB->commitChanges();
804 layerC->commitChanges();
805
806 // count features
807 QCOMPARE( ( int )layerA->featureCount(), 4 );
808 QCOMPARE( ( int )layerB->featureCount(), 2 );
809 QCOMPARE( ( int )layerC->featureCount(), 1 );
810
811 // check joined feature value
812 filter = QgsExpression::createFieldEqualityExpression( QStringLiteral( "id_a" ), 33 );
813
814 request.setFilterExpression( filter );
815 request.setLimit( 1 );
816 layerA->getFeatures( request ).nextFeature( feature );
817
818 QCOMPARE( feature.attribute( "layerB_col0" ), QVariant( 3333 ) );
819
820 // start editing layers
821 layerA->startEditing();
822 layerB->startEditing();
823 layerC->startEditing();
824
825 // update feature which does not exist in joined layer but with null joined
826 // fields. A new feature should NOT be added in joined layer
827 QgsAttributeForm form3( layerA );
828 form3.setMode( QgsAttributeEditorContext::SingleEditMode );
829 form3.setFeature( ft0A );
830 form3.changeAttribute( QStringLiteral( "id_a" ), QVariant( 31 ) );
831 form3.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant() );
832 form3.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant() );
833 form3.save();
834
835 // commit
836 layerA->commitChanges();
837 layerB->commitChanges();
838 layerC->commitChanges();
839
840 // count features
841 QCOMPARE( ( int )layerA->featureCount(), 4 );
842 QCOMPARE( ( int )layerB->featureCount(), 2 );
843 QCOMPARE( ( int )layerC->featureCount(), 1 );
844
845 // start editing layers
846 layerA->startEditing();
847 layerB->startEditing();
848 layerC->startEditing();
849
850 // update feature which does not exist in joined layer with NOT null joined
851 // fields. A new feature should be added in joined layer
852 QgsAttributeForm form4( layerA );
853 form4.setMode( QgsAttributeEditorContext::SingleEditMode );
854 form4.setFeature( ft0A );
855 form4.changeAttribute( QStringLiteral( "id_a" ), QVariant( 31 ) );
856 form4.changeAttribute( QStringLiteral( "layerB_col0" ), QVariant( 1111 ) );
857 form4.changeAttribute( QStringLiteral( "layerC_col0" ), QVariant( 3131 ) );
858 form4.save();
859
860 // commit
861 layerA->commitChanges();
862 layerB->commitChanges();
863 layerC->commitChanges();
864
865 // count features
866 QCOMPARE( ( int )layerA->featureCount(), 4 );
867 QCOMPARE( ( int )layerB->featureCount(), 3 );
868 QCOMPARE( ( int )layerC->featureCount(), 1 );
869
870 // check joined feature value
871 filter = QgsExpression::createFieldEqualityExpression( QStringLiteral( "id_a" ), 31 );
872
873 request.setFilterExpression( filter );
874 request.setLimit( 1 );
875 layerA->getFeatures( request ).nextFeature( feature );
876
877 QCOMPARE( feature.attribute( "layerB_col0" ), QVariant( 1111 ) );
878
879 // clean
880 delete layerA;
881 delete layerB;
882 delete layerC;
883 }
884
testFixAttributeForm()885 void TestQgsAttributeForm::testFixAttributeForm()
886 {
887 const QString def = QStringLiteral( "Point?field=id:integer&field=col1:integer" );
888 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "layer" ), QStringLiteral( "memory" ) );
889
890 QVERIFY( layer );
891
892 QgsFeature f( layer->fields() );
893 f.setAttribute( 0, 1 );
894 f.setAttribute( 1, 681 );
895
896 QgsAttributeForm form( layer );
897
898 form.setMode( QgsAttributeEditorContext::FixAttributeMode );
899 form.setFeature( f );
900
901 QgsEditorWidgetWrapper *ww = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
902 QCOMPARE( ww->field().name(), QString( "col1" ) );
903 QCOMPARE( ww->value(), QVariant( 681 ) );
904
905 // now change the value
906 ww->setValue( QVariant( 630 ) );
907
908 // the value should be updated
909 QCOMPARE( ww->value(), QVariant( 630 ) );
910 // the feature is not saved yet, so contains the old value
911 QCOMPARE( form.feature().attribute( QStringLiteral( "col1" ) ), QVariant( 681 ) );
912 // now save the feature and enjoy its new value, but don't update the layer
913 QVERIFY( form.save() );
914 QCOMPARE( form.feature().attribute( QStringLiteral( "col1" ) ), QVariant( 630 ) );
915 QCOMPARE( ( int )layer->featureCount(), 0 );
916
917 delete layer;
918 }
919
testAttributeFormInterface()920 void TestQgsAttributeForm::testAttributeFormInterface()
921 {
922 // Issue https://github.com/qgis/QGIS/issues/29667
923 // we simulate a python code execution that would be triggered
924 // at form opening and that would modify the value of a widget.
925 // We want to check that emitted signal widgetValueChanged is
926 // correctly emitted with correct parameters
927
928 // make a temporary vector layer
929 const QString def = QStringLiteral( "Point?field=col0:integer" );
930 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
931 layer->setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() ) );
932
933 // add a feature to the vector layer
934 QgsFeature ft( layer->dataProvider()->fields(), 1 );
935 ft.setAttribute( QStringLiteral( "col0" ), 10 );
936
937 class MyInterface : public QgsAttributeFormInterface
938 {
939 public:
940 MyInterface( QgsAttributeForm *form )
941 : QgsAttributeFormInterface( form ) {}
942
943 virtual void featureChanged()
944 {
945 QgsAttributeForm *f = form();
946 QLineEdit *le = f->findChild<QLineEdit *>( "col0" );
947 le->setText( "100" );
948 }
949 };
950
951 // build a form for this feature
952 QgsAttributeForm form( layer );
953 form.addInterface( new MyInterface( &form ) );
954
955 bool set = false;
956 connect( &form, &QgsAttributeForm::widgetValueChanged, this,
957 [&set]( const QString & attribute, const QVariant & newValue, bool attributeChanged )
958 {
959
960 // Check that our value set by the QgsAttributeFormInterface has correct parameters.
961 // attributeChanged has to be true because it won't be taken into account by others
962 // (QgsValueRelationWidgetWrapper for instance)
963 if ( attribute == "col0" && newValue.toInt() == 100 && attributeChanged )
964 set = true;
965 } );
966
967 form.setFeature( ft );
968 QVERIFY( set );
969 }
970
971
testDefaultValueUpdate()972 void TestQgsAttributeForm::testDefaultValueUpdate()
973 {
974 // make a temporary layer to check through
975 const QString def = QStringLiteral( "Point?field=col0:integer&field=col1:integer&field=col2:integer&field=col3:integer" );
976 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
977
978 //set defaultValueDefinitions
979 //col0 - no default value
980 //col1 - "col0"+1
981 //col2 - "col0"+"col1"
982 //col3 - "col2"
983
984 // set constraints for each field
985 layer->setDefaultValueDefinition( 1, QgsDefaultValue( QStringLiteral( "\"col0\"+1" ) ) );
986 layer->setDefaultValueDefinition( 2, QgsDefaultValue( QStringLiteral( "\"col0\"+\"col1\"" ) ) );
987 layer->setDefaultValueDefinition( 3, QgsDefaultValue( QStringLiteral( "\"col2\"" ) ) );
988
989 layer->startEditing();
990
991 // build a form for this feature
992 QgsFeature ft( layer->dataProvider()->fields(), 1 );
993 ft.setAttribute( QStringLiteral( "col0" ), 0 );
994 QgsAttributeForm form( layer );
995 form.setMode( QgsAttributeEditorContext::AddFeatureMode );
996 form.setFeature( ft );
997
998 // get wrappers for each widget
999 QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3;
1000 ww0 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
1001 ww1 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
1002 ww2 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
1003 ww3 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[3] );
1004
1005 //set value in col0:
1006 ww0->setValue( 5 );
1007
1008 //we expect
1009 //col0 - 5
1010 //col1 - 6
1011 //col2 - 11
1012 //col3 - 11
1013
1014 QCOMPARE( ww0->value().toInt(), 5 );
1015 QCOMPARE( ww1->value().toInt(), 6 );
1016 QCOMPARE( ww2->value().toInt(), 11 );
1017 QCOMPARE( ww3->value().toInt(), 11 );
1018
1019 //set value in col1:
1020 ww1->setValue( 10 );
1021
1022 //we expect
1023 //col0 - 5
1024 //col1 - 10
1025 //col2 - 15
1026 //col3 - 15
1027
1028 QCOMPARE( ww0->value().toInt(), 5 );
1029 QCOMPARE( ww1->value().toInt(), 10 );
1030 QCOMPARE( ww2->value().toInt(), 15 );
1031 QCOMPARE( ww3->value().toInt(), 15 );
1032 }
1033
testDefaultValueUpdateRecursion()1034 void TestQgsAttributeForm::testDefaultValueUpdateRecursion()
1035 {
1036 // make a temporary layer to check through
1037 const QString def = QStringLiteral( "Point?field=col0:integer&field=col1:integer&field=col2:integer&field=col3:integer" );
1038 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
1039
1040 //let's make a recursion
1041 //col0 - COALESCE( 0, "col3"+1)
1042 //col1 - COALESCE( 0, "col0"+1)
1043 //col2 - COALESCE( 0, "col1"+1)
1044 //col3 - COALESCE( 0, "col2"+1)
1045
1046 // set constraints for each field
1047 layer->setDefaultValueDefinition( 0, QgsDefaultValue( QStringLiteral( "\"col3\"+1" ) ) );
1048 layer->setDefaultValueDefinition( 1, QgsDefaultValue( QStringLiteral( "\"col0\"+1" ) ) );
1049 layer->setDefaultValueDefinition( 2, QgsDefaultValue( QStringLiteral( "\"col1\"+1" ) ) );
1050 layer->setDefaultValueDefinition( 3, QgsDefaultValue( QStringLiteral( "\"col2\"+1" ) ) );
1051
1052 layer->startEditing();
1053
1054 // build a form for this feature
1055 QgsFeature ft( layer->dataProvider()->fields(), 1 );
1056 ft.setAttribute( QStringLiteral( "col0" ), 0 );
1057 QgsAttributeForm form( layer );
1058 form.setMode( QgsAttributeEditorContext::AddFeatureMode );
1059 form.setFeature( ft );
1060
1061 // get wrappers for each widget
1062 QgsEditorWidgetWrapper *ww0, *ww1, *ww2, *ww3;
1063 ww0 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[0] );
1064 ww1 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[1] );
1065 ww2 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[2] );
1066 ww3 = qobject_cast<QgsEditorWidgetWrapper *>( form.mWidgets[3] );
1067
1068 //set value in col0:
1069 ww0->setValue( 20 );
1070
1071 //we expect
1072 //col0 - 20
1073 //col1 - 21
1074 //col2 - 22
1075 //col3 - 23
1076
1077 QCOMPARE( ww0->value().toInt(), 20 );
1078 QCOMPARE( ww1->value().toInt(), 21 );
1079 QCOMPARE( ww2->value().toInt(), 22 );
1080 QCOMPARE( ww3->value().toInt(), 23 );
1081
1082 //set value in col2:
1083 ww2->setValue( 30 );
1084
1085 //we expect
1086 //col0 - 32
1087 //col1 - 33
1088 //col2 - 30
1089 //col3 - 31
1090
1091 QCOMPARE( ww0->value().toInt(), 32 );
1092 QCOMPARE( ww1->value().toInt(), 33 );
1093 QCOMPARE( ww2->value().toInt(), 30 );
1094 QCOMPARE( ww3->value().toInt(), 31 );
1095
1096 //set value in col0 again:
1097 ww0->setValue( 40 );
1098
1099 //we expect
1100 //col0 - 40
1101 //col1 - 41
1102 //col2 - 42
1103 //col3 - 43
1104
1105 QCOMPARE( ww0->value().toInt(), 40 );
1106 QCOMPARE( ww1->value().toInt(), 41 );
1107 QCOMPARE( ww2->value().toInt(), 42 );
1108 QCOMPARE( ww3->value().toInt(), 43 );
1109 }
1110
testSameFieldSync()1111 void TestQgsAttributeForm::testSameFieldSync()
1112 {
1113 // Check that widget synchronisation works when a form contains the same field several times
1114 // and there is no issues when editing
1115
1116 // make a temporary vector layer
1117 const QString def = QStringLiteral( "Point?field=col0:integer" );
1118 QgsVectorLayer *layer = new QgsVectorLayer( def, QStringLiteral( "test" ), QStringLiteral( "memory" ) );
1119 layer->setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() ) );
1120
1121 // add a feature to the vector layer
1122 QgsFeature ft( layer->dataProvider()->fields(), 1 );
1123 ft.setAttribute( QStringLiteral( "col0" ), 10 );
1124
1125 // add same field twice so they get synced
1126 QgsEditFormConfig editFormConfig = layer->editFormConfig();
1127 editFormConfig.clearTabs();
1128 editFormConfig.addTab( new QgsAttributeEditorField( "col0", 0, editFormConfig.invisibleRootContainer() ) );
1129 editFormConfig.addTab( new QgsAttributeEditorField( "col0", 0, editFormConfig.invisibleRootContainer() ) );
1130 editFormConfig.setLayout( QgsEditFormConfig::TabLayout );
1131 layer->setEditFormConfig( editFormConfig );
1132
1133 layer->startEditing();
1134
1135 // build a form for this feature
1136 QgsAttributeForm form( layer );
1137 form.setFeature( ft );
1138
1139 QList<QLineEdit *> les = form.findChildren<QLineEdit *>( "col0" );
1140 QCOMPARE( les.count(), 2 );
1141
1142 les[0]->setCursorPosition( 1 );
1143 QTest::keyClick( les[0], Qt::Key_2 );
1144 QTest::keyClick( les[0], Qt::Key_3 );
1145
1146 QCOMPARE( les[0]->text(), QString( "1230" ) );
1147 QCOMPARE( les[0]->cursorPosition(), 3 );
1148 QCOMPARE( les[1]->text(), QString( "1230" ) );
1149 QCOMPARE( les[1]->cursorPosition(), 4 );
1150 }
1151
testZeroDoubles()1152 void TestQgsAttributeForm::testZeroDoubles()
1153 {
1154 // See issue GH #34118
1155 const QString def = QStringLiteral( "Point?field=col0:double" );
1156 QgsVectorLayer layer { def, QStringLiteral( "test" ), QStringLiteral( "memory" ) };
1157 layer.setEditorWidgetSetup( 0, QgsEditorWidgetSetup( QStringLiteral( "TextEdit" ), QVariantMap() ) );
1158 QgsFeature ft( layer.dataProvider()->fields(), 1 );
1159 ft.setAttribute( QStringLiteral( "col0" ), 0.0 );
1160 QgsAttributeForm form( &layer );
1161 form.setFeature( ft );
1162 const QList<QLineEdit *> les = form.findChildren<QLineEdit *>( "col0" );
1163 QCOMPARE( les.count(), 1 );
1164 QCOMPARE( les.at( 0 )->text(), QStringLiteral( "0" ) );
1165 }
1166
1167 QGSTEST_MAIN( TestQgsAttributeForm )
1168 #include "testqgsattributeform.moc"
1169