1 /***************************************************************************
2      testqgsproject.cpp
3      --------------------------------------
4     Date                 : June 2014
5     Copyright            : (C) 2014 by Martin Dobias
6     Email                : wonder.sk at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 #include "qgstest.h"
16 
17 #include <QObject>
18 
19 #include "qgsapplication.h"
20 #include "qgsmarkersymbollayer.h"
21 #include "qgspathresolver.h"
22 #include "qgsproject.h"
23 #include "qgssinglesymbolrenderer.h"
24 #include "qgslayertree.h"
25 #include "qgssettings.h"
26 #include "qgsunittypes.h"
27 #include "qgsvectorlayer.h"
28 #include "qgssymbollayerutils.h"
29 #include "qgslayoutmanager.h"
30 
31 class TestQgsProject : public QObject
32 {
33     Q_OBJECT
34   private slots:
35     void initTestCase();// will be called before the first testfunction is executed.
36     void cleanupTestCase();// will be called after the last testfunction was executed.
37     void init();// will be called before each testfunction is executed.
38     void cleanup();// will be called after every testfunction.
39 
40     void testReadPath();
41     void testPathResolver();
42     void testPathResolverSvg();
43     void testProjectUnits();
44     void variablesChanged();
45     void testLayerFlags();
46     void testLocalFiles();
47     void testLocalUrlFiles();
48     void testReadFlags();
49     void testSetGetCrs();
50     void testEmbeddedLayerGroupFromQgz();
51     void projectSaveUser();
52     void testCrsExpressions();
53     void testCrsValidAfterReadingProjectFile();
54 };
55 
init()56 void TestQgsProject::init()
57 {
58 }
59 
cleanup()60 void TestQgsProject::cleanup()
61 {
62   // will be called after every testfunction.
63 }
64 
initTestCase()65 void TestQgsProject::initTestCase()
66 {
67   // Runs once before any tests are run
68 
69   // Set up the QgsSettings environment
70   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
71   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
72   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
73 
74   QgsApplication::init();
75   QgsApplication::initQgis();
76   QgsSettings().clear();
77 }
78 
79 
cleanupTestCase()80 void TestQgsProject::cleanupTestCase()
81 {
82   // Runs once after all tests are run
83   QgsApplication::exitQgis();
84 }
85 
testReadPath()86 void TestQgsProject::testReadPath()
87 {
88   QgsProject *prj = new QgsProject;
89   // this is a bit hacky as we do not really load such project
90   QString prefix;
91 #if defined(Q_OS_WIN)
92   prefix = "C:";
93 #endif
94   prj->setFileName( prefix + "/home/qgis/a-project-file.qgs" ); // not expected to exist
95   // make sure we work with relative paths!
96   prj->writeEntry( QStringLiteral( "Paths" ), QStringLiteral( "Absolute" ), false );
97 
98   QCOMPARE( prj->readPath( "./x.shp" ), QString( prefix + "/home/qgis/x.shp" ) );
99   QCOMPARE( prj->readPath( "../x.shp" ), QString( prefix + "/home/x.shp" ) );
100 
101   // TODO: old style (seems QGIS < 1.3) - needs existing project file and existing file
102   // QCOMPARE( prj->readPath( "x.shp" ), QString( "/home/qgis/x.shp" ) );
103 
104   // VSI: /vsizip, /vsitar, /vsigzip, *.zip, *.gz, *.tgz, ...
105 
106   QCOMPARE( prj->readPath( "./x.gz" ), QString( prefix + "/home/qgis/x.gz" ) );
107   QCOMPARE( prj->readPath( "/vsigzip/./x.gz" ), QString( "/vsigzip/%1/home/qgis/x.gz" ).arg( prefix ) ); // not sure how useful this really is...
108 
109   delete prj;
110 }
111 
testPathResolver()112 void TestQgsProject::testPathResolver()
113 {
114   // Test resolver with a non existing file path
115   QgsPathResolver resolverLegacy( QStringLiteral( "/home/qgis/test.qgs" ) );
116   QCOMPARE( resolverLegacy.readPath( QString() ), QString() );
117   QCOMPARE( resolverLegacy.writePath( QString() ), QString() );
118   QCOMPARE( resolverLegacy.writePath( "/home/qgis/file1.txt" ), QString( "./file1.txt" ) );
119   QCOMPARE( resolverLegacy.writePath( "/home/qgis/subdir/file1.txt" ), QString( "./subdir/file1.txt" ) );
120   QCOMPARE( resolverLegacy.writePath( "/home/file1.txt" ), QString( "../file1.txt" ) );
121   QCOMPARE( resolverLegacy.readPath( "./file1.txt" ), QString( "/home/qgis/file1.txt" ) );
122   QCOMPARE( resolverLegacy.readPath( "./subdir/file1.txt" ), QString( "/home/qgis/subdir/file1.txt" ) );
123   QCOMPARE( resolverLegacy.readPath( "../file1.txt" ), QString( "/home/file1.txt" ) );
124   QCOMPARE( resolverLegacy.readPath( "/home/qgis/file1.txt" ), QString( "/home/qgis/file1.txt" ) );
125 
126   // Test resolver with existing file path
127   QTemporaryDir tmpDir;
128   QString tmpDirName = tmpDir.path();
129   QDir dir( tmpDirName );
130   dir.mkpath( tmpDirName + "/home/qgis/" );
131 
132   QgsPathResolver resolverRel( QString( tmpDirName + "/home/qgis/test.qgs" ) );
133   QCOMPARE( resolverRel.readPath( QString() ), QString() );
134   QCOMPARE( resolverRel.writePath( QString() ), QString() );
135   QCOMPARE( resolverRel.writePath( tmpDirName + "/home/qgis/file1.txt" ), QString( "./file1.txt" ) );
136   QCOMPARE( resolverRel.writePath( tmpDirName + "/home/qgis/subdir/file1.txt" ), QString( "./subdir/file1.txt" ) );
137   QCOMPARE( resolverRel.writePath( tmpDirName + "/home/file1.txt" ), QString( "../file1.txt" ) );
138   QCOMPARE( resolverRel.readPath( "./file1.txt" ), QString( tmpDirName + "/home/qgis/file1.txt" ) );
139   QCOMPARE( resolverRel.readPath( "./subdir/file1.txt" ), QString( tmpDirName + "/home/qgis/subdir/file1.txt" ) );
140   QCOMPARE( resolverRel.readPath( "../file1.txt" ), QString( tmpDirName + "/home/file1.txt" ) );
141   QCOMPARE( resolverRel.readPath( tmpDirName + "/home/qgis/file1.txt" ), QString( tmpDirName + "/home/qgis/file1.txt" ) );
142 
143   // test older style relative path - file must exist for this to work
144   QTemporaryFile tmpFile;
145   tmpFile.open(); // fileName is not available until we open the file
146   QString tmpName =  tmpFile.fileName();
147   tmpFile.close();
148   QgsPathResolver tempRel( tmpName );
149   QFileInfo fi( tmpName );
150   QFile testFile( fi.path() + QStringLiteral( "/file1.txt" ) );
151   QVERIFY( testFile.open( QIODevice::WriteOnly | QIODevice::Text ) );
152   testFile.close();
153   QVERIFY( QFile::exists( fi.path() + QStringLiteral( "/file1.txt" ) ) );
154   QCOMPARE( tempRel.readPath( "file1.txt" ), QString( fi.path() + QStringLiteral( "/file1.txt" ) ) );
155 
156   QgsPathResolver resolverAbs;
157   QCOMPARE( resolverAbs.writePath( "/home/qgis/file1.txt" ), QString( "/home/qgis/file1.txt" ) );
158   QCOMPARE( resolverAbs.readPath( "/home/qgis/file1.txt" ), QString( "/home/qgis/file1.txt" ) );
159   QCOMPARE( resolverAbs.readPath( "./file1.txt" ), QString( "./file1.txt" ) );
160 
161   // TODO: test non-canonical paths - there are inconsistencies in the implementation
162   // e.g. base filename "/home/qgis/../test.qgs" resolving "/home/qgis/../file1.txt" back and forth
163 }
164 
_useRendererWithSvgSymbol(QgsVectorLayer * layer,const QString & path)165 static void _useRendererWithSvgSymbol( QgsVectorLayer *layer, const QString &path )
166 {
167   QgsSvgMarkerSymbolLayer *sl = new QgsSvgMarkerSymbolLayer( path );
168   QgsMarkerSymbol *markerSymbol = new QgsMarkerSymbol( QgsSymbolLayerList() << sl );
169   QgsSingleSymbolRenderer *renderer = new QgsSingleSymbolRenderer( markerSymbol );
170   layer->setRenderer( renderer );
171 }
172 
_getLayerSvgMarkerPath(const QgsProject & prj,const QString & layerName)173 static QString _getLayerSvgMarkerPath( const QgsProject &prj, const QString &layerName )
174 {
175   QList<QgsMapLayer *> layers = prj.mapLayersByName( layerName );
176   Q_ASSERT( layers.count() == 1 );
177   Q_ASSERT( layers[0]->type() == QgsMapLayerType::VectorLayer );
178   QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( layers[0] );
179   Q_ASSERT( layer->renderer() );
180   Q_ASSERT( layer->renderer()->type() == "singleSymbol" );
181   QgsSingleSymbolRenderer *r = static_cast<QgsSingleSymbolRenderer *>( layer->renderer() );
182   QgsSymbol *s = r->symbol();
183   Q_ASSERT( s && s->symbolLayerCount() == 1 );
184   Q_ASSERT( s->symbolLayer( 0 )->layerType() == "SvgMarker" );
185   QgsSvgMarkerSymbolLayer *sl = static_cast<QgsSvgMarkerSymbolLayer *>( s->symbolLayer( 0 ) );
186   return sl->path();
187 }
188 
_parseSvgPathsForLayers(const QString & projectFilename)189 static QHash<QString, QString> _parseSvgPathsForLayers( const QString &projectFilename )
190 {
191   QHash<QString, QString> projectFileSvgPaths;   // key = layer name, value = svg path
192 
193   QDomDocument doc;
194   QFile projectFile( projectFilename );
195   bool res = projectFile.open( QIODevice::ReadOnly );
196   Q_ASSERT( res );
197   res = doc.setContent( &projectFile );
198   Q_ASSERT( res );
199   projectFile.close();
200 
201   QDomElement docElem = doc.documentElement();
202   QDomElement layersElem = docElem.firstChildElement( QStringLiteral( "projectlayers" ) );
203   QDomElement layerElem = layersElem.firstChildElement();
204   while ( !layerElem.isNull() )
205   {
206     QString layerName = layerElem.firstChildElement( QStringLiteral( "layername" ) ).text();
207     QString svgPath;
208     QDomElement symbolElem = layerElem.firstChildElement( QStringLiteral( "renderer-v2" ) ).firstChildElement( QStringLiteral( "symbols" ) ).firstChildElement( QStringLiteral( "symbol" ) ).firstChildElement( QStringLiteral( "layer" ) );
209     QDomElement propElem = symbolElem.firstChildElement( QStringLiteral( "prop" ) );
210     while ( !propElem.isNull() )
211     {
212       if ( propElem.attribute( QStringLiteral( "k" ) ) == QLatin1String( "name" ) )
213       {
214         svgPath = propElem.attribute( QStringLiteral( "v" ) );
215         break;
216       }
217       propElem = propElem.nextSiblingElement( QStringLiteral( "prop" ) );
218     }
219     projectFileSvgPaths[layerName] = svgPath;
220     layerElem = layerElem.nextSiblingElement();
221   }
222   return projectFileSvgPaths;
223 }
224 
testPathResolverSvg()225 void TestQgsProject::testPathResolverSvg()
226 {
227   QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
228   QString layerPath = dataDir + "/points.shp";
229 
230   QVERIFY( QgsSymbolLayerUtils::svgSymbolNameToPath( QString(), QgsPathResolver() ).isEmpty() );
231   QVERIFY( QgsSymbolLayerUtils::svgSymbolPathToName( QString(), QgsPathResolver() ).isEmpty() );
232 
233   // build a project with 3 layers, each having a simple renderer with SVG marker
234   // - existing SVG file in project dir
235   // - existing SVG file in QGIS dir
236   // - non-exsiting SVG file
237 
238   QTemporaryDir dir;
239   QVERIFY( dir.isValid() );
240   // on mac the returned path was not canonical and the resolver failed to convert paths properly
241   QString dirPath = QFileInfo( dir.path() ).canonicalFilePath();
242 
243   QString projectFilename = dirPath + "/project.qgs";
244   QString ourSvgPath = dirPath + "/valid.svg";
245   QString invalidSvgPath = dirPath + "/invalid.svg";
246 
247   QFile svgFile( ourSvgPath );
248   QVERIFY( svgFile.open( QIODevice::WriteOnly ) );
249   svgFile.write( "<svg/>" );   // not a proper SVG, but good enough for this case
250   svgFile.close();
251 
252   QVERIFY( QFileInfo::exists( ourSvgPath ) );  // should exist now
253 
254   QString librarySvgPath = QgsSymbolLayerUtils::svgSymbolNameToPath( QStringLiteral( "transport/transport_airport.svg" ), QgsPathResolver() );
255   QCOMPARE( QgsSymbolLayerUtils::svgSymbolPathToName( librarySvgPath, QgsPathResolver() ), QStringLiteral( "transport/transport_airport.svg" ) );
256 
257   QgsVectorLayer *layer1 = new QgsVectorLayer( layerPath, QStringLiteral( "points 1" ), QStringLiteral( "ogr" ) );
258   _useRendererWithSvgSymbol( layer1, ourSvgPath );
259 
260   QgsVectorLayer *layer2 = new QgsVectorLayer( layerPath, QStringLiteral( "points 2" ), QStringLiteral( "ogr" ) );
261   _useRendererWithSvgSymbol( layer2, invalidSvgPath );
262 
263   QgsVectorLayer *layer3 = new QgsVectorLayer( layerPath, QStringLiteral( "points 3" ), QStringLiteral( "ogr" ) );
264   _useRendererWithSvgSymbol( layer3, librarySvgPath );
265 
266   QVERIFY( layer1->isValid() );
267 
268   QgsProject project;
269   project.addMapLayers( QList<QgsMapLayer *>() << layer1 << layer2 << layer3 );
270   project.write( projectFilename );
271 
272   // make sure the path resolver works with relative paths (enabled by default)
273   QCOMPARE( project.pathResolver().readPath( "./a.txt" ), QString( dirPath + "/a.txt" ) );
274   QCOMPARE( project.pathResolver().writePath( dirPath + "/a.txt" ), QString( "./a.txt" ) );
275 
276   // check that the saved paths are relative
277 
278   // key = layer name, value = svg path
279   QHash<QString, QString> projectFileSvgPaths = _parseSvgPathsForLayers( projectFilename );
280 
281   QCOMPARE( projectFileSvgPaths.count(), 3 );
282   QCOMPARE( projectFileSvgPaths["points 1"], QString( "./valid.svg" ) ); // relative path to project
283   QCOMPARE( projectFileSvgPaths["points 2"], invalidSvgPath );  // full path to non-existent file (not sure why - but that's how it works now)
284   QCOMPARE( projectFileSvgPaths["points 3"], QString( "transport/transport_airport.svg" ) );  // relative path to library
285 
286   // load project again, check that the paths are absolute
287   QgsProject projectLoaded;
288   projectLoaded.read( projectFilename );
289   QString svg1 = _getLayerSvgMarkerPath( projectLoaded, QStringLiteral( "points 1" ) );
290   QString svg2 = _getLayerSvgMarkerPath( projectLoaded, QStringLiteral( "points 2" ) );
291   QString svg3 = _getLayerSvgMarkerPath( projectLoaded, QStringLiteral( "points 3" ) );
292   QCOMPARE( svg1, ourSvgPath );
293   QCOMPARE( svg2, invalidSvgPath );
294   QCOMPARE( svg3, librarySvgPath );
295 
296   //
297   // now let's use these layers in embedded in another project...
298   //
299 
300   QList<QDomNode> brokenNodes;
301   QgsProject projectMaster;
302   QVERIFY( projectMaster.createEmbeddedLayer( layer1->id(), projectFilename, brokenNodes ) );
303   QVERIFY( projectMaster.createEmbeddedLayer( layer2->id(), projectFilename, brokenNodes ) );
304   QVERIFY( projectMaster.createEmbeddedLayer( layer3->id(), projectFilename, brokenNodes ) );
305 
306   QString svg1x = _getLayerSvgMarkerPath( projectMaster, QStringLiteral( "points 1" ) );
307   QString svg2x = _getLayerSvgMarkerPath( projectLoaded, QStringLiteral( "points 2" ) );
308   QString svg3x = _getLayerSvgMarkerPath( projectLoaded, QStringLiteral( "points 3" ) );
309   QCOMPARE( svg1x, ourSvgPath );
310   QCOMPARE( svg2x, invalidSvgPath );
311   QCOMPARE( svg3x, librarySvgPath );
312 
313 }
314 
315 
testProjectUnits()316 void TestQgsProject::testProjectUnits()
317 {
318   //test setting and retrieving project units
319 
320   // DISTANCE
321 
322   //first set a default QGIS distance unit
323   QgsSettings s;
324   s.setValue( QStringLiteral( "/qgis/measure/displayunits" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::DistanceFeet ) );
325 
326   QgsProject *prj = new QgsProject;
327   // new project should inherit QGIS default distance unit
328   prj->clear();
329   QCOMPARE( prj->distanceUnits(), QgsUnitTypes::DistanceFeet );
330 
331   //changing default QGIS unit should not affect existing project
332   s.setValue( QStringLiteral( "/qgis/measure/displayunits" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::DistanceNauticalMiles ) );
333   QCOMPARE( prj->distanceUnits(), QgsUnitTypes::DistanceFeet );
334 
335   //test setting new units for project
336   prj->setDistanceUnits( QgsUnitTypes::DistanceNauticalMiles );
337   QCOMPARE( prj->distanceUnits(), QgsUnitTypes::DistanceNauticalMiles );
338 
339   // AREA
340 
341   //first set a default QGIS area unit
342   s.setValue( QStringLiteral( "/qgis/measure/areaunits" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::AreaSquareYards ) );
343 
344   // new project should inherit QGIS default area unit
345   prj->clear();
346   QCOMPARE( prj->areaUnits(), QgsUnitTypes::AreaSquareYards );
347 
348   //changing default QGIS unit should not affect existing project
349   s.setValue( QStringLiteral( "/qgis/measure/areaunits" ), QgsUnitTypes::encodeUnit( QgsUnitTypes::AreaAcres ) );
350   QCOMPARE( prj->areaUnits(), QgsUnitTypes::AreaSquareYards );
351 
352   //test setting new units for project
353   prj->setAreaUnits( QgsUnitTypes::AreaAcres );
354   QCOMPARE( prj->areaUnits(), QgsUnitTypes::AreaAcres );
355 
356   delete prj;
357 }
358 
variablesChanged()359 void TestQgsProject::variablesChanged()
360 {
361   QgsProject *prj = new QgsProject;
362   QSignalSpy spyVariablesChanged( prj, &QgsProject::customVariablesChanged );
363   QVariantMap vars;
364   vars.insert( QStringLiteral( "variable" ), QStringLiteral( "1" ) );
365   prj->setCustomVariables( vars );
366   QVERIFY( spyVariablesChanged.count() == 1 );
367   delete prj;
368 }
369 
testLayerFlags()370 void TestQgsProject::testLayerFlags()
371 {
372   QString dataDir( TEST_DATA_DIR ); //defined in CmakeLists.txt
373   QString layerPath = dataDir + "/points.shp";
374   QgsVectorLayer *layer1 = new QgsVectorLayer( layerPath, QStringLiteral( "points 1" ), QStringLiteral( "ogr" ) );
375   QgsVectorLayer *layer2 = new QgsVectorLayer( layerPath, QStringLiteral( "points 2" ), QStringLiteral( "ogr" ) );
376 
377   QgsProject prj;
378   prj.addMapLayer( layer1 );
379   prj.addMapLayer( layer2 );
380 
381   layer2->setFlags( layer2->flags() & ~QgsMapLayer::Removable );
382 
383   QString layer2id = layer2->id();
384 
385   QTemporaryFile f;
386   QVERIFY( f.open() );
387   f.close();
388   prj.setFileName( f.fileName() );
389   prj.write();
390 
391   // test reading required layers back
392   QgsProject prj2;
393   prj2.setFileName( f.fileName() );
394   QVERIFY( prj2.read() );
395   QgsMapLayer *layer = prj.mapLayer( layer2id );
396   QVERIFY( layer );
397   QVERIFY( !layer->flags().testFlag( QgsMapLayer::Removable ) );
398 }
399 
testLocalFiles()400 void TestQgsProject::testLocalFiles()
401 {
402   QTemporaryFile f;
403   QVERIFY( f.open() );
404   f.close();
405   QgsProject prj;
406   QFileInfo info( f.fileName() );
407   prj.setFileName( f.fileName() );
408   prj.write();
409   QString shpPath = info.dir().path() + '/' + info.baseName() + ".shp";
410   QString layerPath = "file://" + shpPath;
411   QFile f2( shpPath );
412   QVERIFY( f2.open( QFile::ReadWrite ) );
413   f2.close();
414   QgsPathResolver resolver( f.fileName( ) );
415   QCOMPARE( resolver.writePath( layerPath ), QString( "./" + info.baseName() + ".shp" ) ) ;
416 
417 }
418 
testLocalUrlFiles()419 void TestQgsProject::testLocalUrlFiles()
420 {
421   QTemporaryFile f;
422   QVERIFY( f.open() );
423   f.close();
424   QgsProject prj;
425   QFileInfo info( f.fileName() );
426   prj.setFileName( f.fileName() );
427   prj.write();
428   QString shpPath = info.dir().path() + '/' + info.baseName() + ".shp";
429   QString extraStuff {"?someVar=someValue&someOtherVar=someOtherValue" };
430   QString layerPath = "file://" + shpPath + extraStuff;
431   QFile f2( shpPath );
432   QVERIFY( f2.open( QFile::ReadWrite ) );
433   f2.close();
434   QgsPathResolver resolver( f.fileName( ) );
435   QCOMPARE( resolver.writePath( layerPath ), QString( "./" + info.baseName() + ".shp" + extraStuff ) ) ;
436 
437 }
438 
testReadFlags()439 void TestQgsProject::testReadFlags()
440 {
441   QString project1Path = QString( TEST_DATA_DIR ) + QStringLiteral( "/embedded_groups/project1.qgs" );
442   QgsProject p;
443   QVERIFY( p.read( project1Path, QgsProject::ReadFlag::FlagDontResolveLayers ) );
444   auto layers = p.mapLayers();
445   QCOMPARE( layers.count(), 3 );
446   // layers should be invalid - we skipped loading them!
447   QVERIFY( !layers.value( QStringLiteral( "points20170310142652246" ) )->isValid() );
448   QVERIFY( !layers.value( QStringLiteral( "lines20170310142652255" ) )->isValid() );
449   QVERIFY( !layers.value( QStringLiteral( "polys20170310142652234" ) )->isValid() );
450 
451   // but they should have renderers (and other stuff!)
452   QCOMPARE( qobject_cast< QgsVectorLayer * >( layers.value( QStringLiteral( "points20170310142652246" ) ) )->renderer()->type(), QStringLiteral( "categorizedSymbol" ) );
453   QCOMPARE( qobject_cast< QgsVectorLayer * >( layers.value( QStringLiteral( "lines20170310142652255" ) ) )->renderer()->type(), QStringLiteral( "categorizedSymbol" ) );
454   QCOMPARE( qobject_cast< QgsVectorLayer * >( layers.value( QStringLiteral( "polys20170310142652234" ) ) )->renderer()->type(), QStringLiteral( "categorizedSymbol" ) );
455   QVERIFY( ! layers.value( QStringLiteral( "polys20170310142652234" ) )->originalXmlProperties().isEmpty() );
456 
457   // do not store styles
458   QVERIFY( p.read( project1Path, QgsProject::ReadFlag::FlagDontStoreOriginalStyles ) );
459   layers = p.mapLayers();
460   QVERIFY( layers.value( QStringLiteral( "polys20170310142652234" ) )->originalXmlProperties().isEmpty() );
461 
462   // project with embedded groups
463   QString project2Path = QString( TEST_DATA_DIR ) + QStringLiteral( "/embedded_groups/project2.qgs" );
464   QgsProject p2;
465   QVERIFY( p2.read( project2Path, QgsProject::ReadFlag::FlagDontResolveLayers ) );
466   // layers should be invalid - we skipped loading them!
467   layers = p2.mapLayers();
468   QCOMPARE( layers.count(), 2 );
469   QVERIFY( !layers.value( QStringLiteral( "lines20170310142652255" ) )->isValid() );
470   QVERIFY( !layers.value( QStringLiteral( "polys20170310142652234" ) )->isValid() );
471   QCOMPARE( qobject_cast< QgsVectorLayer * >( layers.value( QStringLiteral( "lines20170310142652255" ) ) )->renderer()->type(), QStringLiteral( "categorizedSymbol" ) );
472   QCOMPARE( qobject_cast< QgsVectorLayer * >( layers.value( QStringLiteral( "polys20170310142652234" ) ) )->renderer()->type(), QStringLiteral( "categorizedSymbol" ) );
473 
474 
475   QString project3Path = QString( TEST_DATA_DIR ) + QStringLiteral( "/layouts/layout_casting.qgs" );
476   QgsProject p3;
477   QVERIFY( p3.read( project3Path, QgsProject::ReadFlag::FlagDontLoadLayouts ) );
478   QCOMPARE( p3.layoutManager()->layouts().count(), 0 );
479 }
480 
testEmbeddedLayerGroupFromQgz()481 void TestQgsProject::testEmbeddedLayerGroupFromQgz()
482 {
483   QString path = QString( TEST_DATA_DIR ) + QStringLiteral( "/embedded_groups/project1.qgz" );
484   QList<QDomNode> brokenNodes;
485 
486   QgsProject p0;
487   p0.read( path );
488   QgsMapLayer *points = p0.mapLayersByName( "points" )[0];
489   QgsMapLayer *polys = p0.mapLayersByName( "polys" )[0];
490 
491   QgsProject p1;
492   p1.createEmbeddedLayer( points->id(), p0.fileName(), brokenNodes );
493   p1.createEmbeddedGroup( "group1", p0.fileName(), QStringList() );
494 
495   QCOMPARE( p1.layerIsEmbedded( points->id() ), path );
496   QCOMPARE( p1.layerIsEmbedded( polys->id() ), path );
497 
498   // test embedded layers when origin project is something like ../XXX
499   path = QString( TEST_DATA_DIR ) + QStringLiteral( "/embedded_layers/project.qgz" );
500   QgsProject p2;
501   p2.read( path );
502 
503   QgsMapLayer *points2 = p0.mapLayersByName( "points" )[0];
504   bool saveFlag = p2.mEmbeddedLayers[points2->id()].second;
505   QCOMPARE( saveFlag, true );
506 
507   bool valid = p2.loadEmbeddedNodes( p2.layerTreeRoot() );
508   QCOMPARE( valid, true );
509 }
510 
projectSaveUser()511 void TestQgsProject::projectSaveUser()
512 {
513   QgsProject p;
514   QVERIFY( p.saveUser().isEmpty() );
515   QVERIFY( p.saveUserFullName().isEmpty() );
516   QVERIFY( !p.lastSaveDateTime().isValid() );
517 
518   QTemporaryFile f;
519   QVERIFY( f.open() );
520   f.close();
521   p.setFileName( f.fileName() );
522   p.write();
523 
524   QCOMPARE( p.saveUser(), QgsApplication::userLoginName() );
525   QCOMPARE( p.saveUserFullName(), QgsApplication::userFullName() );
526   QCOMPARE( p.lastSaveDateTime().date(), QDateTime::currentDateTime().date() );
527   QCOMPARE( p.lastSaveVersion().text(), QgsProjectVersion( Qgis::version() ).text() );
528 
529   QgsSettings s;
530   s.setValue( QStringLiteral( "projects/anonymize_saved_projects" ), true, QgsSettings::Core );
531 
532   p.write();
533 
534   QVERIFY( p.saveUser().isEmpty() );
535   QVERIFY( p.saveUserFullName().isEmpty() );
536   QVERIFY( !p.lastSaveDateTime().isValid() );
537 
538   s.setValue( QStringLiteral( "projects/anonymize_saved_projects" ), false, QgsSettings::Core );
539 
540   p.write();
541   QCOMPARE( p.saveUser(), QgsApplication::userLoginName() );
542   QCOMPARE( p.saveUserFullName(), QgsApplication::userFullName() );
543   QCOMPARE( p.lastSaveDateTime().date(), QDateTime::currentDateTime().date() );
544 
545   QgsProject p2;
546   QVERIFY( p2.read( QString( TEST_DATA_DIR ) + QStringLiteral( "/embedded_groups/project1.qgs" ) ) );
547   QCOMPARE( p2.lastSaveVersion().text(), QStringLiteral( "2.99.0-Master" ) );
548   p2.clear();
549   QVERIFY( p2.lastSaveVersion().isNull() );
550 }
551 
testSetGetCrs()552 void TestQgsProject::testSetGetCrs()
553 {
554   QgsProject p;
555 
556   // Set 4326
557   //  - CRS changes
558   //  - ellipsoid stays as NONE
559   QSignalSpy crsChangedSpy( &p, &QgsProject::crsChanged );
560   QSignalSpy ellipsoidChangedSpy( &p, &QgsProject::ellipsoidChanged );
561 
562   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
563 
564   QCOMPARE( crsChangedSpy.count(), 1 );
565   QCOMPARE( ellipsoidChangedSpy.count(), 0 );
566 
567   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
568   QCOMPARE( p.ellipsoid(), QStringLiteral( "NONE" ) );
569 
570   crsChangedSpy.clear();
571   ellipsoidChangedSpy.clear();
572 
573   // Set 21781
574   //  - CRS changes
575   //  - ellipsoid stays as NONE
576 
577   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 21781 ) );
578 
579   QCOMPARE( crsChangedSpy.count(), 1 );
580   QCOMPARE( ellipsoidChangedSpy.count(), 0 );
581 
582   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 21781 ) );
583   QCOMPARE( p.ellipsoid(), QStringLiteral( "NONE" ) );
584 
585   crsChangedSpy.clear();
586   ellipsoidChangedSpy.clear();
587 
588   // Set 21781 again, including adjustEllipsoid flag
589   //  - CRS changes
590   //  - ellipsoid changes to Bessel
591 
592   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 21781 ), true );
593 
594   QCOMPARE( crsChangedSpy.count(), 0 );
595   QCOMPARE( ellipsoidChangedSpy.count(), 1 );
596 
597   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 21781 ) );
598 #if PROJ_VERSION_MAJOR>=6
599   QCOMPARE( p.ellipsoid(), QStringLiteral( "EPSG:7004" ) );
600 #else
601   QCOMPARE( p.ellipsoid(), QStringLiteral( "bessel" ) );
602 #endif
603 
604   crsChangedSpy.clear();
605   ellipsoidChangedSpy.clear();
606 
607   // Set 2056, including adjustEllipsoid flag
608   //  - CRS changes
609   //  - ellipsoid stays
610 
611   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 2056 ), true );
612 
613   QCOMPARE( crsChangedSpy.count(), 1 );
614   QCOMPARE( ellipsoidChangedSpy.count(), 0 );
615 
616   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 2056 ) );
617 #if PROJ_VERSION_MAJOR>=6
618   QCOMPARE( p.ellipsoid(), QStringLiteral( "EPSG:7004" ) );
619 #else
620   QCOMPARE( p.ellipsoid(), QStringLiteral( "bessel" ) );
621 #endif
622 }
623 
testCrsValidAfterReadingProjectFile()624 void TestQgsProject::testCrsValidAfterReadingProjectFile()
625 {
626   QgsProject p;
627   QSignalSpy crsChangedSpy( &p, &QgsProject::crsChanged );
628 
629   //  - new project
630   //  - set CRS tp 4326, the crs changes
631   //  - save the project
632   //  - clear()
633   //  - load the project, the CRS should be 4326
634   QTemporaryDir dir;
635   QVERIFY( dir.isValid() );
636   // on mac the returned path was not canonical and the resolver failed to convert paths properly
637   QString dirPath = QFileInfo( dir.path() ).canonicalFilePath();
638   QString projectFilename = dirPath + "/project.qgs";
639 
640   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
641 
642   QCOMPARE( crsChangedSpy.count(), 1 );
643   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
644 
645   QVERIFY( p.write( projectFilename ) );
646   p.clear();
647 
648   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem() );
649   QCOMPARE( crsChangedSpy.count(), 1 );
650 
651   QVERIFY( p.read( projectFilename ) );
652   QCOMPARE( p.crs(), QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
653   QCOMPARE( crsChangedSpy.count(), 2 );
654 }
655 
testCrsExpressions()656 void TestQgsProject::testCrsExpressions()
657 {
658   QgsProject p;
659   QVariant r;
660 
661   p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) );
662 
663   QgsExpressionContext c = p.createExpressionContext();
664 
665   QgsExpression e2( QStringLiteral( "@project_crs" ) );
666   r = e2.evaluate( &c );
667   QCOMPARE( r.toString(), QString( "EPSG:4326" ) );
668 
669   QgsExpression e3( QStringLiteral( "@project_crs_definition" ) );
670   r = e3.evaluate( &c );
671   QCOMPARE( r.toString(), QString( "+proj=longlat +datum=WGS84 +no_defs" ) );
672 
673   QgsExpression e4( QStringLiteral( "@project_units" ) );
674   r = e4.evaluate( &c );
675   QCOMPARE( r.toString(), QString( "degrees" ) );
676 
677   QgsExpression e5( QStringLiteral( "@project_crs_description" ) );
678   r = e5.evaluate( &c );
679   QCOMPARE( r.toString(), QString( "WGS 84" ) );
680 
681   QgsExpression e6( QStringLiteral( "@project_crs_acronym" ) );
682   r = e6.evaluate( &c );
683   QCOMPARE( r.toString(), QString( "longlat" ) );
684 
685   QgsExpression e7( QStringLiteral( "@project_crs_proj4" ) );
686   r = e7.evaluate( &c );
687   QCOMPARE( r.toString(), QString( "+proj=longlat +datum=WGS84 +no_defs" ) );
688 
689   QgsExpression e8( QStringLiteral( "@project_crs_wkt" ) );
690   r = e8.evaluate( &c );
691   QVERIFY( r.toString().length() >= 15 );
692 
693   QgsExpression e9( QStringLiteral( "@project_crs_ellipsoid" ) );
694   r = e9.evaluate( &c );
695 #if PROJ_VERSION_MAJOR>=6
696   QCOMPARE( r.toString(), QString( "EPSG:7030" ) );
697 #else
698   QCOMPARE( r.toString(), QString( "WGS84" ) );
699 #endif
700 }
701 
702 QGSTEST_MAIN( TestQgsProject )
703 #include "testqgsproject.moc"
704