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