1 /***************************************************************************
2                          testqgsproperty.cpp
3                          -------------------
4     begin                : April 2015
5     copyright            : (C) 2015 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgstest.h"
19 #include "qgsproperty.h"
20 #include "qgspropertycollection.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsapplication.h"
23 #include "qgscolorramp.h"
24 #include "qgssymbollayerutils.h"
25 #include "qgspropertytransformer.h"
26 #include <QObject>
27 
28 enum PropertyKeys
29 {
30   Property1,
31   Property2,
32   Property3,
33   Property4,
34 };
35 
36 class TestTransformer : public QgsPropertyTransformer
37 {
38   public:
39 
TestTransformer(double minValue,double maxValue)40     TestTransformer( double minValue, double maxValue )
41       : QgsPropertyTransformer( minValue, maxValue )
42     {
43 
44     }
45 
transformerType() const46     Type transformerType() const override { return SizeScaleTransformer; }
clone() const47     TestTransformer *clone() const override
48     {
49       return new TestTransformer( mMinValue, mMaxValue );
50     }
toExpression(const QString &) const51     QString toExpression( const QString & ) const override { return QString(); }
52 
53   private:
54 
transform(const QgsExpressionContext & context,const QVariant & value) const55     QVariant transform( const QgsExpressionContext &context, const QVariant &value ) const override
56     {
57       Q_UNUSED( context );
58 
59       if ( value.isNull() )
60         return -1.0;
61 
62       return value.toDouble() * 2;
63     }
64 
65 };
66 
67 
68 class TestQgsProperty : public QObject
69 {
70     Q_OBJECT
71 
72   private slots:
73     void initTestCase();// will be called before the first testfunction is executed.
74     void cleanupTestCase();// will be called after the last testfunction was executed.
75     void init();// will be called before each testfunction is executed.
76     void cleanup();// will be called after every testfunction.
77     void conversions(); //test QgsProperty static conversion methods
78     void invalid(); //test invalid properties
79     void staticProperty(); //test for QgsStaticProperty
80     void fieldBasedProperty(); //test for QgsFieldBasedProperty
81     void expressionBasedProperty(); //test for QgsExpressionBasedProperty
82     void equality();
83     void propertyTransformer(); //test for QgsPropertyTransformer
84     void propertyTransformerFromExpression(); // text converting expression into QgsPropertyTransformer
85     void genericNumericTransformer();
86     void genericNumericTransformerFromExpression(); // text converting expression to QgsGenericNumericTransformer
87     void sizeScaleTransformer(); //test for QgsSizeScaleTransformer
88     void sizeScaleTransformerFromExpression(); // text converting expression to QgsSizeScaleTransformer
89     void colorRampTransformer(); //test for QgsColorRampTransformer
90     void propertyToTransformer(); //test converting expression based property to transformer/expression pair
91     void asExpression(); //test converting property to expression
92     void propertyCollection(); //test for QgsPropertyCollection
93     void collectionStack(); //test for QgsPropertyCollectionStack
94     void curveTransform();
95     void asVariant();
96     void isProjectColor();
97     void referencedFieldsIgnoreContext();
98 
99   private:
100 
101     QgsPropertiesDefinition mDefinitions;
102     void checkCurveResult( const QList< QgsPointXY > &controlPoints, const QVector<double> &x, const QVector<double> &y );
103 
104 };
105 
initTestCase()106 void TestQgsProperty::initTestCase()
107 {
108   QgsApplication::init();
109   QgsApplication::initQgis();
110   mDefinitions.insert( Property1, QgsPropertyDefinition( QStringLiteral( "p1" ), QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
111   mDefinitions.insert( Property2, QgsPropertyDefinition( QStringLiteral( "p2" ), QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
112   mDefinitions.insert( Property3, QgsPropertyDefinition( QStringLiteral( "p3" ), QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
113   mDefinitions.insert( Property4, QgsPropertyDefinition( QStringLiteral( "p4" ), QgsPropertyDefinition::DataTypeString, QString(), QString() ) );
114 }
115 
cleanupTestCase()116 void TestQgsProperty::cleanupTestCase()
117 {
118   QgsApplication::exitQgis();
119 }
120 
init()121 void TestQgsProperty::init()
122 {
123 
124 }
125 
cleanup()126 void TestQgsProperty::cleanup()
127 {
128 
129 }
130 
conversions()131 void TestQgsProperty::conversions()
132 {
133   QgsExpressionContext context;
134 
135   //all these tests are done for both a property and a collection
136   QgsPropertyCollection collection;
137 
138   bool ok = false;
139 
140   //test color conversions
141 
142   //no color, should return defaultColor
143   QgsProperty c1 = QgsProperty::fromValue( QVariant(), true );
144   collection.setProperty( 0, c1 );
145   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
146   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
147   c1.setStaticValue( QColor( 255, 200, 100, 50 ) ); //color in qvariant
148   collection.property( 0 ).setStaticValue( QColor( 255, 200, 100, 50 ) ); //color in qvariant
149   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ), &ok ), QColor( 255, 200, 100, 50 ) );
150   QVERIFY( ok );
151   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
152   c1.setStaticValue( QColor() );  //invalid color in qvariant, should return default color
153   collection.property( 0 ).setStaticValue( QColor() );  //invalid color in qvariant, should return default color
154   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
155   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
156   c1.setStaticValue( QgsSymbolLayerUtils::encodeColor( QColor( 255, 200, 100, 50 ) ) ); //encoded color
157   collection.property( 0 ).setStaticValue( QgsSymbolLayerUtils::encodeColor( QColor( 255, 200, 100, 50 ) ) ); //encoded color
158   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
159   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 255, 200, 100, 50 ) );
160   c1.setStaticValue( "i am not a color" ); //badly encoded color, should return default color
161   collection.property( 0 ).setStaticValue( "i am not a color" ); //badly encoded color, should return default color
162   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
163   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ) ), QColor( 200, 210, 220 ) );
164   collection.property( 0 ).setStaticValue( QVariant( QVariant::String ) ); //null value
165   QCOMPARE( c1.valueAsColor( context, QColor( 200, 210, 220 ), &ok ), QColor( 200, 210, 220 ) );
166   QVERIFY( !ok );
167   QCOMPARE( collection.valueAsColor( 0, context, QColor( 200, 210, 220 ), &ok ), QColor( 200, 210, 220 ) );
168   QVERIFY( !ok );
169 
170   // test double conversions
171   QgsProperty d1 = QgsProperty::fromValue( QVariant(), true );
172   collection.setProperty( 1, d1 );
173   QCOMPARE( d1.valueAsDouble( context, -1.2, &ok ), -1.2 );
174   QVERIFY( !ok );
175   QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), -1.2 );
176   d1.setStaticValue( 12.3 ); //double in qvariant
177   collection.property( 1 ).setStaticValue( 12.3 ); //double in qvariant
178   QCOMPARE( d1.valueAsDouble( context, -1.2, &ok ), 12.3 );
179   QVERIFY( ok );
180   QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), 12.3 );
181   d1.setStaticValue( "15.6" ); //double as string
182   collection.property( 1 ).setStaticValue( "15.6" ); //double as string
183   QCOMPARE( d1.valueAsDouble( context, -1.2 ), 15.6 );
184   QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), 15.6 );
185   d1.setStaticValue( "i am not a double" ); //not a double, should return default value
186   collection.property( 1 ).setStaticValue( "i am not a double" ); //not a double, should return default value
187   QCOMPARE( d1.valueAsDouble( context, -1.2 ), -1.2 );
188   QCOMPARE( collection.valueAsDouble( 1, context, -1.2 ), -1.2 );
189   d1.setStaticValue( QVariant( QVariant::Double ) ); //null value
190   collection.property( 1 ).setStaticValue( QVariant( QVariant::Double ) ); //null value
191   QCOMPARE( d1.valueAsDouble( context, -1.2, &ok ), -1.2 );
192   QVERIFY( !ok );
193   QCOMPARE( collection.valueAsDouble( 1, context, -1.2, &ok ), -1.2 );
194   QVERIFY( !ok );
195 
196   // test integer conversions
197   QgsProperty i1 = QgsProperty::fromValue( QVariant(), true );
198   collection.setProperty( 2, i1 );
199   QCOMPARE( i1.valueAsInt( context, -11, &ok ), -11 );
200   QVERIFY( !ok );
201   QCOMPARE( collection.valueAsInt( 2, context, -11 ), -11 );
202   i1.setStaticValue( 13 ); //integer in qvariant
203   collection.property( 2 ).setStaticValue( 13 ); //integer in qvariant
204   QCOMPARE( i1.valueAsInt( context, -11, &ok ), 13 );
205   QVERIFY( ok );
206   QCOMPARE( collection.valueAsInt( 2, context, -11, &ok ), 13 );
207   QVERIFY( ok );
208   i1.setStaticValue( 13.9 ); //double in qvariant, should be rounded
209   collection.property( 2 ).setStaticValue( 13.9 ); //double in qvariant, should be rounded
210   QCOMPARE( i1.valueAsInt( context, -11 ), 14 );
211   QCOMPARE( collection.valueAsInt( 2, context, -11 ), 14 );
212   i1.setStaticValue( "15" ); //integer as string
213   collection.property( 2 ).setStaticValue( "15" ); //integer as string
214   QCOMPARE( i1.valueAsInt( context, -11 ), 15 );
215   QCOMPARE( collection.valueAsInt( 2, context, -11 ), 15 );
216   i1.setStaticValue( "15.9" ); //double as string, should be rounded
217   collection.property( 2 ).setStaticValue( "15.9" ); //double as string, should be rounded
218   QCOMPARE( i1.valueAsInt( context, -11 ), 16 );
219   QCOMPARE( collection.valueAsInt( 2, context, -11 ), 16 );
220   i1.setStaticValue( "i am not a int" ); //not a int, should return default value
221   collection.property( 2 ).setStaticValue( "i am not a int" ); //not a int, should return default value
222   QCOMPARE( i1.valueAsInt( context, -11 ), -11 );
223   QCOMPARE( collection.valueAsInt( 2, context, -11 ), -11 );
224   i1.setStaticValue( QVariant( QVariant::Int ) ); // null value
225   collection.property( 2 ).setStaticValue( QVariant( QVariant::Int ) ); // null value
226   QCOMPARE( i1.valueAsInt( context, -11, &ok ), -11 );
227   QVERIFY( !ok );
228   QCOMPARE( collection.valueAsInt( 2, context, -11, &ok ), -11 );
229   QVERIFY( !ok );
230 
231   // test boolean conversions
232   QgsProperty b1 = QgsProperty::fromValue( QVariant(), true );
233   collection.setProperty( 3, b1 );
234   QCOMPARE( b1.valueAsBool( context, false, &ok ), false );
235   QVERIFY( !ok );
236   QCOMPARE( b1.valueAsBool( context, true, &ok ), true );
237   QVERIFY( !ok );
238   QCOMPARE( collection.valueAsBool( 3, context, false ), false );
239   QCOMPARE( collection.valueAsBool( 3, context, true ), true );
240   b1.setStaticValue( true );
241   collection.property( 3 ).setStaticValue( true );
242   QCOMPARE( b1.valueAsBool( context, false, &ok ), true );
243   QVERIFY( ok );
244   QCOMPARE( b1.valueAsBool( context, true, &ok ), true );
245   QVERIFY( ok );
246   QCOMPARE( collection.valueAsBool( 3, context, false ), true );
247   QCOMPARE( collection.valueAsBool( 3, context, true ), true );
248   b1.setStaticValue( false );
249   collection.property( 3 ).setStaticValue( false );
250   QCOMPARE( b1.valueAsBool( context, false ), false );
251   QCOMPARE( b1.valueAsBool( context, true ), false );
252   QCOMPARE( collection.valueAsBool( 3, context, false ), false );
253   QCOMPARE( collection.valueAsBool( 3, context, true ), false );
254   b1.setStaticValue( 1 );
255   collection.property( 3 ).setStaticValue( 1 );
256   QCOMPARE( b1.valueAsBool( context, false ), true );
257   QCOMPARE( b1.valueAsBool( context, true ), true );
258   QCOMPARE( collection.valueAsBool( 3, context, false ), true );
259   QCOMPARE( collection.valueAsBool( 3, context, true ), true );
260   b1.setStaticValue( 0 );
261   collection.property( 3 ).setStaticValue( 0 );
262   QCOMPARE( b1.valueAsBool( context, false ), false );
263   QCOMPARE( b1.valueAsBool( context, true ), false );
264   QCOMPARE( collection.valueAsBool( 3, context, false ), false );
265   QCOMPARE( collection.valueAsBool( 3, context, true ), false );
266   b1.setStaticValue( "true" );
267   collection.property( 3 ).setStaticValue( "true" );
268   QCOMPARE( b1.valueAsBool( context, false ), true );
269   QCOMPARE( b1.valueAsBool( context, true ), true );
270   QCOMPARE( collection.valueAsBool( 3, context, false ), true );
271   QCOMPARE( collection.valueAsBool( 3, context, true ), true );
272   b1.setStaticValue( "" );
273   collection.property( 3 ).setStaticValue( "" );
274   QCOMPARE( b1.valueAsBool( context, false ), false );
275   QCOMPARE( b1.valueAsBool( context, true ), false );
276   QCOMPARE( collection.valueAsBool( 3, context, false ), false );
277   QCOMPARE( collection.valueAsBool( 3, context, true ), false );
278   b1.setStaticValue( QVariant( QVariant::Bool ) ); // null value
279   collection.property( 3 ).setStaticValue( QVariant( QVariant::Bool ) );
280   QCOMPARE( b1.valueAsBool( context, false ), false );
281   QCOMPARE( b1.valueAsBool( context, true ), true );
282   QCOMPARE( collection.valueAsBool( 3, context, false, &ok ), false );
283   QVERIFY( !ok );
284   QCOMPARE( collection.valueAsBool( 3, context, true, &ok ), true );
285   QVERIFY( !ok );
286 
287   // test string conversions
288   QgsProperty s1 = QgsProperty::fromValue( QVariant(), true );
289   collection.setProperty( 4, s1 );
290   QCOMPARE( s1.valueAsString( context, "n", &ok ), QStringLiteral( "n" ) );
291   QVERIFY( !ok );
292   QCOMPARE( collection.valueAsString( 4, context, "y", &ok ), QStringLiteral( "y" ) );
293   QVERIFY( !ok );
294   s1.setStaticValue( "s" );
295   collection.property( 4 ).setStaticValue( "s" );
296   QCOMPARE( s1.valueAsString( context, "n", &ok ), QStringLiteral( "s" ) );
297   QVERIFY( ok );
298   QCOMPARE( collection.valueAsString( 4, context, "y", &ok ), QStringLiteral( "s" ) );
299   QVERIFY( ok );
300   s1.setStaticValue( QVariant( QVariant::String ) );
301   collection.property( 4 ).setStaticValue( QVariant( QVariant::String ) );
302   QCOMPARE( s1.valueAsString( context, "n", &ok ), QStringLiteral( "n" ) );
303   QVERIFY( !ok );
304   QCOMPARE( collection.valueAsString( 4, context, "y", &ok ), QStringLiteral( "y" ) );
305   QVERIFY( !ok );
306 
307   // test datetime conversions
308   QDateTime dt = QDateTime( QDate( 2020, 1, 1 ), QTime( 0, 0, 0 ) );
309   QDateTime dt2 = QDateTime( QDate( 2010, 1, 1 ), QTime( 0, 0, 0 ) );
310   QgsProperty dt1 = QgsProperty::fromValue( QVariant(), true );
311   collection.setProperty( 5, dt1 );
312   QCOMPARE( d1.valueAsDateTime( context, dt, &ok ), dt );
313   QVERIFY( !ok );
314   QCOMPARE( collection.valueAsDateTime( 5, context, dt, &ok ), dt );
315   QVERIFY( !ok );
316   d1.setStaticValue( dt2 ); //datetime in qvariant
317   collection.property( 5 ).setStaticValue( dt2 ); //datetime in qvariant
318   QCOMPARE( d1.valueAsDateTime( context, dt, &ok ), dt2 );
319   QVERIFY( ok );
320   QCOMPARE( collection.valueAsDateTime( 5, context,  dt, &ok ), dt2 );
321   QVERIFY( ok );
322   d1.setStaticValue( "2010-01-01" ); //datetime as string
323   collection.property( 5 ).setStaticValue( "2010-01-01" ); //datetime as string
324   QCOMPARE( d1.valueAsDateTime( context, dt ), dt2 );
325   QCOMPARE( collection.valueAsDateTime( 5, context, dt ), dt2 );
326   d1.setStaticValue( "i am not a datetime" ); //not a datetime, should return default value
327   collection.property( 5 ).setStaticValue( "i am not a datetime" ); //not a double, should return default value
328   QCOMPARE( d1.valueAsDateTime( context, dt ), dt );
329   QCOMPARE( collection.valueAsDateTime( 5, context, dt ), dt );
330   d1.setStaticValue( QVariant( QVariant::DateTime ) ); // null value
331   collection.property( 5 ).setStaticValue( QVariant( QVariant::DateTime ) ); // null value
332   QCOMPARE( d1.valueAsDateTime( context, dt, &ok ), dt );
333   QVERIFY( !ok );
334   QCOMPARE( collection.valueAsDateTime( 5, context, dt, &ok ), dt );
335   QVERIFY( !ok );
336 }
337 
invalid()338 void TestQgsProperty::invalid()
339 {
340   QgsProperty p; //invalid property
341   QCOMPARE( p.propertyType(), QgsProperty::InvalidProperty );
342   QgsProperty p2( p );
343   QCOMPARE( p2.propertyType(), QgsProperty::InvalidProperty );
344   QgsProperty p3 = QgsProperty::fromValue( 5 );
345   p3 = p;
346   QCOMPARE( p3.propertyType(), QgsProperty::InvalidProperty );
347 
348 }
349 
staticProperty()350 void TestQgsProperty::staticProperty()
351 {
352   QgsExpressionContext context;
353   QgsProperty property = QgsProperty::fromValue( QStringLiteral( "test" ), true );
354   QCOMPARE( property.propertyType(), QgsProperty::StaticProperty );
355   QVERIFY( property.isActive() );
356   QVERIFY( property.referencedFields( context ).isEmpty() );
357   QCOMPARE( property.value( context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "test" ) );
358   property.setActive( false );
359   QVERIFY( property.referencedFields( context ).isEmpty() );
360   QVERIFY( !property.isActive() );
361   QCOMPARE( property.value( context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "default" ) );
362   property.setStaticValue( 5 );
363   property.setActive( true );
364   QCOMPARE( property.value( context ).toInt(), 5 );
365 
366   //saving and restoring
367 
368   //create a test dom element
369   QDomImplementation DomImplementation;
370   QDomDocumentType documentType =
371     DomImplementation.createDocumentType(
372       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
373   QDomDocument doc( documentType );
374 
375   QgsProperty p1;
376   p1.setActive( true );
377   p1.setStaticValue( "test" );
378   p1.setTransformer( new TestTransformer( 10, 20 ) );
379 
380   QVariant element = p1.toVariant();
381 
382   QgsProperty r1;
383   r1.loadVariant( element );
384   QVERIFY( r1.isActive() );
385   QVERIFY( r1.transformer() );
386   QCOMPARE( r1.staticValue(), QVariant( "test" ) );
387 
388   p1.setActive( false );
389   element = p1.toVariant();
390   r1.loadVariant( element );
391   QVERIFY( !r1.isActive() );
392 
393   //saving/restoring different types
394   p1.setStaticValue( QVariant( 5 ) ); //int
395   element = p1.toVariant();
396   r1.loadVariant( element );
397   QCOMPARE( r1.staticValue(), p1.staticValue() );
398   p1.setStaticValue( QVariant( 5.7 ) ); //double
399   element = p1.toVariant();
400   r1.loadVariant( element );
401   QCOMPARE( r1.staticValue(), p1.staticValue() );
402   p1.setStaticValue( QVariant( true ) ); //bool
403   element = p1.toVariant();
404   r1.loadVariant( element );
405   QCOMPARE( r1.staticValue(), p1.staticValue() );
406   p1.setStaticValue( QVariant( 5LL ) ); //longlong
407   element = p1.toVariant();
408   r1.loadVariant( element );
409   QCOMPARE( r1.staticValue(), p1.staticValue() );
410 
411   // test copying a static property
412   QgsProperty p2;
413   p2.setActive( true );
414   p2.setStaticValue( "test" );
415   p2.setTransformer( new TestTransformer( 10, 20 ) );
416   // copy assign
417   QgsProperty p3;
418   p3 = p2;
419   QVERIFY( p3.isActive() );
420   QCOMPARE( p3.staticValue().toString(), QStringLiteral( "test" ) );
421   QVERIFY( p3.transformer() );
422   p2.setActive( false );
423   p2.setStaticValue( 5.9 );
424   p3 = p2;
425   QVERIFY( !p3.isActive() );
426   QCOMPARE( p3.staticValue().toDouble(), 5.9 );
427 
428   // copy constructor
429   QgsProperty p4( p2 );
430   QVERIFY( !p4.isActive() );
431   QCOMPARE( p4.staticValue().toDouble(), 5.9 );
432   QVERIFY( p4.transformer() );
433 }
434 
fieldBasedProperty()435 void TestQgsProperty::fieldBasedProperty()
436 {
437   //make a feature
438   QgsFeature ft;
439   QgsFields fields;
440   fields.append( QgsField( QStringLiteral( "field1" ), QVariant::Int ) );
441   fields.append( QgsField( QStringLiteral( "field2" ), QVariant::Int ) );
442   ft.setFields( fields );
443   QgsAttributes attr;
444   attr << QVariant( 5 ) << QVariant( 7 );
445   ft.setAttributes( attr );
446   ft.setValid( true );
447 
448   // throw it in an expression context
449   QgsExpressionContext context;
450   context.setFeature( ft );
451   context.setFields( fields );
452 
453   QgsProperty property = QgsProperty::fromField( QStringLiteral( "field1" ), true );
454   QCOMPARE( property.propertyType(), QgsProperty::FieldBasedProperty );
455   QVERIFY( property.isActive() );
456   QCOMPARE( property.value( context, -1 ).toInt(), 5 );
457   QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field1" );
458   property.setActive( false );
459   QVERIFY( !property.isActive() );
460   QCOMPARE( property.value( context, -1 ).toInt(), -1 );
461   QVERIFY( property.referencedFields( context ).isEmpty() );
462   property.setField( QStringLiteral( "field2" ) );
463   property.setActive( true );
464   QCOMPARE( property.value( context, -1 ).toInt(), 7 );
465   QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field2" );
466   //bad field reference
467   property.setField( QStringLiteral( "bad_field" ) );
468   QCOMPARE( property.value( context, -1 ).toInt(), -1 );
469   // unset field name
470   QgsProperty defaultProperty = QgsProperty::fromField( QString() );
471   QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
472   QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
473   defaultProperty.setActive( true );
474   QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
475   QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
476 
477   //test preparation
478   QgsProperty property3 = QgsProperty::fromField( QStringLiteral( "field1" ), true );
479   QVERIFY( property3.prepare( context ) );
480   QCOMPARE( property3.value( context, -1 ).toInt(), 5 );
481 
482   //saving and restoring
483 
484   //create a test dom element
485   QDomImplementation DomImplementation;
486   QDomDocumentType documentType =
487     DomImplementation.createDocumentType(
488       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
489   QDomDocument doc( documentType );
490 
491   QgsProperty p1;
492   p1.setActive( true );
493   p1.setField( QStringLiteral( "test_field" ) );
494 
495   QVariant element;
496   QgsProperty r1;
497   //try reading from an empty element
498   r1.loadVariant( element );
499   QVERIFY( !r1.isActive() );
500   QVERIFY( r1.field().isEmpty() );
501 
502   // now populate element and re-read
503   element = p1.toVariant();
504   r1.loadVariant( element );
505   QVERIFY( r1.isActive() );
506   QCOMPARE( r1.field(), QStringLiteral( "test_field" ) );
507 
508   p1.setActive( false );
509   element = p1.toVariant();
510   r1.loadVariant( element );
511   QVERIFY( !r1.isActive() );
512 
513   // test copying a field based property
514   QgsProperty p2;
515   p2.setActive( true );
516   p2.setField( QStringLiteral( "test" ) );
517   p2.setTransformer( new TestTransformer( 10, 20 ) );
518 
519   // copy constructor
520   QgsProperty p3( p2 );
521   QVERIFY( p3.isActive() );
522   QCOMPARE( p3.field(), QStringLiteral( "test" ) );
523   QVERIFY( p3.transformer() );
524   p2.setActive( false );
525 
526   // assignment operator
527   QgsProperty p4;
528   p4 = p2;
529   QVERIFY( !p4.isActive() );
530   QCOMPARE( p4.field(), QStringLiteral( "test" ) );
531   QVERIFY( p4.transformer() );
532 }
533 
expressionBasedProperty()534 void TestQgsProperty::expressionBasedProperty()
535 {
536   //make a feature
537   QgsFeature ft;
538   QgsFields fields;
539   fields.append( QgsField( QStringLiteral( "field1" ), QVariant::Int ) );
540   fields.append( QgsField( QStringLiteral( "field2" ), QVariant::Int ) );
541   ft.setFields( fields );
542   QgsAttributes attr;
543   attr << QVariant( 5 ) << QVariant( 7 );
544   ft.setAttributes( attr );
545   ft.setValid( true );
546 
547   // throw it in an expression context
548   QgsExpressionContext context;
549   context.setFeature( ft );
550   context.setFields( fields );
551 
552   QgsProperty property = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true );
553   QCOMPARE( property.propertyType(), QgsProperty::ExpressionBasedProperty );
554   QVERIFY( property.isActive() );
555   QCOMPARE( property.value( context, -1 ).toInt(), 12 );
556   QCOMPARE( property.referencedFields( context ).count(), 2 );
557   QVERIFY( property.referencedFields( context ).contains( "field1" ) );
558   QVERIFY( property.referencedFields( context ).contains( "field2" ) );
559   property.setExpressionString( QStringLiteral( "\"field2\"*2" ) );
560   QCOMPARE( property.value( context, -1 ).toInt(), 14 );
561   QCOMPARE( property.referencedFields( context ), QSet< QString >() << "field2" );
562   property.setActive( false );
563   QVERIFY( !property.isActive() );
564   QCOMPARE( property.value( context, -1 ).toInt(), -1 );
565   QVERIFY( property.referencedFields( context ).isEmpty() );
566   property.setExpressionString( QStringLiteral( "'a'||'b'" ) );
567   property.setActive( true );
568   QVERIFY( property.referencedFields( context ).isEmpty() );
569   QCOMPARE( property.value( context, "bb" ).toString(), QStringLiteral( "ab" ) );
570   //bad expression
571   property.setExpressionString( QStringLiteral( "bad_ 5" ) );
572   QCOMPARE( property.value( context, -1 ).toInt(), -1 );
573   QVERIFY( property.referencedFields( context ).isEmpty() );
574   // unset expression
575   QgsProperty defaultProperty = QgsProperty::fromExpression( QString() );
576   QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
577   QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
578   defaultProperty.setActive( true );
579   QCOMPARE( defaultProperty.value( context, -1 ).toInt(), -1 );
580   QVERIFY( defaultProperty.referencedFields( context ).isEmpty() );
581 
582   //preparation
583   QgsProperty property3 = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true );
584   QVERIFY( property3.prepare( context ) );
585   QCOMPARE( property3.value( context, -1 ).toInt(), 12 );
586   QgsProperty property4 = QgsProperty::fromExpression( QStringLiteral( "\"field1\" + " ), true );
587   QVERIFY( !property4.prepare( context ) );
588 
589   //saving and restoring
590 
591   //create a test dom element
592   QDomImplementation DomImplementation;
593   QDomDocumentType documentType =
594     DomImplementation.createDocumentType(
595       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
596   QDomDocument doc( documentType );
597 
598   QgsProperty p1;
599   p1.setActive( true );
600   p1.setExpressionString( QStringLiteral( "4+5" ) );
601 
602   QVariant element;
603   QgsProperty r1;
604   //try reading from an empty element
605   r1.loadVariant( element );
606   QVERIFY( !r1.isActive() );
607   QVERIFY( r1.expressionString().isEmpty() );
608   QCOMPARE( r1.value( context, -1 ).toInt(), -1 );
609 
610   // now populate element and re-read
611   element = p1.toVariant();
612   r1.loadVariant( element );
613   QVERIFY( r1.isActive() );
614   QCOMPARE( r1.expressionString(), QStringLiteral( "4+5" ) );
615   QCOMPARE( r1.value( context, -1 ).toInt(), 9 );
616 
617   p1.setActive( false );
618   element = p1.toVariant();
619   r1.loadVariant( element );
620   QVERIFY( !r1.isActive() );
621   QCOMPARE( r1.value( context, -1 ).toInt(), -1 );
622 
623   // test copying an expression based property
624   QgsProperty p2;
625   p2.setActive( true );
626   p2.setExpressionString( QStringLiteral( "1+6" ) );
627 
628   // copy constructor
629   QgsProperty p3( p2 );
630   QVERIFY( p3.isActive() );
631   QCOMPARE( p3.expressionString(), QStringLiteral( "1+6" ) );
632   QCOMPARE( p3.value( context, -1 ).toInt(), 7 );
633 
634   // assignment operator
635   QgsProperty p4;
636   p2.setActive( false );
637   p4 = p2;
638   QVERIFY( !p4.isActive() );
639   QCOMPARE( p4.value( context, -1 ).toInt(), -1 );
640   p2.setTransformer( new TestTransformer( 10, 20 ) );
641   p4 = p2;
642   QVERIFY( p4.transformer() );
643 }
644 
equality()645 void TestQgsProperty::equality()
646 {
647   QgsProperty dd1;
648   dd1.setActive( true );
649   dd1.setField( QStringLiteral( "field" ) );
650   QgsProperty dd2;
651   dd2.setActive( true );
652   dd2.setField( QStringLiteral( "field" ) );
653   QVERIFY( dd1 == dd2 );
654   QVERIFY( !( dd1 != dd2 ) );
655 
656   dd1.setExpressionString( QStringLiteral( "expression" ) );
657   dd2.setExpressionString( QStringLiteral( "expression" ) );
658   QVERIFY( dd1 == dd2 );
659   QVERIFY( !( dd1 != dd2 ) );
660 
661   //test that all applicable components contribute to equality
662   dd2.setActive( false );
663   QVERIFY( !( dd1 == dd2 ) );
664   QVERIFY( dd1 != dd2 );
665   dd2.setActive( true );
666   dd2.setExpressionString( QStringLiteral( "a" ) );
667   QVERIFY( !( dd1 == dd2 ) );
668   QVERIFY( dd1 != dd2 );
669   dd2.setField( QStringLiteral( "field" ) );
670   QVERIFY( !( dd1 == dd2 ) );
671   QVERIFY( dd1 != dd2 );
672   dd1.setField( QStringLiteral( "fieldb" ) );
673   QVERIFY( !( dd1 == dd2 ) );
674   QVERIFY( dd1 != dd2 );
675   dd1.setField( QStringLiteral( "field" ) );
676   QVERIFY( dd1 == dd2 );
677   QVERIFY( !( dd1 != dd2 ) );
678 
679   // with transformer
680   dd1.setTransformer( new QgsGenericNumericTransformer( 1, 2, 3, 4 ) );
681   QVERIFY( !( dd1 == dd2 ) );
682   QVERIFY( dd1 != dd2 );
683   dd2.setTransformer( new QgsGenericNumericTransformer( 1, 2, 3, 4 ) );
684   QVERIFY( dd1 == dd2 );
685   QVERIFY( !( dd1 != dd2 ) );
686 }
687 
propertyTransformer()688 void TestQgsProperty::propertyTransformer()
689 {
690   QgsExpressionContext context;
691   TestTransformer transform( -5, 5 );
692   QCOMPARE( transform.minValue(), -5.0 );
693   transform.setMinValue( -1 );
694   QCOMPARE( transform.minValue(), -1.0 );
695   QCOMPARE( transform.maxValue(), 5.0 );
696   transform.setMaxValue( 10.0 );
697   QCOMPARE( transform.maxValue(), 10.0 );
698 
699   //saving and restoring
700 
701   //create a test dom element
702   QDomImplementation DomImplementation;
703   QDomDocumentType documentType =
704     DomImplementation.createDocumentType(
705       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
706   QDomDocument doc( documentType );
707 
708   TestTransformer t1( -5, 6 );
709   QVariant element;
710   TestTransformer r1( -99, -98 );
711   element = t1.toVariant();
712   QVERIFY( r1.loadVariant( element ) );
713   QCOMPARE( r1.minValue(), -5.0 );
714   QCOMPARE( r1.maxValue(), 6.0 );
715 
716   //install into property and test evaluation
717   QgsProperty p1;
718   p1.setTransformer( new TestTransformer( 10, 20 ) );
719   QVERIFY( dynamic_cast< const TestTransformer * >( p1.transformer() ) );
720   QCOMPARE( static_cast< const TestTransformer * >( p1.transformer() )->minValue(), 10.0 );
721   QCOMPARE( static_cast< const TestTransformer * >( p1.transformer() )->maxValue(), 20.0 );
722   p1.setStaticValue( QVariant( QVariant::Double ) );
723   QCOMPARE( p1.value( context, -99 ).toDouble(), -1.0 );
724   p1.setStaticValue( 11.0 );
725   QCOMPARE( p1.value( context, -99 ).toDouble(), 22.0 );
726 
727   //test that transform is saved/restored with property
728   QVariant propElement;
729   QgsProperty p2;
730   QVERIFY( !p2.transformer() );
731   propElement = p1.toVariant();
732   p2.loadVariant( propElement );
733   QVERIFY( p2.transformer() );
734   QCOMPARE( p2.transformer()->minValue(), 10.0 );
735   QCOMPARE( p2.transformer()->maxValue(), 20.0 );
736 
737   //test that copy constructor copies transformer
738   QgsProperty p4( p1 );
739   QVERIFY( p4.transformer() );
740   QCOMPARE( p4.transformer()->minValue(), 10.0 );
741   QCOMPARE( p4.transformer()->maxValue(), 20.0 );
742 
743   //test that assignment operator copies transformer
744   QgsProperty p5;
745   p5 = p1;
746   QVERIFY( p5.transformer() );
747   QCOMPARE( p5.transformer()->minValue(), 10.0 );
748   QCOMPARE( p5.transformer()->maxValue(), 20.0 );
749 }
750 
propertyTransformerFromExpression()751 void TestQgsProperty::propertyTransformerFromExpression()
752 {
753   QString baseExpression;
754   QString fieldName;
755   // not convertible to a transformer
756   std::unique_ptr< QgsPropertyTransformer > exp( QgsPropertyTransformer::fromExpression( QStringLiteral( "1 * 2" ), baseExpression, fieldName ) );
757   QVERIFY( !exp.get() );
758   QVERIFY( baseExpression.isEmpty() );
759   QVERIFY( fieldName.isEmpty() );
760 
761   // convertible to a size scale transformer
762   exp.reset( QgsPropertyTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
763   QVERIFY( exp.get() );
764   QCOMPARE( exp->transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
765   QCOMPARE( fieldName, QStringLiteral( "column" ) );
766   QVERIFY( baseExpression.isEmpty() );
767 
768   exp.reset( QgsPropertyTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column * 2, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
769   QVERIFY( exp.get() );
770   QCOMPARE( exp->transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
771   QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
772   QVERIFY( fieldName.isEmpty() );
773 }
774 
genericNumericTransformer()775 void TestQgsProperty::genericNumericTransformer()
776 {
777   QgsExpressionContext context;
778   QgsGenericNumericTransformer t1( 10,
779                                    20,
780                                    100,
781                                    200,
782                                    -10,
783                                    1.0 );
784   QCOMPARE( t1.transformerType(), QgsPropertyTransformer::GenericNumericTransformer );
785   QCOMPARE( t1.minValue(), 10.0 );
786   QCOMPARE( t1.maxValue(), 20.0 );
787   QCOMPARE( t1.minOutputValue(), 100.0 );
788   QCOMPARE( t1.maxOutputValue(), 200.0 );
789   QCOMPARE( t1.nullOutputValue(), -10.0 );
790   QCOMPARE( t1.exponent(), 1.0 );
791 
792   //transform
793   QCOMPARE( t1.transform( context, 10 ).toInt(), 100 );
794   QCOMPARE( t1.transform( context, 20 ).toInt(), 200 );
795   //null value
796   QCOMPARE( t1.transform( context, QVariant( QVariant::Double ) ).toInt(), -10 );
797   //non numeric value
798   QCOMPARE( t1.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
799 
800   // add a curve
801   QVERIFY( !t1.curveTransform() );
802   t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
803   QVERIFY( t1.curveTransform() );
804   QCOMPARE( t1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
805 
806   QCOMPARE( t1.transform( context, 10 ).toInt(), 180 );
807   QCOMPARE( t1.transform( context, 20 ).toInt(), 120 );
808 
809   // copy
810   QgsGenericNumericTransformer s1( t1 );
811   QVERIFY( s1.curveTransform() );
812   QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
813 
814   // assignment
815   QgsGenericNumericTransformer s2;
816   s2 = t1;
817   QVERIFY( s2.curveTransform() );
818   QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
819 
820   //saving and restoring
821 
822   //create a test dom element
823   QDomImplementation DomImplementation;
824   QDomDocumentType documentType =
825     DomImplementation.createDocumentType(
826       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
827   QDomDocument doc( documentType );
828 
829   QgsGenericNumericTransformer t2( 15,
830                                    25,
831                                    150,
832                                    250,
833                                    -10,
834                                    99 );
835   t2.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
836 
837   QVariant element;
838   element = t2.toVariant();
839   QgsGenericNumericTransformer r1;
840   QVERIFY( r1.loadVariant( element ) );
841   QCOMPARE( r1.minValue(), 15.0 );
842   QCOMPARE( r1.maxValue(), 25.0 );
843   QCOMPARE( r1.minOutputValue(), 150.0 );
844   QCOMPARE( r1.maxOutputValue(), 250.0 );
845   QCOMPARE( r1.nullOutputValue(), -10.0 );
846   QCOMPARE( r1.exponent(), 99.0 );
847   QVERIFY( r1.curveTransform() );
848   QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
849 
850   // test cloning
851   std::unique_ptr< QgsGenericNumericTransformer > r2( t2.clone() );
852   QCOMPARE( r2->minValue(), 15.0 );
853   QCOMPARE( r2->maxValue(), 25.0 );
854   QCOMPARE( r2->minOutputValue(), 150.0 );
855   QCOMPARE( r2->maxOutputValue(), 250.0 );
856   QCOMPARE( r2->nullOutputValue(), -10.0 );
857   QCOMPARE( r2->exponent(), 99.0 );
858   QVERIFY( r2->curveTransform() );
859   QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
860 
861   //test various min/max value/size and scaling methods
862 
863   //getters and setters
864   QgsGenericNumericTransformer t;
865   t.setMinValue( 100 );
866   QCOMPARE( t.minValue(), 100.0 );
867   t.setMaxValue( 200 );
868   QCOMPARE( t.maxValue(), 200.0 );
869   t.setMinOutputValue( 10.0 );
870   QCOMPARE( t.minOutputValue(), 10.0 );
871   t.setMaxOutputValue( 20.0 );
872   QCOMPARE( t.maxOutputValue(), 20.0 );
873   t.setNullOutputValue( 1 );
874   QCOMPARE( t.nullOutputValue(), 1.0 );
875   t.setExponent( 2.5 );
876   QCOMPARE( t.exponent(), 2.5 );
877 
878   //test linear scaling
879   t.setExponent( 1.0 );
880   QCOMPARE( t.value( 100 ), 10.0 );
881   QCOMPARE( t.value( 150 ), 15.0 );
882   QCOMPARE( t.value( 200 ), 20.0 );
883   //test exponential scaling
884   t.setExponent( 1.5 );
885   QCOMPARE( t.value( 100 ), 10.0 );
886   QGSCOMPARENEAR( t.value( 150 ), 13.5355, 0.001 );
887   QCOMPARE( t.value( 200 ), 20.0 );
888 
889   // invalid settings, where minValue = maxValue
890   QgsGenericNumericTransformer invalid( 1.0, 1.0, 0, 1.0 );
891   QCOMPARE( invalid.value( -1 ), 0.0 );
892   QCOMPARE( invalid.value( 0 ), 0.0 );
893   QCOMPARE( invalid.value( 1.0 ), 1.0 );
894   QCOMPARE( invalid.value( 2.0 ), 1.0 );
895 
896   //as expression
897   QgsGenericNumericTransformer t3( 15,
898                                    25,
899                                    150,
900                                    250,
901                                    -10,
902                                    1.0 );
903   QCOMPARE( t3.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_linear(5+6, 15, 25, 150, 250), -10)" ) );
904   t3.setExponent( 1.6 );
905   QCOMPARE( t3.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_exp(5+6, 15, 25, 150, 250, 1.6), -10)" ) );
906 
907   // test size scale transformer inside property
908   QgsProperty p;
909   p.setTransformer( new QgsGenericNumericTransformer( 15,
910                     25,
911                     150,
912                     250,
913                     -10,
914                     99 ) );
915   p.setStaticValue( QVariant() );
916   bool ok = false;
917   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
918   QVERIFY( ok );
919   p.setExpressionString( QStringLiteral( "NULL" ) );
920   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
921   QVERIFY( ok );
922   p.setExpressionString( QStringLiteral( "no field" ) );
923   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
924   QVERIFY( ok );
925 }
926 
genericNumericTransformerFromExpression()927 void TestQgsProperty::genericNumericTransformerFromExpression()
928 {
929   QString baseExpression;
930   QString fieldName;
931   std::unique_ptr< QgsGenericNumericTransformer > exp( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
932   QVERIFY( exp.get() );
933   QCOMPARE( fieldName, QStringLiteral( "column" ) );
934   QVERIFY( baseExpression.isEmpty() );
935   QCOMPARE( exp->minValue(), 1. );
936   QCOMPARE( exp->maxValue(), 7. );
937   QCOMPARE( exp->minOutputValue(), 2. );
938   QCOMPARE( exp->maxOutputValue(), 10. );
939   QCOMPARE( exp->nullOutputValue(), 0.0 );
940 
941   exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "scale_linear(column, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
942   QVERIFY( exp.get() );
943   QCOMPARE( fieldName, QStringLiteral( "column" ) );
944   QVERIFY( baseExpression.isEmpty() );
945   QCOMPARE( exp->minValue(), 1. );
946   QCOMPARE( exp->maxValue(), 7. );
947   QCOMPARE( exp->minOutputValue(), 2. );
948   QCOMPARE( exp->maxOutputValue(), 10. );
949 
950   exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "scale_linear(column * 2, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
951   QVERIFY( exp.get() );
952   QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
953   QVERIFY( fieldName.isEmpty() );
954   QCOMPARE( exp->minValue(), 1. );
955   QCOMPARE( exp->maxValue(), 7. );
956   QCOMPARE( exp->minOutputValue(), 2. );
957   QCOMPARE( exp->maxOutputValue(), 10. );
958 
959   exp.reset( QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.51), 1)" ), baseExpression, fieldName ) );
960   QVERIFY( exp.get() );
961   QCOMPARE( exp->minValue(), 1. );
962   QCOMPARE( exp->maxValue(), 7. );
963   QCOMPARE( exp->minOutputValue(), 2. );
964   QCOMPARE( exp->maxOutputValue(), 10. );
965   QCOMPARE( exp->exponent(), 0.51 );
966   QCOMPARE( exp->nullOutputValue(), 1.0 );
967 
968   QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, a, 10, 0.5), 0)" ), baseExpression, fieldName ) );
969   QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7), 0)" ), baseExpression, fieldName ) );
970   QVERIFY( !QgsGenericNumericTransformer::fromExpression( QStringLiteral( "1+2" ), baseExpression, fieldName ) );
971   QVERIFY( !QgsGenericNumericTransformer::fromExpression( QString(), baseExpression, fieldName ) );
972 }
973 
sizeScaleTransformer()974 void TestQgsProperty::sizeScaleTransformer()
975 {
976   QgsExpressionContext context;
977   QgsSizeScaleTransformer scale( QgsSizeScaleTransformer::Linear,
978                                  10,
979                                  20,
980                                  100,
981                                  200,
982                                  -10,
983                                  1.0 );
984   QCOMPARE( scale.transformerType(), QgsPropertyTransformer::SizeScaleTransformer );
985   QCOMPARE( scale.minValue(), 10.0 );
986   QCOMPARE( scale.maxValue(), 20.0 );
987   QCOMPARE( scale.minSize(), 100.0 );
988   QCOMPARE( scale.maxSize(), 200.0 );
989   QCOMPARE( scale.nullSize(), -10.0 );
990   QCOMPARE( scale.exponent(), 1.0 );
991   QCOMPARE( scale.type(), QgsSizeScaleTransformer::Linear );
992 
993   //transform
994   QCOMPARE( scale.transform( context, 10 ).toInt(), 100 );
995   QCOMPARE( scale.transform( context, 20 ).toInt(), 200 );
996   //null value
997   QCOMPARE( scale.transform( context, QVariant( QVariant::Double ) ).toInt(), -10 );
998   //non numeric value
999   QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
1000 
1001   // add a curve
1002   QVERIFY( !scale.curveTransform() );
1003   scale.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) ) );
1004   QVERIFY( scale.curveTransform() );
1005   QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1006   QCOMPARE( scale.transform( context, 10 ).toInt(), 120 );
1007   QCOMPARE( scale.transform( context, 20 ).toInt(), 180 );
1008 
1009   // copy
1010   QgsSizeScaleTransformer s1( scale );
1011   QVERIFY( s1.curveTransform() );
1012   QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1013 
1014   // assignment
1015   QgsSizeScaleTransformer s2;
1016   s2 = scale;
1017   QVERIFY( s2.curveTransform() );
1018   QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1019 
1020   //saving and restoring
1021 
1022   //create a test dom element
1023   QDomImplementation DomImplementation;
1024   QDomDocumentType documentType =
1025     DomImplementation.createDocumentType(
1026       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1027   QDomDocument doc( documentType );
1028 
1029   QgsSizeScaleTransformer t1( QgsSizeScaleTransformer::Exponential,
1030                               15,
1031                               25,
1032                               150,
1033                               250,
1034                               -10,
1035                               99 );
1036   t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
1037 
1038   QVariant element;
1039   element = t1.toVariant();
1040   QgsSizeScaleTransformer r1;
1041   QVERIFY( r1.loadVariant( element ) );
1042   QCOMPARE( r1.minValue(), 15.0 );
1043   QCOMPARE( r1.maxValue(), 25.0 );
1044   QCOMPARE( r1.minSize(), 150.0 );
1045   QCOMPARE( r1.maxSize(), 250.0 );
1046   QCOMPARE( r1.nullSize(), -10.0 );
1047   QCOMPARE( r1.exponent(), 99.0 );
1048   QCOMPARE( r1.type(), QgsSizeScaleTransformer::Exponential );
1049   QVERIFY( r1.curveTransform() );
1050   QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1051 
1052   // test cloning
1053   std::unique_ptr< QgsSizeScaleTransformer > r2( t1.clone() );
1054   QCOMPARE( r2->minValue(), 15.0 );
1055   QCOMPARE( r2->maxValue(), 25.0 );
1056   QCOMPARE( r2->minSize(), 150.0 );
1057   QCOMPARE( r2->maxSize(), 250.0 );
1058   QCOMPARE( r2->nullSize(), -10.0 );
1059   QCOMPARE( r2->exponent(), 99.0 );
1060   QCOMPARE( r2->type(), QgsSizeScaleTransformer::Exponential );
1061   QVERIFY( r2->curveTransform() );
1062   QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1063 
1064   //test various min/max value/size and scaling methods
1065 
1066   //getters and setters
1067   QgsSizeScaleTransformer t;
1068   t.setMinValue( 100 );
1069   QCOMPARE( t.minValue(), 100.0 );
1070   t.setMaxValue( 200 );
1071   QCOMPARE( t.maxValue(), 200.0 );
1072   t.setMinSize( 10.0 );
1073   QCOMPARE( t.minSize(), 10.0 );
1074   t.setMaxSize( 20.0 );
1075   QCOMPARE( t.maxSize(), 20.0 );
1076   t.setNullSize( 1 );
1077   QCOMPARE( t.nullSize(), 1.0 );
1078   t.setType( QgsSizeScaleTransformer::Area );
1079   QCOMPARE( t.type(), QgsSizeScaleTransformer::Area );
1080   t.setExponent( 2.5 );
1081   QCOMPARE( t.exponent(), 2.5 );
1082 
1083   //test that setting type updates exponent
1084   t.setType( QgsSizeScaleTransformer::Linear );
1085   QCOMPARE( t.exponent(), 1.0 );
1086   t.setType( QgsSizeScaleTransformer::Area );
1087   QCOMPARE( t.exponent(), 0.5 );
1088   t.setType( QgsSizeScaleTransformer::Flannery );
1089   QCOMPARE( t.exponent(), 0.57 );
1090 
1091   //test linear scaling
1092   t.setType( QgsSizeScaleTransformer::Linear );
1093   QCOMPARE( t.size( 100 ), 10.0 );
1094   QCOMPARE( t.size( 150 ), 15.0 );
1095   QCOMPARE( t.size( 200 ), 20.0 );
1096   //test area scaling
1097   t.setType( QgsSizeScaleTransformer::Area );
1098   QCOMPARE( t.size( 100 ), 10.0 );
1099   QGSCOMPARENEAR( t.size( 150 ), 17.0711, 0.001 );
1100   QCOMPARE( t.size( 200 ), 20.0 );
1101   //test flannery scaling
1102   t.setType( QgsSizeScaleTransformer::Flannery );
1103   QCOMPARE( t.size( 100 ), 10.0 );
1104   QGSCOMPARENEAR( t.size( 150 ), 16.7362, 0.001 );
1105   QCOMPARE( t.size( 200 ), 20.0 );
1106   //test exponential scaling
1107   t.setType( QgsSizeScaleTransformer::Exponential );
1108   t.setExponent( 1.5 );
1109   QCOMPARE( t.size( 100 ), 10.0 );
1110   QGSCOMPARENEAR( t.size( 150 ), 13.5355, 0.001 );
1111   QCOMPARE( t.size( 200 ), 20.0 );
1112 
1113   //as expression
1114   QgsSizeScaleTransformer t2( QgsSizeScaleTransformer::Linear,
1115                               15,
1116                               25,
1117                               150,
1118                               250,
1119                               -10,
1120                               1.6 );
1121   QCOMPARE( t2.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_linear(5+6, 15, 25, 150, 250), -10)" ) );
1122   t2.setType( QgsSizeScaleTransformer::Exponential );
1123   t2.setExponent( 1.6 );
1124   QCOMPARE( t2.toExpression( "5+6" ), QStringLiteral( "coalesce(scale_exp(5+6, 15, 25, 150, 250, 1.6), -10)" ) );
1125 
1126   // test size scale transformer inside property
1127   QgsProperty p;
1128   p.setTransformer( new  QgsSizeScaleTransformer( QgsSizeScaleTransformer::Exponential,
1129                     15,
1130                     25,
1131                     150,
1132                     250,
1133                     -10,
1134                     99 ) );
1135   p.setStaticValue( QVariant() );
1136   bool ok = false;
1137   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
1138   QVERIFY( ok );
1139   p.setExpressionString( QStringLiteral( "NULL" ) );
1140   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
1141   QVERIFY( ok );
1142   p.setExpressionString( QStringLiteral( "no field" ) );
1143   QCOMPARE( p.valueAsDouble( context, 100, &ok ), -10.0 );
1144   QVERIFY( ok );
1145 }
1146 
sizeScaleTransformerFromExpression()1147 void TestQgsProperty::sizeScaleTransformerFromExpression()
1148 {
1149   QString baseExpression;
1150   QString fieldName;
1151   std::unique_ptr< QgsSizeScaleTransformer > exp( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ), baseExpression, fieldName ) );
1152   QVERIFY( exp.get() );
1153   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
1154   QCOMPARE( fieldName, QStringLiteral( "column" ) );
1155   QVERIFY( baseExpression.isEmpty() );
1156   QCOMPARE( exp->minValue(), 1. );
1157   QCOMPARE( exp->maxValue(), 7. );
1158   QCOMPARE( exp->minSize(), 2. );
1159   QCOMPARE( exp->maxSize(), 10. );
1160   QCOMPARE( exp->nullSize(), 0.0 );
1161 
1162   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.5), 0)" ), baseExpression, fieldName ) );
1163   QVERIFY( exp.get() );
1164   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Area );
1165 
1166   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.57), 0)" ), baseExpression, fieldName ) );
1167   QVERIFY( exp.get() );
1168   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Flannery );
1169 
1170   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_linear(column, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
1171   QVERIFY( exp.get() );
1172   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
1173   QCOMPARE( fieldName, QStringLiteral( "column" ) );
1174   QVERIFY( baseExpression.isEmpty() );
1175   QCOMPARE( exp->minValue(), 1. );
1176   QCOMPARE( exp->maxValue(), 7. );
1177   QCOMPARE( exp->minSize(), 2. );
1178   QCOMPARE( exp->maxSize(), 10. );
1179 
1180   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_linear(column * 2, 1, 7, 2, 10)" ), baseExpression, fieldName ) );
1181   QVERIFY( exp.get() );
1182   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Linear );
1183   QCOMPARE( baseExpression, QStringLiteral( "column * 2" ) );
1184   QVERIFY( fieldName.isEmpty() );
1185   QCOMPARE( exp->minValue(), 1. );
1186   QCOMPARE( exp->maxValue(), 7. );
1187   QCOMPARE( exp->minSize(), 2. );
1188   QCOMPARE( exp->maxSize(), 10. );
1189 
1190   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_exp(column, 1, 7, 2, 10, 0.5)" ), baseExpression, fieldName ) );
1191   QVERIFY( exp.get() );
1192   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Area );
1193 
1194   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "scale_exp(column, 1, 7, 2, 10, 0.57)" ), baseExpression, fieldName ) );
1195   QVERIFY( exp.get() );
1196   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Flannery );
1197 
1198   exp.reset( QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, 2, 10, 0.51), 22)" ), baseExpression, fieldName ) );
1199   QVERIFY( exp.get() );
1200   QCOMPARE( exp->type(), QgsSizeScaleTransformer::Exponential );
1201   QCOMPARE( exp->nullSize(), 22.0 );
1202 
1203   QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7, a, 10, 0.5), 0)" ), baseExpression, fieldName ) );
1204   QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "coalesce(scale_exp(column, 1, 7), 0)" ), baseExpression, fieldName ) );
1205   QVERIFY( !QgsSizeScaleTransformer::fromExpression( QStringLiteral( "1+2" ), baseExpression, fieldName ) );
1206   QVERIFY( !QgsSizeScaleTransformer::fromExpression( QString(), baseExpression, fieldName ) );
1207 }
1208 
colorRampTransformer()1209 void TestQgsProperty::colorRampTransformer()
1210 {
1211   QgsExpressionContext context;
1212   QgsColorRampTransformer scale( 10,
1213                                  20,
1214                                  new QgsGradientColorRamp( QColor( 0, 0, 0 ), QColor( 255, 255, 255 ) ),
1215                                  QColor( 100, 150, 200 ) );
1216   QCOMPARE( scale.transformerType(), QgsPropertyTransformer::ColorRampTransformer );
1217   QCOMPARE( scale.minValue(), 10.0 );
1218   QCOMPARE( scale.maxValue(), 20.0 );
1219   QVERIFY( scale.colorRamp() );
1220   QCOMPARE( scale.nullColor(), QColor( 100, 150, 200 ) );
1221 
1222   //transform
1223   QCOMPARE( scale.transform( context, 10 ).value<QColor>(), QColor( 0, 0, 0 ) );
1224   QCOMPARE( scale.transform( context, 20 ).value<QColor>(), QColor( 255, 255, 255 ) );
1225   //null value
1226   QCOMPARE( scale.transform( context, QVariant( QVariant::Double ) ).value<QColor>(), QColor( 100, 150, 200 ) );
1227   //non numeric value
1228   QCOMPARE( scale.transform( context, QVariant( "ffff" ) ), QVariant( "ffff" ) );
1229 
1230   // add a curve
1231   QVERIFY( !scale.curveTransform() );
1232   scale.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) ) );
1233   QVERIFY( scale.curveTransform() );
1234   QCOMPARE( scale.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1235 
1236   QCOMPARE( scale.transform( context, 10 ).value<QColor>().name(), QString( "#333333" ) );
1237   QCOMPARE( scale.transform( context, 20 ).value<QColor>().name(), QString( "#cccccc" ) );
1238 
1239   // copy
1240   QgsColorRampTransformer s1( scale );
1241   QVERIFY( s1.curveTransform() );
1242   QCOMPARE( s1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1243 
1244   // assignment
1245   QgsColorRampTransformer s2;
1246   s2 = scale;
1247   QVERIFY( s2.curveTransform() );
1248   QCOMPARE( s2.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1, 0.8 ) );
1249 
1250   //saving and restoring
1251 
1252   //create a test dom element
1253   QDomImplementation DomImplementation;
1254   QDomDocumentType documentType =
1255     DomImplementation.createDocumentType(
1256       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1257   QDomDocument doc( documentType );
1258 
1259   QgsColorRampTransformer t1( 15,
1260                               25,
1261                               new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
1262                               QColor( 100, 150, 200 ) );
1263   t1.setRampName( QStringLiteral( "rampname " ) );
1264   t1.setCurveTransform( new QgsCurveTransform( QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) ) );
1265 
1266   QVariant element;
1267   element = t1.toVariant();
1268   QgsColorRampTransformer r1;
1269   QVERIFY( r1.loadVariant( element ) );
1270   QCOMPARE( r1.minValue(), 15.0 );
1271   QCOMPARE( r1.maxValue(), 25.0 );
1272   QCOMPARE( r1.nullColor(), QColor( 100, 150, 200 ) );
1273   QCOMPARE( r1.rampName(), QStringLiteral( "rampname " ) );
1274   QVERIFY( dynamic_cast< QgsGradientColorRamp * >( r1.colorRamp() ) );
1275   QCOMPARE( static_cast< QgsGradientColorRamp * >( r1.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
1276   QCOMPARE( static_cast< QgsGradientColorRamp * >( r1.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
1277   QVERIFY( r1.curveTransform() );
1278   QCOMPARE( r1.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1279 
1280   // test cloning
1281   std::unique_ptr< QgsColorRampTransformer > r2( t1.clone() );
1282   QCOMPARE( r2->minValue(), 15.0 );
1283   QCOMPARE( r2->maxValue(), 25.0 );
1284   QCOMPARE( r2->nullColor(), QColor( 100, 150, 200 ) );
1285   QCOMPARE( r2->rampName(), QStringLiteral( "rampname " ) );
1286   QCOMPARE( static_cast< QgsGradientColorRamp * >( r2->colorRamp() )->color1(), QColor( 10, 20, 30 ) );
1287   QCOMPARE( static_cast< QgsGradientColorRamp * >( r2->colorRamp() )->color2(), QColor( 200, 190, 180 ) );
1288   QVERIFY( r2->curveTransform() );
1289   QCOMPARE( r2->curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1290 
1291   // copy constructor
1292   QgsColorRampTransformer r3( t1 );
1293   QCOMPARE( r3.minValue(), 15.0 );
1294   QCOMPARE( r3.maxValue(), 25.0 );
1295   QCOMPARE( r3.nullColor(), QColor( 100, 150, 200 ) );
1296   QCOMPARE( r3.rampName(), QStringLiteral( "rampname " ) );
1297   QCOMPARE( static_cast< QgsGradientColorRamp * >( r3.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
1298   QCOMPARE( static_cast< QgsGradientColorRamp * >( r3.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
1299   QVERIFY( r3.curveTransform() );
1300   QCOMPARE( r3.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1301 
1302   // assignment operator
1303   QgsColorRampTransformer r4;
1304   r4 = t1;
1305   QCOMPARE( r4.minValue(), 15.0 );
1306   QCOMPARE( r4.maxValue(), 25.0 );
1307   QCOMPARE( r4.nullColor(), QColor( 100, 150, 200 ) );
1308   QCOMPARE( r4.rampName(), QStringLiteral( "rampname " ) );
1309   QCOMPARE( static_cast< QgsGradientColorRamp * >( r4.colorRamp() )->color1(), QColor( 10, 20, 30 ) );
1310   QCOMPARE( static_cast< QgsGradientColorRamp * >( r4.colorRamp() )->color2(), QColor( 200, 190, 180 ) );
1311   QVERIFY( r4.curveTransform() );
1312   QCOMPARE( r4.curveTransform()->controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0, 0.8 ) << QgsPointXY( 1, 0.2 ) );
1313 
1314   //test various min/max value/color and scaling methods
1315 
1316   //getters and setters
1317   QgsColorRampTransformer t;
1318   t.setMinValue( 100 );
1319   QCOMPARE( t.minValue(), 100.0 );
1320   t.setMaxValue( 200 );
1321   QCOMPARE( t.maxValue(), 200.0 );
1322   t.setNullColor( QColor( 1, 10, 11, 21 ) );
1323   QCOMPARE( t.nullColor(), QColor( 1, 10, 11, 21 ) );
1324   t.setColorRamp( new QgsGradientColorRamp( QColor( 10, 20, 100 ), QColor( 100, 200, 200 ) ) );
1325   QCOMPARE( static_cast< QgsGradientColorRamp * >( t.colorRamp() )->color1(), QColor( 10, 20, 100 ) );
1326   t.setRampName( QStringLiteral( "colorramp" ) );
1327   QCOMPARE( t.rampName(), QStringLiteral( "colorramp" ) );
1328 
1329   //test colors
1330   QCOMPARE( t.color( 50 ), QColor( 10, 20, 100 ) ); //out of range
1331   QCOMPARE( t.color( 100 ), QColor( 10, 20, 100 ) );
1332   QCOMPARE( t.color( 150 ), QColor( 55, 110, 150 ) );
1333   QCOMPARE( t.color( 200 ), QColor( 100, 200, 200 ) );
1334   QCOMPARE( t.color( 250 ), QColor( 100, 200, 200 ) ); //out of range
1335 
1336   //toExpression
1337   QgsColorRampTransformer t5( 15,
1338                               25,
1339                               new QgsGradientColorRamp( QColor( 10, 20, 30 ), QColor( 200, 190, 180 ) ),
1340                               QColor( 100, 150, 200 ) );
1341   QCOMPARE( t5.toExpression( "5+6" ), QStringLiteral( "coalesce(ramp_color('custom ramp',scale_linear(5+6, 15, 25, 0, 1)), '#6496c8')" ) );
1342   t5.setRampName( QStringLiteral( "my ramp" ) );
1343   QCOMPARE( t5.toExpression( "5+6" ), QStringLiteral( "coalesce(ramp_color('my ramp',scale_linear(5+6, 15, 25, 0, 1)), '#6496c8')" ) );
1344 }
1345 
propertyToTransformer()1346 void TestQgsProperty::propertyToTransformer()
1347 {
1348   // not convertible to a transformer:
1349 
1350   // fields cannot be converted
1351   QgsProperty p = QgsProperty::fromField( QStringLiteral( "a field" ) );
1352   QVERIFY( !p.convertToTransformer() );
1353   QVERIFY( !p.transformer() );
1354   QCOMPARE( p.field(), QStringLiteral( "a field" ) );
1355 
1356   // static values cannot be converted
1357   p = QgsProperty::fromValue( 5 );
1358   QVERIFY( !p.convertToTransformer() );
1359   QVERIFY( !p.transformer() );
1360   QCOMPARE( p.staticValue(), QVariant( 5 ) );
1361 
1362   // bad expression which cannot be converted
1363   p = QgsProperty::fromExpression( QStringLiteral( "5*5" ) );
1364   QVERIFY( !p.convertToTransformer() );
1365   QVERIFY( !p.transformer() );
1366   QCOMPARE( p.expressionString(), QStringLiteral( "5*5" ) );
1367 
1368   // expression which can be converted to size scale transformer with base expression
1369   p = QgsProperty::fromExpression( QStringLiteral( "coalesce(scale_linear(column * 2, 1, 7, 2, 10), 0)" ) );
1370   QVERIFY( p.convertToTransformer() );
1371   QVERIFY( p.transformer() );
1372   QCOMPARE( p.expressionString(), QStringLiteral( "column * 2" ) );
1373 
1374   // expression which can be converted to a size scale transformer with base column ref
1375   p = QgsProperty::fromExpression( QStringLiteral( "coalesce(scale_linear(column, 1, 7, 2, 10), 0)" ) );
1376   QVERIFY( p.convertToTransformer() );
1377   QVERIFY( p.transformer() );
1378   QCOMPARE( p.field(), QStringLiteral( "column" ) );
1379 }
1380 
asExpression()1381 void TestQgsProperty::asExpression()
1382 {
1383   // static property
1384   QgsProperty p = QgsProperty::fromValue( 5 );
1385   QCOMPARE( p.asExpression(), QStringLiteral( "5" ) );
1386   p = QgsProperty::fromValue( "value" );
1387   QCOMPARE( p.asExpression(), QStringLiteral( "'value'" ) );
1388 
1389   // field based property
1390   p = QgsProperty::fromField( QStringLiteral( "a field" ) );
1391   QCOMPARE( p.asExpression(), QStringLiteral( "\"a field\"" ) );
1392 
1393   // expression based property
1394   p = QgsProperty::fromExpression( QStringLiteral( "5 + 6" ) );
1395   QCOMPARE( p.asExpression(), QStringLiteral( "5 + 6" ) );
1396 
1397   // with transformer
1398   p.setTransformer( new QgsSizeScaleTransformer( QgsSizeScaleTransformer::Linear,
1399                     15,
1400                     25,
1401                     150,
1402                     250,
1403                     -10,
1404                     1 ) );
1405   QCOMPARE( p.asExpression(), QStringLiteral( "coalesce(scale_linear(5 + 6, 15, 25, 150, 250), -10)" ) );
1406 }
1407 
propertyCollection()1408 void TestQgsProperty::propertyCollection()
1409 {
1410   //make a feature
1411   QgsFeature ft;
1412   QgsFields fields;
1413   fields.append( QgsField( QStringLiteral( "field1" ), QVariant::Int ) );
1414   fields.append( QgsField( QStringLiteral( "field2" ), QVariant::Int ) );
1415   ft.setFields( fields );
1416   QgsAttributes attr;
1417   attr << QVariant( 5 ) << QVariant( 7 );
1418   ft.setAttributes( attr );
1419   ft.setValid( true );
1420 
1421   // throw it in an expression context
1422   QgsExpressionContext context;
1423   context.setFeature( ft );
1424   context.setFields( fields );
1425 
1426   QgsPropertyCollection collection( QStringLiteral( "collection" ) );
1427   QCOMPARE( collection.name(), QStringLiteral( "collection" ) );
1428   QVERIFY( !collection.hasProperty( Property1 ) );
1429   QVERIFY( collection.referencedFields( context ).isEmpty() );
1430   QCOMPARE( collection.count(), 0 );
1431   QCOMPARE( collection.propertyKeys(), QSet< int >() );
1432   QVERIFY( !collection.hasDynamicProperties() );
1433   QVERIFY( !collection.hasActiveProperties() );
1434 
1435   QgsPropertyCollection collection2;
1436   QVERIFY( collection == collection2 );
1437   QVERIFY( !( collection != collection2 ) );
1438 
1439   QgsProperty property = QgsProperty::fromValue( "value", true );
1440   collection.setProperty( Property1, property );
1441   QVERIFY( collection.hasProperty( Property1 ) );
1442   QCOMPARE( collection.count(), 1 );
1443   QCOMPARE( collection.propertyKeys(), QSet< int >() << Property1 );
1444   QCOMPARE( collection.property( Property1 ).value( context ), property.value( context ) );
1445   QCOMPARE( collection.value( Property1, context ), property.value( context ) );
1446   QVERIFY( collection.isActive( Property1 ) );
1447   QVERIFY( collection.hasActiveProperties() );
1448   QVERIFY( !collection.hasDynamicProperties() );
1449 
1450   QVERIFY( collection != collection2 );
1451   QVERIFY( !( collection == collection2 ) );
1452   collection2.setProperty( Property1, property );
1453   QVERIFY( collection == collection2 );
1454   QVERIFY( !( collection != collection2 ) );
1455 
1456   //preparation
1457   QVERIFY( collection.prepare( context ) );
1458 
1459   //test bad property
1460   QVERIFY( !const_cast< const QgsPropertyCollection * >( &collection )->property( Property2 ) );
1461   QVERIFY( !collection.value( Property2, context ).isValid() );
1462   QCOMPARE( collection.value( Property2, context, QStringLiteral( "default" ) ).toString(), QStringLiteral( "default" ) );
1463   QVERIFY( !collection.isActive( Property2 ) );
1464 
1465   //test replacing property
1466   QgsProperty property2 = QgsProperty::fromValue( "value2", true );
1467   collection.setProperty( Property1, property2 );
1468   QCOMPARE( collection.count(), 1 );
1469   QCOMPARE( collection.propertyKeys(), QSet< int >() << Property1 );
1470   QCOMPARE( collection.property( Property1 ).value( context ), property2.value( context ) );
1471   QVERIFY( collection.hasActiveProperties() );
1472   QVERIFY( !collection.hasDynamicProperties() );
1473   QVERIFY( collection != collection2 );
1474   QVERIFY( !( collection == collection2 ) );
1475 
1476   //implicit conversion
1477   collection.setProperty( Property3, 5 );
1478   QCOMPARE( collection.property( Property3 ).value( context ).toInt(), 5 );
1479   QVERIFY( collection.property( Property3 ).isActive() );
1480   QCOMPARE( collection.count(), 2 );
1481   QCOMPARE( collection.propertyKeys(), QSet<int>() << Property1 << Property3 );
1482 
1483   //test removing a property
1484   collection.setProperty( Property1, QgsProperty() );
1485   QVERIFY( !const_cast< const QgsPropertyCollection * >( &collection )->property( Property1 ) );
1486   QVERIFY( !collection.hasProperty( Property1 ) );
1487   QCOMPARE( collection.propertyKeys(), QSet<int>() << Property3 );
1488   QVERIFY( !collection.property( Property1 ) ); // should insert a default created invalid property in internal hash
1489   QVERIFY( !collection.hasProperty( Property1 ) );
1490 
1491   //clear
1492   collection.clear();
1493   QCOMPARE( collection.count(), 0 );
1494   QCOMPARE( collection.propertyKeys(), QSet<int>() );
1495   QVERIFY( !collection.hasActiveProperties() );
1496   QVERIFY( !collection.hasDynamicProperties() );
1497 
1498   collection.setProperty( Property1, QgsProperty::fromValue( "v1", true ) );
1499   collection.setProperty( Property2, QgsProperty::fromValue( "v2", false ) );
1500   collection.setProperty( Property3, QgsProperty::fromField( QStringLiteral( "field1" ), true ) );
1501   collection.setProperty( Property4, QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true ) );
1502   QCOMPARE( collection.count(), 4 );
1503 
1504   collection2 = collection;
1505   QVERIFY( collection == collection2 );
1506   QVERIFY( !( collection != collection2 ) );
1507   collection2.setProperty( Property3, QgsProperty() );
1508   QVERIFY( collection != collection2 );
1509   QVERIFY( !( collection == collection2 ) );
1510 
1511   // test referenced fields
1512   QCOMPARE( collection.referencedFields( context ).count(), 2 );
1513   QVERIFY( collection.referencedFields( context ).contains( "field1" ) );
1514   QVERIFY( collection.referencedFields( context ).contains( "field2" ) );
1515 
1516   //saving and restoring
1517 
1518   QDomImplementation DomImplementation;
1519   QDomDocumentType documentType =
1520     DomImplementation.createDocumentType(
1521       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1522   QDomDocument doc( documentType );
1523   QVariant collectionElement = collection.toVariant( mDefinitions );
1524 
1525   QgsPropertyCollection restoredCollection;
1526   restoredCollection.loadVariant( collectionElement, mDefinitions );
1527   QCOMPARE( restoredCollection.name(), QStringLiteral( "collection" ) );
1528   QCOMPARE( restoredCollection.count(), 4 );
1529   QCOMPARE( restoredCollection.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
1530   QVERIFY( restoredCollection.property( Property1 ).isActive() );
1531   QCOMPARE( restoredCollection.property( Property1 ).staticValue(), QVariant( "v1" ) );
1532   QCOMPARE( restoredCollection.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
1533   QVERIFY( !restoredCollection.property( Property2 ).isActive() );
1534   QCOMPARE( restoredCollection.property( Property2 ).staticValue(), QVariant( "v2" ) );
1535   QCOMPARE( restoredCollection.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
1536   QVERIFY( restoredCollection.property( Property3 ).isActive() );
1537   QCOMPARE( restoredCollection.property( Property3 ).field(), QStringLiteral( "field1" ) );
1538   QCOMPARE( restoredCollection.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
1539   QVERIFY( restoredCollection.property( Property4 ).isActive() );
1540   QCOMPARE( restoredCollection.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
1541   QVERIFY( restoredCollection.hasActiveProperties() );
1542   QVERIFY( restoredCollection.hasDynamicProperties() );
1543 
1544   // copy constructor
1545   collection2 = QgsPropertyCollection( collection );
1546   QCOMPARE( collection2.name(), QStringLiteral( "collection" ) );
1547   QCOMPARE( collection2.count(), 4 );
1548   QCOMPARE( collection2.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
1549   QVERIFY( collection2.property( Property1 ).isActive() );
1550   QCOMPARE( collection2.property( Property1 ).staticValue(), QVariant( "v1" ) );
1551   QCOMPARE( collection2.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
1552   QVERIFY( !collection2.property( Property2 ).isActive() );
1553   QCOMPARE( collection2.property( Property2 ).staticValue(), QVariant( "v2" ) );
1554   QCOMPARE( collection2.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
1555   QVERIFY( collection2.property( Property3 ).isActive() );
1556   QCOMPARE( collection2.property( Property3 ).field(), QStringLiteral( "field1" ) );
1557   QCOMPARE( collection2.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
1558   QVERIFY( collection2.property( Property4 ).isActive() );
1559   QCOMPARE( collection2.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
1560   QVERIFY( collection2.hasActiveProperties() );
1561   QVERIFY( collection2.hasDynamicProperties() );
1562 
1563   // assignment operator
1564   QgsPropertyCollection collection3;
1565   collection3.setProperty( Property1, QgsProperty::fromValue( "aaaa", false ) );
1566   collection3 = collection;
1567   QCOMPARE( collection3.name(), QStringLiteral( "collection" ) );
1568   QCOMPARE( collection3.count(), 4 );
1569   QCOMPARE( collection3.property( Property1 ).propertyType(), QgsProperty::StaticProperty );
1570   QVERIFY( collection3.property( Property1 ).isActive() );
1571   QCOMPARE( collection3.property( Property1 ).staticValue(), QVariant( "v1" ) );
1572   QCOMPARE( collection3.property( Property2 ).propertyType(), QgsProperty::StaticProperty );
1573   QVERIFY( !collection3.property( Property2 ).isActive() );
1574   QCOMPARE( collection3.property( Property2 ).staticValue(), QVariant( "v2" ) );
1575   QCOMPARE( collection3.property( Property3 ).propertyType(), QgsProperty::FieldBasedProperty );
1576   QVERIFY( collection3.property( Property3 ).isActive() );
1577   QCOMPARE( collection3.property( Property3 ).field(), QStringLiteral( "field1" ) );
1578   QCOMPARE( collection3.property( Property4 ).propertyType(), QgsProperty::ExpressionBasedProperty );
1579   QVERIFY( collection3.property( Property4 ).isActive() );
1580   QCOMPARE( collection3.property( Property4 ).expressionString(), QStringLiteral( "\"field1\" + \"field2\"" ) );
1581   QVERIFY( collection3.hasActiveProperties() );
1582   QVERIFY( collection3.hasDynamicProperties() );
1583 
1584   //test hasActiveProperties() and hasDynamicProperties()
1585   collection3.property( Property1 ).setActive( false );
1586   collection3.property( Property2 ).setActive( false );
1587   collection3.property( Property3 ).setActive( false );
1588   collection3.property( Property4 ).setActive( false );
1589   QVERIFY( !collection3.hasActiveProperties() );
1590   QVERIFY( !collection3.hasDynamicProperties() );
1591   collection3.property( Property4 ).setActive( true );
1592   QVERIFY( collection3.hasDynamicProperties() );
1593   QVERIFY( collection3.hasActiveProperties() );
1594   collection3.property( Property4 ).setActive( false );
1595   collection3.property( Property2 ).setActive( true );
1596   QVERIFY( !collection3.hasDynamicProperties() );
1597   QVERIFY( collection3.hasActiveProperties() );
1598   collection3.property( Property2 ).setActive( false );
1599   QVERIFY( !collection3.hasActiveProperties() );
1600   collection3.setProperty( Property1, "5" );
1601   QVERIFY( collection3.hasActiveProperties() );
1602   collection3.setProperty( Property1, QgsProperty::fromValue( "6", true ) );
1603   QVERIFY( collection3.hasActiveProperties() );
1604   collection3.setProperty( Property1, QgsProperty::fromValue( "7", false ) );
1605   QVERIFY( !collection3.hasActiveProperties() );
1606   collection3.setProperty( Property3, QVariant( "val" ) );
1607   QVERIFY( collection3.hasActiveProperties() );
1608 }
1609 
collectionStack()1610 void TestQgsProperty::collectionStack()
1611 {
1612   //make a feature
1613   QgsFeature ft;
1614   QgsFields fields;
1615   fields.append( QgsField( QStringLiteral( "field1" ), QVariant::Int ) );
1616   fields.append( QgsField( QStringLiteral( "field2" ), QVariant::Int ) );
1617   ft.setFields( fields );
1618   QgsAttributes attr;
1619   attr << QVariant( 5 ) << QVariant( 7 );
1620   ft.setAttributes( attr );
1621   ft.setValid( true );
1622 
1623   // throw it in an expression context
1624   QgsExpressionContext context;
1625   context.setFeature( ft );
1626   context.setFields( fields );
1627 
1628   QgsPropertyCollectionStack stack;
1629   //test retrieving from empty stack
1630   QVERIFY( !stack.property( Property1 ) );
1631   QVERIFY( !stack.at( 0 ) );
1632   QVERIFY( !const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 0 ) );
1633   QVERIFY( !stack.collection( "nothing" ) );
1634   QVERIFY( !stack.value( Property1, context ).isValid() );
1635   QCOMPARE( stack.value( Property1, context, "default" ).toString(), QStringLiteral( "default" ) );
1636   QVERIFY( !stack.isActive( Property1 ) );
1637   QVERIFY( stack.referencedFields( context ).isEmpty() );
1638   QCOMPARE( stack.count(), 0 );
1639   QVERIFY( !stack.hasDynamicProperties() );
1640   QVERIFY( !stack.hasActiveProperties() );
1641 
1642   //add a collection to the stack
1643   QgsPropertyCollection *collection = new QgsPropertyCollection( QStringLiteral( "collection" ) );
1644   stack.appendCollection( collection );
1645   QCOMPARE( stack.count(), 1 );
1646   QCOMPARE( stack.at( 0 ), collection );
1647   QCOMPARE( const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 0 ), collection );
1648   QVERIFY( !stack.collection( "nothing" ) );
1649   QCOMPARE( stack.collection( "collection" ), collection );
1650   QVERIFY( !stack.property( Property1 ) );
1651   QVERIFY( !stack.value( Property1, context ).isValid() );
1652   QCOMPARE( stack.value( Property1, context, "default" ).toString(), QStringLiteral( "default" ) );
1653   QVERIFY( !stack.isActive( Property1 ) );
1654   QVERIFY( !stack.hasDynamicProperties() );
1655   QVERIFY( !stack.hasActiveProperties() );
1656   QVERIFY( stack.referencedFields( context ).isEmpty() );
1657 
1658   //now add a property to the collection
1659   QgsProperty property = QgsProperty::fromValue( "value", true );
1660   stack.at( 0 )->setProperty( Property1, property );
1661   QVERIFY( stack.isActive( Property1 ) );
1662   QCOMPARE( stack.property( Property1 ).value( context ), property.value( context ) );
1663   QCOMPARE( stack.value( Property1, context ), property.value( context ) );
1664   QVERIFY( !stack.hasDynamicProperties() );
1665   QVERIFY( stack.hasActiveProperties() );
1666   QVERIFY( !stack.isActive( Property2 ) );
1667   collection->setProperty( Property2, QgsProperty::fromValue( "value1", true ) );
1668   QVERIFY( stack.isActive( Property2 ) );
1669   QVERIFY( !stack.hasDynamicProperties() );
1670   QVERIFY( stack.hasActiveProperties() );
1671 
1672   //add a second collection
1673   QgsPropertyCollection *collection2 = new QgsPropertyCollection( QStringLiteral( "collection2" ) );
1674   stack.appendCollection( collection2 );
1675   QCOMPARE( stack.count(), 2 );
1676   QCOMPARE( stack.at( 1 ), collection2 );
1677   QCOMPARE( const_cast< const QgsPropertyCollectionStack * >( &stack )->at( 1 ), collection2 );
1678   QCOMPARE( stack.collection( "collection2" ), collection2 );
1679   QVERIFY( !stack.hasDynamicProperties() );
1680   QVERIFY( stack.hasActiveProperties() );
1681   QgsProperty property2 = QgsProperty::fromValue( "value2", true );
1682   collection2->setProperty( Property2, property2 );
1683   QVERIFY( stack.isActive( Property2 ) );
1684   QCOMPARE( stack.property( Property2 ).value( context ), property2.value( context ) );
1685   QCOMPARE( stack.value( Property2, context ), property2.value( context ) );
1686   QVERIFY( !stack.hasDynamicProperties() );
1687   QVERIFY( stack.hasActiveProperties() );
1688 
1689   //preparation
1690   QVERIFY( stack.prepare( context ) );
1691 
1692   //test adding active property later in the stack
1693   QgsProperty property3 = QgsProperty::fromValue( "value3", true );
1694   collection2->setProperty( Property1, property3 );
1695   QVERIFY( stack.isActive( Property1 ) );
1696   QCOMPARE( stack.property( Property1 ).value( context, "default" ), property3.value( context ) );
1697   QCOMPARE( stack.value( Property1, context ), property3.value( context ) );
1698   collection2->property( Property1 ).setActive( false );
1699   QCOMPARE( stack.value( Property1, context ), property.value( context ) );
1700 
1701   //test overriding a property
1702   QgsProperty property4 = QgsProperty::fromValue( "value4", true );
1703   collection2->setProperty( Property2, property4 );
1704   QVERIFY( stack.isActive( Property2 ) );
1705   QCOMPARE( stack.property( Property2 ).value( context ), property4.value( context ) );
1706   QCOMPARE( stack.value( Property2, context ), property4.value( context ) );
1707   collection2->property( Property2 ).setActive( false );
1708   QCOMPARE( stack.property( Property2 ).value( context ), QVariant( "value1" ) );
1709   QCOMPARE( stack.value( Property2, context ), QVariant( "value1" ) );
1710 
1711   //clearing
1712   stack.clear();
1713   QCOMPARE( stack.count(), 0 );
1714   QVERIFY( !stack.hasDynamicProperties() );
1715   QVERIFY( !stack.hasActiveProperties() );
1716 
1717   // test copying a stack
1718   QgsPropertyCollectionStack stack2;
1719   stack2.appendCollection( new QgsPropertyCollection( QStringLiteral( "collection1" ) ) );
1720   stack2.at( 0 )->setProperty( Property1, "val1" );
1721   stack2.at( 0 )->setProperty( Property2, "val2" );
1722   stack2.appendCollection( new QgsPropertyCollection( QStringLiteral( "collection2" ) ) );
1723   stack2.at( 1 )->setProperty( Property3, "val3" );
1724   //copy constructor
1725   QgsPropertyCollectionStack stack3( stack2 );
1726   QCOMPARE( stack3.count(), 2 );
1727   QCOMPARE( stack3.at( 0 )->name(), QStringLiteral( "collection1" ) );
1728   QCOMPARE( stack3.at( 1 )->name(), QStringLiteral( "collection2" ) );
1729   QCOMPARE( stack3.at( 0 )->property( Property1 ).staticValue(), QVariant( "val1" ) );
1730   QCOMPARE( stack3.at( 0 )->property( Property2 ).staticValue(), QVariant( "val2" ) );
1731   QCOMPARE( stack3.at( 1 )->property( Property3 ).staticValue(), QVariant( "val3" ) );
1732   QVERIFY( !stack3.hasDynamicProperties() );
1733   QVERIFY( stack3.hasActiveProperties() );
1734   //assignment operator
1735   stack3.clear();
1736   stack3.appendCollection( new QgsPropertyCollection( QStringLiteral( "temp" ) ) );
1737   stack3 = stack2;
1738   QCOMPARE( stack3.count(), 2 );
1739   QCOMPARE( stack3.at( 0 )->name(), QStringLiteral( "collection1" ) );
1740   QCOMPARE( stack3.at( 1 )->name(), QStringLiteral( "collection2" ) );
1741   QCOMPARE( stack3.at( 0 )->property( Property1 ).staticValue(), QVariant( "val1" ) );
1742   QCOMPARE( stack3.at( 0 )->property( Property2 ).staticValue(), QVariant( "val2" ) );
1743   QCOMPARE( stack3.at( 1 )->property( Property3 ).staticValue(), QVariant( "val3" ) );
1744   QVERIFY( !stack3.hasDynamicProperties() );
1745   QVERIFY( stack3.hasActiveProperties() );
1746 
1747   //check hasDynamicProperties() and hasActiveProperties()
1748   QgsPropertyCollectionStack stack4;
1749   stack4.appendCollection( new QgsPropertyCollection( QStringLiteral( "collection1" ) ) );
1750   stack4.at( 0 )->setProperty( Property1, "val1" );
1751   QVERIFY( !stack4.hasDynamicProperties() );
1752   QVERIFY( stack4.hasActiveProperties() );
1753   stack4.at( 0 )->property( Property1 ).setActive( false );
1754   QVERIFY( !stack4.hasActiveProperties() );
1755   stack4.at( 0 )->setProperty( Property1, "6" );
1756   QVERIFY( stack4.hasActiveProperties() );
1757   stack4.at( 0 )->setProperty( Property2, QgsProperty::fromExpression( QStringLiteral( "\"field1\" + \"field2\"" ), true ) );
1758   QVERIFY( stack4.hasActiveProperties() );
1759   QVERIFY( stack4.hasDynamicProperties() );
1760   QCOMPARE( stack4.referencedFields( context ), QSet< QString>() << "field1" << "field2" );
1761   stack4.at( 0 )->property( Property1 ).setActive( false );
1762   QVERIFY( stack4.hasActiveProperties() );
1763   QVERIFY( stack4.hasDynamicProperties() );
1764   stack4.at( 0 )->property( Property2 ).setActive( false );
1765   QVERIFY( !stack4.hasActiveProperties() );
1766   QVERIFY( !stack4.hasDynamicProperties() );
1767 }
1768 
curveTransform()1769 void TestQgsProperty::curveTransform()
1770 {
1771   QgsCurveTransform t;
1772   // linear transform
1773   QCOMPARE( t.y( -1 ), 0.0 );
1774   QCOMPARE( t.y( 0 ), 0.0 );
1775   QCOMPARE( t.y( 0.2 ), 0.2 );
1776   QCOMPARE( t.y( 0.5 ), 0.5 );
1777   QCOMPARE( t.y( 0.8 ), 0.8 );
1778   QCOMPARE( t.y( 1 ), 1.0 );
1779   QCOMPARE( t.y( 2 ), 1.0 );
1780 
1781   QVector< double > x;
1782   x << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2;
1783   QVector< double > y = t.y( x );
1784   QCOMPARE( y[0], 0.0 );
1785   QCOMPARE( y[1], 0.0 );
1786   QCOMPARE( y[2], 0.2 );
1787   QCOMPARE( y[3], 0.5 );
1788   QCOMPARE( y[4], 0.8 );
1789   QCOMPARE( y[5], 1.0 );
1790   QCOMPARE( y[6], 1.0 );
1791 
1792   // linear transform with y =/= x
1793   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0, 0.2 ) << QgsPointXY( 1.0, 0.8 ),
1794                     QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
1795                     QVector< double >() << 0.2 << 0.2 << 0.32 << 0.5 << 0.68 << 0.8 << 0.8 );
1796 
1797   // reverse linear transform with y = -x
1798   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 1.0 ) << QgsPointXY( 1.0, 0 ),
1799                     QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
1800                     QVector< double >() << 1.0 << 1.0 << 0.8 << 0.5 << 0.2 << 0.0 << 0.0 );
1801 
1802   // OK, time for some more complex tests...
1803 
1804   // 3 control points, but linear
1805   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0, 0.0 ) << QgsPointXY( 0.2, 0.2 ) << QgsPointXY( 1.0, 1.0 ),
1806                     QVector< double >() << -1 << 0 << 0.2 << 0.5 << 0.8 << 1 << 2,
1807                     QVector< double >() << 0.0 << 0.0 << 0.2 << 0.5 << 0.8 << 1.0 << 1.0 );
1808 
1809   // test for "flat" response for x outside of control point range
1810   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.2, 0.2 ) << QgsPointXY( 0.5, 0.5 ) << QgsPointXY( 0.8, 0.8 ),
1811                     QVector< double >() << -1 << 0 << 0.1 << 0.2 << 0.5 << 0.8 << 0.9 << 1 << 2,
1812                     QVector< double >() << 0.2 << 0.2 << 0.2 << 0.2 << 0.5 << 0.8 << 0.8 << 0.8 << 0.8 );
1813 
1814   //curves!
1815   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 ),
1816                     QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
1817                     QVector< double >() << 0.0 << 0.0 << 0.321429 << 0.6 << 0.710714 << 0.8 << 0.921429 << 0.963393 << 1.0 << 1.0 );
1818 
1819   //curves with more control points
1820   checkCurveResult( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.6 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 0.8, 0.3 ) << QgsPointXY( 1.0, 1.0 ),
1821                     QVector< double >() << -1 << 0 << 0.2 << 0.4 << 0.5 << 0.6 << 0.8 << 0.9 << 1.0 << 2.0,
1822                     QVector< double >() << 0.0 << 0.0 << 0.6 << 0.6 << 0.751316 << 0.8 << 0.3 << 0.508074 << 1.0 << 1.0 );
1823 
1824   // general tests
1825   QList< QgsPointXY > points = QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.4, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 );
1826   QgsCurveTransform src( points );
1827   QCOMPARE( src.controlPoints(), points );
1828   points = QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.5, 0.6 ) << QgsPointXY( 0.6, 0.8 ) << QgsPointXY( 1.0, 1.0 );
1829   src.setControlPoints( points );
1830   QCOMPARE( src.controlPoints(), points );
1831 
1832   src.setControlPoints( QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 1.0, 1.0 ) );
1833   src.addControlPoint( 0.2, 0.3 );
1834   src.addControlPoint( 0.1, 0.4 );
1835   QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.1, 0.4 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
1836 
1837   // remove non-existent point
1838   src.removeControlPoint( 0.6, 0.7 );
1839   QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.1, 0.4 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
1840 
1841   // remove valid point
1842   src.removeControlPoint( 0.1, 0.4 );
1843   QCOMPARE( src.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
1844 
1845   // copy constructor
1846   QgsCurveTransform dest( src );
1847   QCOMPARE( dest.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
1848   // check a value to ensure that derivative matrix was copied OK
1849   QGSCOMPARENEAR( dest.y( 0.5 ), 0.1, 0.638672 );
1850 
1851   // assignment operator
1852   QgsCurveTransform dest2;
1853   dest2 = src;
1854   QCOMPARE( dest2.controlPoints(), QList< QgsPointXY >() << QgsPointXY( 0.0, 0.0 ) << QgsPointXY( 0.2, 0.3 ) << QgsPointXY( 1.0, 1.0 ) );
1855   QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
1856 
1857   // writing and reading from xml
1858   QDomImplementation DomImplementation;
1859   QDomDocumentType documentType =
1860     DomImplementation.createDocumentType(
1861       QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
1862   QDomDocument doc( documentType );
1863 
1864   QDomElement element = doc.createElement( QStringLiteral( "xform" ) );
1865   QVERIFY( src.writeXml( element, doc ) );
1866 
1867   QgsCurveTransform r1;
1868   QVERIFY( r1.readXml( element, doc ) );
1869   QCOMPARE( r1.controlPoints(), src.controlPoints() );
1870   QGSCOMPARENEAR( dest2.y( 0.5 ), 0.1, 0.638672 );
1871 }
1872 
asVariant()1873 void TestQgsProperty::asVariant()
1874 {
1875   QgsProperty original = QgsProperty::fromField( QStringLiteral( "field1" ), true );
1876 
1877   //convert to and from a QVariant
1878   QVariant var = QVariant::fromValue( original );
1879   QVERIFY( var.isValid() );
1880 
1881   QgsProperty fromVar = qvariant_cast<QgsProperty>( var );
1882   QCOMPARE( fromVar.propertyType(), QgsProperty::FieldBasedProperty );
1883   QVERIFY( fromVar.isActive() );
1884   QCOMPARE( fromVar.field(), QStringLiteral( "field1" ) );
1885 }
1886 
isProjectColor()1887 void TestQgsProperty::isProjectColor()
1888 {
1889   QgsProperty p = QgsProperty::fromValue( 3, true );
1890   QVERIFY( !p.isProjectColor() );
1891   p = QgsProperty::fromField( QStringLiteral( "blah" ), true );
1892   QVERIFY( !p.isProjectColor() );
1893   p = QgsProperty::fromExpression( QStringLiteral( "1+2" ), true );
1894   QVERIFY( !p.isProjectColor() );
1895   p = QgsProperty::fromExpression( QStringLiteral( "project_color('mine')" ), true );
1896   QVERIFY( p.isProjectColor() );
1897   p = QgsProperty::fromExpression( QStringLiteral( "project_color('burnt pineapple Skin 76')" ), true );
1898   QVERIFY( p.isProjectColor() );
1899   p.setActive( false );
1900   QVERIFY( p.isProjectColor() );
1901 }
1902 
referencedFieldsIgnoreContext()1903 void TestQgsProperty::referencedFieldsIgnoreContext()
1904 {
1905   // Currently QgsProperty::referencedFields() for an expression will return field names
1906   // only if those field names are present in the context's fields. The ignoreContext
1907   // argument is a workaround for the case when we don't have fields yet.
1908 
1909   QgsProperty p = QgsProperty::fromExpression( QStringLiteral( "foo + bar" ) );
1910   QCOMPARE( p.referencedFields( QgsExpressionContext() ), QSet<QString>() );
1911   QCOMPARE( p.referencedFields( QgsExpressionContext(), true ), QSet<QString>() << QStringLiteral( "foo" ) << QStringLiteral( "bar" ) );
1912 
1913   // if the property is from a field, the ignoreContext does not make a difference
1914   QgsProperty p2 = QgsProperty::fromField( QStringLiteral( "boo" ) );
1915   QCOMPARE( p2.referencedFields( QgsExpressionContext() ), QSet<QString>() << QStringLiteral( "boo" ) );
1916   QCOMPARE( p2.referencedFields( QgsExpressionContext(), true ), QSet<QString>() << QStringLiteral( "boo" ) );
1917 
1918   QgsPropertyCollection collection;
1919   collection.setProperty( 0, p );
1920   collection.setProperty( 1, p2 );
1921 
1922   QCOMPARE( collection.referencedFields( QgsExpressionContext() ), QSet<QString>() << QStringLiteral( "boo" ) );
1923   QCOMPARE( collection.referencedFields( QgsExpressionContext(), true ), QSet<QString>() << QStringLiteral( "boo" ) << QStringLiteral( "foo" ) << QStringLiteral( "bar" ) );
1924 }
1925 
checkCurveResult(const QList<QgsPointXY> & controlPoints,const QVector<double> & x,const QVector<double> & y)1926 void TestQgsProperty::checkCurveResult( const QList<QgsPointXY> &controlPoints, const QVector<double> &x, const QVector<double> &y )
1927 {
1928   // build transform
1929   QgsCurveTransform t( controlPoints );
1930 
1931   // we check two approaches
1932   for ( int i = 0; i < x.count(); ++i )
1933   {
1934     QGSCOMPARENEAR( t.y( x.at( i ) ), y.at( i ), 0.0001 );
1935   }
1936 
1937   QVector< double > results = t.y( x );
1938   for ( int i = 0; i < y.count(); ++i )
1939   {
1940     QGSCOMPARENEAR( results.at( i ), y.at( i ), 0.0001 );
1941   }
1942 }
1943 
1944 QGSTEST_MAIN( TestQgsProperty )
1945 #include "testqgsproperty.moc"
1946