QGIS/tests/src/python/test_qgsproject.py
Nyall Dawson 6cfc6a1b98 Allow retrieval of project layer order through QgsProject
Previously this was only accessible through app
2017-03-13 12:52:55 +10:00

268 lines
9.4 KiB
Python

# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsProject.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
from builtins import chr
from builtins import range
__author__ = 'Sebastian Dietrich'
__date__ = '19/11/2015'
__copyright__ = 'Copyright 2015, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import os
import qgis # NOQA
from qgis.core import (QgsProject,
QgsApplication,
QgsUnitTypes,
QgsCoordinateReferenceSystem,
QgsVectorLayer)
from qgis.gui import (QgsLayerTreeMapCanvasBridge,
QgsMapCanvas)
from qgis.testing import start_app, unittest
from utilities import (unitTestDataPath)
from qgis.PyQt.QtCore import QDir
from qgis.PyQt.QtTest import QSignalSpy
app = start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsProject(unittest.TestCase):
def __init__(self, methodName):
"""Run once on class initialization."""
unittest.TestCase.__init__(self, methodName)
self.messageCaught = False
def test_makeKeyTokens_(self):
# see http://www.w3.org/TR/REC-xml/#d0e804 for a list of valid characters
invalidTokens = []
validTokens = []
# all test tokens will be generated by prepending or inserting characters to this token
validBase = "valid"
# some invalid characters, not allowed anywhere in a token
# note that '/' must not be added here because it is taken as a separator by makeKeyTokens_()
invalidChars = "+*,;<>|!$%()=?#\x01"
# generate the characters that are allowed at the start of a token (and at every other position)
validStartChars = ":_"
charRanges = [
(ord('a'), ord('z')),
(ord('A'), ord('Z')),
(0x00F8, 0x02FF),
(0x0370, 0x037D),
(0x037F, 0x1FFF),
(0x200C, 0x200D),
(0x2070, 0x218F),
(0x2C00, 0x2FEF),
(0x3001, 0xD7FF),
(0xF900, 0xFDCF),
(0xFDF0, 0xFFFD),
# (0x10000, 0xEFFFF), while actually valid, these are not yet accepted by makeKeyTokens_()
]
for r in charRanges:
for c in range(r[0], r[1]):
validStartChars += chr(c)
# generate the characters that are only allowed inside a token, not at the start
validInlineChars = "-.\xB7"
charRanges = [
(ord('0'), ord('9')),
(0x0300, 0x036F),
(0x203F, 0x2040),
]
for r in charRanges:
for c in range(r[0], r[1]):
validInlineChars += chr(c)
# test forbidden start characters
for c in invalidChars + validInlineChars:
invalidTokens.append(c + validBase)
# test forbidden inline characters
for c in invalidChars:
invalidTokens.append(validBase[:4] + c + validBase[4:])
# test each allowed start character
for c in validStartChars:
validTokens.append(c + validBase)
# test each allowed inline character
for c in validInlineChars:
validTokens.append(validBase[:4] + c + validBase[4:])
logger = QgsApplication.messageLog()
logger.messageReceived.connect(self.catchMessage)
prj = QgsProject.instance()
for token in validTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = "valid token '%s' not accepted" % (token)
assert not self.messageCaught, myMessage
for token in invalidTokens:
self.messageCaught = False
prj.readEntry("test", token)
myMessage = "invalid token '%s' accepted" % (token)
assert self.messageCaught, myMessage
logger.messageReceived.disconnect(self.catchMessage)
def catchMessage(self):
self.messageCaught = True
def testCrs(self):
prj = QgsProject.instance()
prj.clear()
self.assertFalse(prj.crs().isValid())
prj.setCrs(QgsCoordinateReferenceSystem.fromOgcWmsCrs('EPSG:3111'))
self.assertEqual(prj.crs().authid(), 'EPSG:3111')
def testEllipsoid(self):
prj = QgsProject.instance()
prj.clear()
prj.setCrs(QgsCoordinateReferenceSystem.fromOgcWmsCrs('EPSG:3111'))
prj.setEllipsoid('WGS84')
self.assertEqual(prj.ellipsoid(), 'WGS84')
# if project has NO crs, then ellipsoid should always be none
prj.setCrs(QgsCoordinateReferenceSystem())
self.assertEqual(prj.ellipsoid(), 'NONE')
def testDistanceUnits(self):
prj = QgsProject.instance()
prj.clear()
prj.setDistanceUnits(QgsUnitTypes.DistanceFeet)
self.assertEqual(prj.distanceUnits(), QgsUnitTypes.DistanceFeet)
def testAreaUnits(self):
prj = QgsProject.instance()
prj.clear()
prj.setAreaUnits(QgsUnitTypes.AreaSquareFeet)
self.assertEqual(prj.areaUnits(), QgsUnitTypes.AreaSquareFeet)
def testReadEntry(self):
prj = QgsProject.instance()
prj.read(os.path.join(TEST_DATA_DIR, 'labeling/test-labeling.qgs'))
# valid key, valid int value
self.assertEqual(prj.readNumEntry("SpatialRefSys", "/ProjectionsEnabled", -1)[0], 0)
# invalid key
self.assertEqual(prj.readNumEntry("SpatialRefSys", "/InvalidKey", -1)[0], -1)
def testEmbeddedGroup(self):
testdata_path = unitTestDataPath('embedded_groups') + '/'
prj_path = os.path.join(testdata_path, "project2.qgs")
prj = QgsProject()
prj.read(prj_path)
layer_tree_group = prj.layerTreeRoot()
layers_ids = layer_tree_group.findLayerIds()
layers_names = []
for layer_id in layers_ids:
name = prj.mapLayer(layer_id).name()
layers_names.append(name)
expected = ['polys', 'lines']
self.assertEqual(sorted(layers_names), sorted(expected))
def testLayerOrder(self):
""" test project layer order"""
prj = QgsProject()
layer = QgsVectorLayer("Point?field=fldtxt:string",
"layer1", "memory")
layer2 = QgsVectorLayer("Point?field=fldtxt:string",
"layer2", "memory")
layer3 = QgsVectorLayer("Point?field=fldtxt:string",
"layer3", "memory")
prj.addMapLayers([layer, layer2, layer3])
layer_order_changed_spy = QSignalSpy(prj.layerOrderChanged)
prj.setLayerOrder([layer2, layer])
self.assertEqual(len(layer_order_changed_spy), 1)
prj.setLayerOrder([layer2, layer])
self.assertEqual(len(layer_order_changed_spy), 1) # no signal, order not changed
self.assertEqual(prj.layerOrder(), [layer2, layer])
prj.setLayerOrder([layer])
self.assertEqual(prj.layerOrder(), [layer])
self.assertEqual(len(layer_order_changed_spy), 2)
# remove a layer
prj.setLayerOrder([layer2, layer, layer3])
self.assertEqual(len(layer_order_changed_spy), 3)
prj.removeMapLayer(layer)
self.assertEqual(prj.layerOrder(), [layer2, layer3])
self.assertEqual(len(layer_order_changed_spy), 4)
# save and restore
file_name = os.path.join(str(QDir.tempPath()), 'proj.qgs')
prj.setFileName(file_name)
prj.write()
prj2 = QgsProject()
prj2.setFileName(file_name)
prj2.read()
self.assertEqual([l.id() for l in prj2.layerOrder()], [layer2.id(), layer3.id()])
# clear project
prj.clear()
self.assertEqual(prj.layerOrder(), [])
def testLayerOrderUpdatedThroughBridge(self):
""" test that project layer order is updated when layer tree changes """
prj = QgsProject.instance()
layer = QgsVectorLayer("Point?field=fldtxt:string",
"layer1", "memory")
layer2 = QgsVectorLayer("Point?field=fldtxt:string",
"layer2", "memory")
layer3 = QgsVectorLayer("Point?field=fldtxt:string",
"layer3", "memory")
prj.addMapLayers([layer, layer2, layer3])
canvas = QgsMapCanvas()
bridge = QgsLayerTreeMapCanvasBridge(prj.layerTreeRoot(), canvas)
#custom layer order
bridge.setHasCustomLayerOrder(True)
bridge.setCustomLayerOrder([layer3.id(), layer.id(), layer2.id()])
app.processEvents()
self.assertEqual([l.id() for l in prj.layerOrder()], [layer3.id(), layer.id(), layer2.id()])
# no custom layer order
bridge.setHasCustomLayerOrder(False)
app.processEvents()
self.assertEqual([l.id() for l in prj.layerOrder()], [layer.id(), layer2.id(), layer3.id()])
# mess around with the layer tree order
root = prj.layerTreeRoot()
layer_node = root.findLayer(layer2.id())
cloned_node = layer_node.clone()
parent = layer_node.parent()
parent.insertChildNode(0, cloned_node)
parent.removeChildNode(layer_node)
app.processEvents()
# make sure project respects this
self.assertEqual([l.id() for l in prj.layerOrder()], [layer2.id(), layer.id(), layer3.id()])
if __name__ == '__main__':
unittest.main()