# -*- 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__ = '<NAME>'
__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__ = '176c06ceefb5f555205e72b20c962740cc0ec183'

import os

import qgis  # NOQA

from qgis.core import (QgsProject,
                       QgsCoordinateTransformContext,
                       QgsProjectDirtyBlocker,
                       QgsApplication,
                       QgsUnitTypes,
                       QgsCoordinateReferenceSystem,
                       QgsLabelingEngineSettings,
                       QgsVectorLayer,
                       QgsRasterLayer,
                       QgsMapLayer,
                       QgsExpressionContextUtils,
                       QgsProjectColorScheme)
from qgis.gui import (QgsLayerTreeMapCanvasBridge,
                      QgsMapCanvas)

from qgis.PyQt.QtTest import QSignalSpy
from qgis.PyQt.QtCore import QT_VERSION_STR, QTemporaryDir
from qgis.PyQt.QtGui import QColor
from qgis.PyQt import sip

from qgis.testing import start_app, unittest
from utilities import (unitTestDataPath)
from shutil import copyfile

app = start_app()
TEST_DATA_DIR = unitTestDataPath()


def createLayer(name):
    return QgsVectorLayer("Point?field=x:string", name, "memory")


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 testClear(self):
        prj = QgsProject.instance()
        prj.setTitle('xxx')
        spy = QSignalSpy(prj.cleared)
        prj.clear()
        self.assertEqual(len(spy), 1)
        self.assertFalse(prj.title())

    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 testInstance(self):
        """ test retrieving global instance """
        self.assertTrue(QgsProject.instance())

        # register a layer to the singleton
        QgsProject.instance().addMapLayer(createLayer('test'))

        # check that the same instance is returned
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayer(self):
        """ test adding individual map layers to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        self.assertEqual(QgsProject.instance().addMapLayer(l1), l1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        # adding a second layer should leave existing layers intact
        l2 = createLayer('test2')
        self.assertEqual(QgsProject.instance().addMapLayer(l2), l2)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(QgsProject.instance().count(), 2)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerAlreadyAdded(self):
        """ test that already added layers can't be readded to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        QgsProject.instance().addMapLayer(l1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().addMapLayer(l1), None)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerInvalid(self):
        """ test that invalid map layers can be added to registry """
        QgsProject.instance().removeAllMapLayers()

        vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
        self.assertEqual(QgsProject.instance().addMapLayer(vl), vl)
        self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().validCount(), 0)

        self.assertEqual(len(QgsProject.instance().mapLayers(True)), 0)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerSignals(self):
        """ test that signals are correctly emitted when adding map layer"""

        QgsProject.instance().removeAllMapLayers()

        layer_was_added_spy = QSignalSpy(QgsProject.instance().layerWasAdded)
        layers_added_spy = QSignalSpy(QgsProject.instance().layersAdded)
        legend_layers_added_spy = QSignalSpy(QgsProject.instance().legendLayersAdded)

        l1 = createLayer('test')
        QgsProject.instance().addMapLayer(l1)

        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layer_was_added_spy), 1)
        self.assertEqual(len(layers_added_spy), 1)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # layer not added to legend
        QgsProject.instance().addMapLayer(createLayer('test2'), False)
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # try readding a layer already in the registry
        QgsProject.instance().addMapLayer(l1)
        # should be no extra signals emitted
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

    def test_addMapLayers(self):
        """ test adding multiple map layers to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        l2 = createLayer('test2')
        self.assertEqual(set(QgsProject.instance().addMapLayers([l1, l2])), set([l1, l2]))
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(QgsProject.instance().count(), 2)

        # adding more layers should leave existing layers intact
        l3 = createLayer('test3')
        l4 = createLayer('test4')
        self.assertEqual(set(QgsProject.instance().addMapLayers([l3, l4])), set([l3, l4]))
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test3')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test4')), 1)
        self.assertEqual(QgsProject.instance().count(), 4)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersInvalid(self):
        """ test that invalid map layers can be added to registry """
        QgsProject.instance().removeAllMapLayers()

        vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
        self.assertEqual(QgsProject.instance().addMapLayers([vl]), [vl])
        self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().validCount(), 0)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersAlreadyAdded(self):
        """ test that already added layers can't be readded to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        self.assertEqual(QgsProject.instance().addMapLayers([l1]), [l1])
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().addMapLayers([l1]), [])
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersSignals(self):
        """ test that signals are correctly emitted when adding map layers"""
        QgsProject.instance().removeAllMapLayers()

        layer_was_added_spy = QSignalSpy(QgsProject.instance().layerWasAdded)
        layers_added_spy = QSignalSpy(QgsProject.instance().layersAdded)
        legend_layers_added_spy = QSignalSpy(QgsProject.instance().legendLayersAdded)

        l1 = createLayer('test')
        l2 = createLayer('test2')
        QgsProject.instance().addMapLayers([l1, l2])

        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 1)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # layer not added to legend
        QgsProject.instance().addMapLayers([createLayer('test3'), createLayer('test4')], False)
        self.assertEqual(len(layer_was_added_spy), 4)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # try readding a layer already in the registry
        QgsProject.instance().addMapLayers([l1, l2])
        # should be no extra signals emitted
        self.assertEqual(len(layer_was_added_spy), 4)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

    def test_mapLayerById(self):
        """ test retrieving map layer by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        self.assertEqual(QgsProject.instance().mapLayer('bad'), None)
        self.assertEqual(QgsProject.instance().mapLayer(None), None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])

        self.assertEqual(QgsProject.instance().mapLayer('bad'), None)
        self.assertEqual(QgsProject.instance().mapLayer(None), None)
        self.assertEqual(QgsProject.instance().mapLayer(l1.id()), l1)
        self.assertEqual(QgsProject.instance().mapLayer(l2.id()), l2)

    def test_mapLayersByName(self):
        """ test retrieving map layer by name """
        p = QgsProject()

        # test no crash with empty registry
        self.assertEqual(p.mapLayersByName('bad'), [])
        self.assertEqual(p.mapLayersByName(None), [])

        l1 = createLayer('test')
        l2 = createLayer('test2')

        p.addMapLayers([l1, l2])

        self.assertEqual(p.mapLayersByName('bad'), [])
        self.assertEqual(p.mapLayersByName(None), [])
        self.assertEqual(p.mapLayersByName('test'), [l1])
        self.assertEqual(p.mapLayersByName('test2'), [l2])

        #duplicate name
        l3 = createLayer('test')
        p.addMapLayer(l3)
        self.assertEqual(set(p.mapLayersByName('test')), set([l1, l3]))

    def test_mapLayers(self):
        """ test retrieving map layers list """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        self.assertEqual(QgsProject.instance().mapLayers(), {})

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])

        self.assertEqual(QgsProject.instance().mapLayers(), {l1.id(): l1, l2.id(): l2})

    def test_removeMapLayersById(self):
        """ test removing map layers by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayers(['bad'])
        QgsProject.instance().removeMapLayers([None])

        l1 = createLayer('test')
        l2 = createLayer('test2')
        l3 = createLayer('test3')

        QgsProject.instance().addMapLayers([l1, l2, l3])
        self.assertEqual(QgsProject.instance().count(), 3)

        #remove bad layers
        QgsProject.instance().removeMapLayers(['bad'])
        self.assertEqual(QgsProject.instance().count(), 3)
        QgsProject.instance().removeMapLayers([None])
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove valid layers
        l1_id = l1.id()
        QgsProject.instance().removeMapLayers([l1_id])
        self.assertEqual(QgsProject.instance().count(), 2)
        # double remove
        QgsProject.instance().removeMapLayers([l1_id])
        self.assertEqual(QgsProject.instance().count(), 2)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove multiple
        QgsProject.instance().removeMapLayers([l2.id(), l3.id()])
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l4 = createLayer('test4')
        QgsProject.instance().removeMapLayers([l4.id()])
        self.assertFalse(sip.isdeleted(l4))

    # fails on qt5 due to removeMapLayers list type conversion - needs a PyName alias
    # added to removeMapLayers for QGIS 3.0
    @unittest.expectedFailure(QT_VERSION_STR[0] == '5')
    def test_removeMapLayersByLayer(self):
        """ test removing map layers by layer"""
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayers([None])

        l1 = createLayer('test')
        l2 = createLayer('test2')
        l3 = createLayer('test3')

        QgsProject.instance().addMapLayers([l1, l2, l3])
        self.assertEqual(QgsProject.instance().count(), 3)

        #remove bad layers
        QgsProject.instance().removeMapLayers([None])
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove valid layers
        QgsProject.instance().removeMapLayers([l1])
        self.assertEqual(QgsProject.instance().count(), 2)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove multiple
        QgsProject.instance().removeMapLayers([l2, l3])
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))
        self.assertTrue(sip.isdeleted(l3))

    def test_removeMapLayerById(self):
        """ test removing a map layer by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayer('bad')
        QgsProject.instance().removeMapLayer(None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)

        #remove bad layers
        QgsProject.instance().removeMapLayer('bad')
        self.assertEqual(QgsProject.instance().count(), 2)
        QgsProject.instance().removeMapLayer(None)
        self.assertEqual(QgsProject.instance().count(), 2)

        # remove valid layers
        l1_id = l1.id()
        QgsProject.instance().removeMapLayer(l1_id)
        self.assertEqual(QgsProject.instance().count(), 1)
        # double remove
        QgsProject.instance().removeMapLayer(l1_id)
        self.assertEqual(QgsProject.instance().count(), 1)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove second layer
        QgsProject.instance().removeMapLayer(l2.id())
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3.id())
        self.assertFalse(sip.isdeleted(l3))

    def test_removeMapLayerByLayer(self):
        """ test removing a map layer by layer """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayer('bad')
        QgsProject.instance().removeMapLayer(None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)

        #remove bad layers
        QgsProject.instance().removeMapLayer(None)
        self.assertEqual(QgsProject.instance().count(), 2)
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3)
        self.assertEqual(QgsProject.instance().count(), 2)

        # remove valid layers
        QgsProject.instance().removeMapLayer(l1)
        self.assertEqual(QgsProject.instance().count(), 1)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove second layer
        QgsProject.instance().removeMapLayer(l2)
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3)
        self.assertFalse(sip.isdeleted(l3))

    def test_removeAllMapLayers(self):
        """ test removing all map layers from registry """
        QgsProject.instance().removeAllMapLayers()
        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)
        QgsProject.instance().removeAllMapLayers()
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertEqual(QgsProject.instance().mapLayersByName('test'), [])
        self.assertEqual(QgsProject.instance().mapLayersByName('test2'), [])

    def test_addRemoveLayersSignals(self):
        """ test that signals are correctly emitted when removing map layers"""
        QgsProject.instance().removeAllMapLayers()

        layers_will_be_removed_spy = QSignalSpy(QgsProject.instance().layersWillBeRemoved)
        layer_will_be_removed_spy_str = QSignalSpy(QgsProject.instance().layerWillBeRemoved[str])
        layer_will_be_removed_spy_layer = QSignalSpy(QgsProject.instance().layerWillBeRemoved[QgsMapLayer])
        layers_removed_spy = QSignalSpy(QgsProject.instance().layersRemoved)
        layer_removed_spy = QSignalSpy(QgsProject.instance().layerRemoved)
        remove_all_spy = QSignalSpy(QgsProject.instance().removeAll)

        l1 = createLayer('l1')
        l2 = createLayer('l2')
        l3 = createLayer('l3')
        l4 = createLayer('l4')
        QgsProject.instance().addMapLayers([l1, l2, l3, l4])

        # remove 1 layer
        QgsProject.instance().removeMapLayer(l1)
        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layers_will_be_removed_spy), 1)
        self.assertEqual(len(layer_will_be_removed_spy_str), 1)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 1)
        self.assertEqual(len(layers_removed_spy), 1)
        self.assertEqual(len(layer_removed_spy), 1)
        self.assertEqual(len(remove_all_spy), 0)
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove 2 layers at once
        QgsProject.instance().removeMapLayers([l2.id(), l3.id()])
        self.assertEqual(len(layers_will_be_removed_spy), 2)
        self.assertEqual(len(layer_will_be_removed_spy_str), 3)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 3)
        self.assertEqual(len(layers_removed_spy), 2)
        self.assertEqual(len(layer_removed_spy), 3)
        self.assertEqual(len(remove_all_spy), 0)
        self.assertEqual(QgsProject.instance().count(), 1)

        # remove all
        QgsProject.instance().removeAllMapLayers()
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

        #remove some layers which aren't in the registry
        QgsProject.instance().removeMapLayers(['asdasd'])
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

        l5 = createLayer('test5')
        QgsProject.instance().removeMapLayer(l5)
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

    def test_RemoveLayerShouldNotSegFault(self):
        QgsProject.instance().removeAllMapLayers()

        reg = QgsProject.instance()
        # Should not segfault
        reg.removeMapLayers(['not_exists'])
        reg.removeMapLayer('not_exists2')

        # check also that the removal of an unexistent layer does not insert a null layer
        for k, layer in list(reg.mapLayers().items()):
            assert(layer is not None)

    def testTakeLayer(self):
        # test taking ownership of a layer from the project
        l1 = createLayer('l1')
        l2 = createLayer('l2')
        p = QgsProject()

        # add one layer to project
        p.addMapLayer(l1)
        self.assertEqual(p.mapLayers(), {l1.id(): l1})
        self.assertEqual(l1.parent().parent(), p)

        # try taking some layers which don't exist in project
        self.assertFalse(p.takeMapLayer(None))
        self.assertFalse(p.takeMapLayer(l2))
        # but l2 should still exist..
        self.assertTrue(l2.isValid())

        # take layer from project
        self.assertEqual(p.takeMapLayer(l1), l1)
        self.assertFalse(p.mapLayers()) # no layers left
        # but l1 should still exist
        self.assertTrue(l1.isValid())
        # layer should have no parent now
        self.assertFalse(l1.parent())

        # destroy project
        p = None
        self.assertTrue(l1.isValid())

    def test_transactionsGroup(self):
        # Undefined transaction group (wrong provider key).
        QgsProject.instance().setAutoTransaction(True)
        noTg = QgsProject.instance().transactionGroup("provider-key", "database-connection-string")
        self.assertIsNone(noTg)

    def test_zip_new_project(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        # zip with existing file
        open(tmpFile, 'a').close()

        project = QgsProject()
        self.assertTrue(project.write(tmpFile))

        # zip with non existing file
        os.remove(tmpFile)

        project = QgsProject()
        self.assertTrue(project.write(tmpFile))
        self.assertTrue(os.path.isfile(tmpFile))

    def test_zip_invalid_path(self):
        project = QgsProject()
        self.assertFalse(project.write())
        self.assertFalse(project.write(""))
        self.assertFalse(project.write("/fake/test.zip"))

    def test_zip_filename(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        project = QgsProject()
        self.assertFalse(project.write())

        project.setFileName(tmpFile)
        self.assertTrue(project.write())
        self.assertTrue(os.path.isfile(tmpFile))

    def test_unzip_invalid_path(self):
        project = QgsProject()
        self.assertFalse(project.read())
        self.assertFalse(project.read(""))
        self.assertFalse(project.read("/fake/test.zip"))

    def test_zip_unzip(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "lines.shp"), "lines", "ogr")
        project.addMapLayers([l0, l1])

        self.assertTrue(project.write(tmpFile))

        project2 = QgsProject()
        self.assertFalse(project2.isZipped())
        self.assertTrue(project2.fileName() == "")
        self.assertTrue(project2.read(tmpFile))
        self.assertTrue(project2.isZipped())
        self.assertTrue(project2.fileName() == tmpFile)
        layers = project2.mapLayers()

        self.assertEqual(len(layers.keys()), 2)
        self.assertTrue(layers[l0.id()].isValid(), True)
        self.assertTrue(layers[l1.id()].isValid(), True)

        project2.clear()
        self.assertFalse(project2.isZipped())

    def testUpgradeOtfFrom2x(self):
        """
        Test that upgrading a 2.x project correctly brings across project CRS and OTF transformation settings
        """
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'projects', 'test_memory_layer_proj.qgs'))
        self.assertTrue(prj.crs().isValid())
        self.assertEqual(prj.crs().authid(), 'EPSG:2056')

    def testRelativePaths(self):
        """
        Test whether paths to layer sources are stored as relative to the project path
        """
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())
        copyfile(os.path.join(TEST_DATA_DIR, "points.shp"), os.path.join(tmpDir.path(), "points.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.dbf"), os.path.join(tmpDir.path(), "points.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.shx"), os.path.join(tmpDir.path(), "points.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shp"), os.path.join(tmpDir.path(), "lines.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.dbf"), os.path.join(tmpDir.path(), "lines.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shx"), os.path.join(tmpDir.path(), "lines.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "landsat_4326.tif"), os.path.join(tmpDir.path(), "landsat_4326.tif"))

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(tmpDir.path(), "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(tmpDir.path(), "lines.shp"), "lines", "ogr")
        l2 = QgsRasterLayer(os.path.join(tmpDir.path(), "landsat_4326.tif"), "landsat", "gdal")
        self.assertTrue(l0.isValid())
        self.assertTrue(l1.isValid())
        self.assertTrue(l2.isValid())
        self.assertTrue(project.addMapLayers([l0, l1, l2]))
        self.assertTrue(project.write(tmpFile))
        del project

        with open(tmpFile, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="./lines.shp"' in content)
            self.assertTrue('source="./points.shp"' in content)
            self.assertTrue('source="./landsat_4326.tif"' in content)

        # Re-read the project and store absolute
        project = QgsProject()
        self.assertTrue(project.read(tmpFile))
        store = project.layerStore()
        self.assertEquals(set([l.name() for l in store.mapLayers().values()]), set(['lines', 'landsat', 'points']))
        project.writeEntryBool('Paths', '/Absolute', True)
        tmpFile2 = "{}/project2.qgs".format(tmpDir.path())
        self.assertTrue(project.write(tmpFile2))

        with open(tmpFile2, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="{}/lines.shp"'.format(tmpDir.path()) in content)
            self.assertTrue('source="{}/points.shp"'.format(tmpDir.path()) in content)
            self.assertTrue('source="{}/landsat_4326.tif"'.format(tmpDir.path()) in content)

        del project

    def testSymbolicLinkInProjectPath(self):
        """
        Test whether paths to layer sources relative to the project are stored correctly
        when project'name contains a symbolic link.
        In other words, test if project's and layers' names are correctly resolved.
        """
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())
        copyfile(os.path.join(TEST_DATA_DIR, "points.shp"), os.path.join(tmpDir.path(), "points.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.dbf"), os.path.join(tmpDir.path(), "points.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.shx"), os.path.join(tmpDir.path(), "points.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shp"), os.path.join(tmpDir.path(), "lines.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.dbf"), os.path.join(tmpDir.path(), "lines.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shx"), os.path.join(tmpDir.path(), "lines.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "landsat_4326.tif"), os.path.join(tmpDir.path(), "landsat_4326.tif"))

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(tmpDir.path(), "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(tmpDir.path(), "lines.shp"), "lines", "ogr")
        l2 = QgsRasterLayer(os.path.join(tmpDir.path(), "landsat_4326.tif"), "landsat", "gdal")
        self.assertTrue(l0.isValid())
        self.assertTrue(l1.isValid())
        self.assertTrue(l2.isValid())
        self.assertTrue(project.addMapLayers([l0, l1, l2]))
        self.assertTrue(project.write(tmpFile))
        del project

        # Create symbolic link to previous project
        tmpDir2 = QTemporaryDir()
        symlinkDir = os.path.join(tmpDir2.path(), "dir")
        os.symlink(tmpDir.path(), symlinkDir)
        tmpFile = "{}/project.qgs".format(symlinkDir)

        # Open project from symmlink and force re-save.
        project = QgsProject()
        self.assertTrue(project.read(tmpFile))
        self.assertTrue(project.write(tmpFile))
        del project

        with open(tmpFile, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="./lines.shp"' in content)
            self.assertTrue('source="./points.shp"' in content)
            self.assertTrue('source="./landsat_4326.tif"' in content)

    def testHomePath(self):
        p = QgsProject()
        path_changed_spy = QSignalSpy(p.homePathChanged)
        self.assertFalse(p.homePath())
        self.assertFalse(p.presetHomePath())

        # simulate save file
        tmp_dir = QTemporaryDir()
        tmp_file = "{}/project.qgs".format(tmp_dir.path())
        with open(tmp_file, 'w') as f:
            pass
        p.setFileName(tmp_file)

        # home path should be file path
        self.assertEqual(p.homePath(), tmp_dir.path())
        self.assertFalse(p.presetHomePath())
        self.assertEqual(len(path_changed_spy), 1)

        # manually override home path
        p.setPresetHomePath('/tmp/my_path')
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)
        # check project scope
        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '/tmp/my_path')

        # no extra signal if path is unchanged
        p.setPresetHomePath('/tmp/my_path')
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)

        # setting file name should not affect home path is manually set
        tmp_file_2 = "{}/project/project2.qgs".format(tmp_dir.path())
        os.mkdir(tmp_dir.path() + '/project')
        with open(tmp_file_2, 'w') as f:
            pass
        p.setFileName(tmp_file_2)
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '/tmp/my_path')

        # clear manual path
        p.setPresetHomePath('')
        self.assertEqual(p.homePath(), tmp_dir.path() + '/project')
        self.assertFalse(p.presetHomePath())
        self.assertEqual(len(path_changed_spy), 3)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), tmp_dir.path() + '/project')

        # relative path
        p.setPresetHomePath('../home')
        self.assertEqual(p.homePath(), tmp_dir.path() + '/home')
        self.assertEqual(p.presetHomePath(), '../home')
        self.assertEqual(len(path_changed_spy), 4)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), tmp_dir.path() + '/home')

        # relative path, no filename
        p.setFileName('')
        self.assertEqual(p.homePath(), '../home')
        self.assertEqual(p.presetHomePath(), '../home')

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '../home')

    def testDirtyBlocker(self):
        # first test manual QgsProjectDirtyBlocker construction
        p = QgsProject()

        dirty_spy = QSignalSpy(p.isDirtyChanged)
        # ^ will do *whatever* it takes to discover the enemy's secret plans!

        # simple checks
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 1)
        self.assertEqual(dirty_spy[-1], [True])
        p.setDirty(True) # already dirty
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 1)
        p.setDirty(False)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 2)
        self.assertEqual(dirty_spy[-1], [False])
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 3)
        self.assertEqual(dirty_spy[-1], [True])

        # with a blocker
        blocker = QgsProjectDirtyBlocker(p)
        # blockers will allow cleaning projects
        p.setDirty(False)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        # but not dirtying!
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        # nested block
        blocker2 = QgsProjectDirtyBlocker(p)
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        del blocker2
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        del blocker
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 5)
        self.assertEqual(dirty_spy[-1], [True])

        # using python context manager
        with QgsProject.blockDirtying(p):
            # cleaning allowed
            p.setDirty(False)
            self.assertFalse(p.isDirty())
            self.assertEqual(len(dirty_spy), 6)
            self.assertEqual(dirty_spy[-1], [False])
            # but not dirtying!
            p.setDirty(True)
            self.assertFalse(p.isDirty())
            self.assertEqual(len(dirty_spy), 6)
            self.assertEqual(dirty_spy[-1], [False])

        # unblocked
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 7)
        self.assertEqual(dirty_spy[-1], [True])

    def testCustomLayerOrderFrom2xProject(self):
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'layer_rendering_order_issue_qgis3.qgs'))

        layer_x = prj.mapLayers()['x20180406151213536']
        layer_y = prj.mapLayers()['y20180406151217017']

        # check layer order
        tree = prj.layerTreeRoot()
        self.assertEqual(tree.children()[0].layer(), layer_x)
        self.assertEqual(tree.children()[1].layer(), layer_y)
        self.assertTrue(tree.hasCustomLayerOrder())
        self.assertEqual(tree.customLayerOrder(), [layer_y, layer_x])
        self.assertEqual(tree.layerOrder(), [layer_y, layer_x])

    def testCustomLayerOrderFrom3xProject(self):
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'layer_rendering_order_qgis3_project.qgs'))

        layer_x = prj.mapLayers()['x20180406151213536']
        layer_y = prj.mapLayers()['y20180406151217017']

        # check layer order
        tree = prj.layerTreeRoot()
        self.assertEqual(tree.children()[0].layer(), layer_x)
        self.assertEqual(tree.children()[1].layer(), layer_y)
        self.assertTrue(tree.hasCustomLayerOrder())
        self.assertEqual(tree.customLayerOrder(), [layer_y, layer_x])
        self.assertEqual(tree.layerOrder(), [layer_y, layer_x])

    def testPalPropertiesReadWrite(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())

        s0 = QgsLabelingEngineSettings()
        s0.setNumCandidatePositions(3, 33, 333)

        p0 = QgsProject()
        p0.setFileName(tmpFile)
        p0.setLabelingEngineSettings(s0)
        p0.write()

        p1 = QgsProject()
        p1.read(tmpFile)

        s1 = p1.labelingEngineSettings()
        candidates = s1.numCandidatePositions()

        self.assertEqual(candidates[0], 3)
        self.assertEqual(candidates[1], 33)
        self.assertEqual(candidates[2], 333)

    def testLayerChangeDirtiesProject(self):
        """
        Test that making changes to certain layer properties results in dirty projects
        """
        p = QgsProject()
        l = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "points.shp"), "points", "ogr")
        self.assertTrue(l.isValid())
        self.assertTrue(p.addMapLayers([l]))
        p.setDirty(False)

        l.setCrs(QgsCoordinateReferenceSystem('EPSG:3111'))
        self.assertTrue(p.isDirty())
        p.setDirty(False)

        l.setName('test')
        self.assertTrue(p.isDirty())
        p.setDirty(False)

        self.assertTrue(l.setSubsetString('class=\'a\''))
        self.assertTrue(p.isDirty())

    def testProjectTitleWithPeriod(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/2.18.21.qgs".format(tmpDir.path())
        tmpFile2 = "{}/qgis-3.2.0.qgs".format(tmpDir.path())

        p0 = QgsProject()
        p0.setFileName(tmpFile)

        p1 = QgsProject()
        p1.setFileName(tmpFile2)

        self.assertEqual(p0.baseName(), '2.18.21')
        self.assertEqual(p1.baseName(), 'qgis-3.2.0')

    def testWriteEntry(self):

        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())

        # zip with existing file
        project = QgsProject()
        query = 'select * from "sample DH" where "sample DH"."Elev" > 130 and "sample DH"."Elev" < 140'
        self.assertTrue(project.writeEntry('myscope', 'myentry', query))
        self.assertTrue(project.write(tmpFile))

        self.assertTrue(project.read(tmpFile))
        q, ok = project.readEntry('myscope', 'myentry')
        self.assertTrue(ok)
        self.assertEqual(q, query)

    def testDirtying(self):

        project = QgsProject()

        # writing a new entry should dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', True))
        self.assertTrue(project.isDirty())

        # over-writing a pre-existing entry with the same value should _not_ dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', True))
        self.assertFalse(project.isDirty())

        # over-writing a pre-existing entry with a different value should dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', False))
        self.assertTrue(project.isDirty())

        # removing an existing entry should dirty the project
        project.setDirty(False)
        self.assertTrue(project.removeEntry('myscope', 'myentry'))
        self.assertTrue(project.isDirty())

        # removing a non-existing entry should _not_ dirty the project
        project.setDirty(False)
        self.assertTrue(project.removeEntry('myscope', 'myentry'))
        self.assertFalse(project.isDirty())

        # setting a project CRS with a new value should dirty the project
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        project.setDirty(False)
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:3148'))
        self.assertTrue(project.isDirty())

        # setting a project CRS with the same project CRS should not dirty the project
        project.setDirty(False)
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:3148'))
        self.assertFalse(project.isDirty())

    def testColorScheme(self):
        p = QgsProject.instance()
        spy = QSignalSpy(p.projectColorsChanged)
        p.setProjectColors([[QColor(255, 0, 0), 'red'], [QColor(0, 255, 0), 'green']])
        self.assertEqual(len(spy), 1)
        scheme = [s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme)][0]
        self.assertEqual([[c[0].name(), c[1]] for c in scheme.fetchColors()], [['#ff0000', 'red'], ['#00ff00', 'green']])
        # except color changed signal when clearing project
        p.clear()
        self.assertEqual(len(spy), 2)
        self.assertEqual([[c[0].name(), c[1]] for c in scheme.fetchColors()], [])

        # should be no signal on project destruction -- can cause a crash
        p = QgsProject()
        spy = QSignalSpy(p.projectColorsChanged)
        p.deleteLater()
        del p
        self.assertEqual(len(spy), 0)

    def testTransformContextSignalIsEmitted(self):
        """Test that when a project transform context changes a transformContextChanged signal is emitted"""

        p = QgsProject()
        spy = QSignalSpy(p.transformContextChanged)
        ctx = QgsCoordinateTransformContext()
        ctx.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(4326), QgsCoordinateReferenceSystem(3857), 1234, 1235)
        p.setTransformContext(ctx)
        self.assertEqual(len(spy), 1)


if __name__ == '__main__':
    unittest.main()

# -*- 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__ = '<NAME>'
__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__ = '176c06ceefb5f555205e72b20c962740cc0ec183'

import os

import qgis  # NOQA

from qgis.core import (QgsProject,
                       QgsCoordinateTransformContext,
                       QgsProjectDirtyBlocker,
                       QgsApplication,
                       QgsUnitTypes,
                       QgsCoordinateReferenceSystem,
                       QgsLabelingEngineSettings,
                       QgsVectorLayer,
                       QgsRasterLayer,
                       QgsMapLayer,
                       QgsExpressionContextUtils,
                       QgsProjectColorScheme)
from qgis.gui import (QgsLayerTreeMapCanvasBridge,
                      QgsMapCanvas)

from qgis.PyQt.QtTest import QSignalSpy
from qgis.PyQt.QtCore import QT_VERSION_STR, QTemporaryDir
from qgis.PyQt.QtGui import QColor
from qgis.PyQt import sip

from qgis.testing import start_app, unittest
from utilities import (unitTestDataPath)
from shutil import copyfile

app = start_app()
TEST_DATA_DIR = unitTestDataPath()


def createLayer(name):
    return QgsVectorLayer("Point?field=x:string", name, "memory")


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 testClear(self):
        prj = QgsProject.instance()
        prj.setTitle('xxx')
        spy = QSignalSpy(prj.cleared)
        prj.clear()
        self.assertEqual(len(spy), 1)
        self.assertFalse(prj.title())

    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 testInstance(self):
        """ test retrieving global instance """
        self.assertTrue(QgsProject.instance())

        # register a layer to the singleton
        QgsProject.instance().addMapLayer(createLayer('test'))

        # check that the same instance is returned
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayer(self):
        """ test adding individual map layers to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        self.assertEqual(QgsProject.instance().addMapLayer(l1), l1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        # adding a second layer should leave existing layers intact
        l2 = createLayer('test2')
        self.assertEqual(QgsProject.instance().addMapLayer(l2), l2)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(QgsProject.instance().count(), 2)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerAlreadyAdded(self):
        """ test that already added layers can't be readded to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        QgsProject.instance().addMapLayer(l1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().addMapLayer(l1), None)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerInvalid(self):
        """ test that invalid map layers can be added to registry """
        QgsProject.instance().removeAllMapLayers()

        vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
        self.assertEqual(QgsProject.instance().addMapLayer(vl), vl)
        self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().validCount(), 0)

        self.assertEqual(len(QgsProject.instance().mapLayers(True)), 0)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayerSignals(self):
        """ test that signals are correctly emitted when adding map layer"""

        QgsProject.instance().removeAllMapLayers()

        layer_was_added_spy = QSignalSpy(QgsProject.instance().layerWasAdded)
        layers_added_spy = QSignalSpy(QgsProject.instance().layersAdded)
        legend_layers_added_spy = QSignalSpy(QgsProject.instance().legendLayersAdded)

        l1 = createLayer('test')
        QgsProject.instance().addMapLayer(l1)

        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layer_was_added_spy), 1)
        self.assertEqual(len(layers_added_spy), 1)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # layer not added to legend
        QgsProject.instance().addMapLayer(createLayer('test2'), False)
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # try readding a layer already in the registry
        QgsProject.instance().addMapLayer(l1)
        # should be no extra signals emitted
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

    def test_addMapLayers(self):
        """ test adding multiple map layers to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        l2 = createLayer('test2')
        self.assertEqual(set(QgsProject.instance().addMapLayers([l1, l2])), set([l1, l2]))
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(QgsProject.instance().count(), 2)

        # adding more layers should leave existing layers intact
        l3 = createLayer('test3')
        l4 = createLayer('test4')
        self.assertEqual(set(QgsProject.instance().addMapLayers([l3, l4])), set([l3, l4]))
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test2')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test3')), 1)
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test4')), 1)
        self.assertEqual(QgsProject.instance().count(), 4)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersInvalid(self):
        """ test that invalid map layers can be added to registry """
        QgsProject.instance().removeAllMapLayers()

        vl = QgsVectorLayer("Point?field=x:string", 'test', "xxx")
        self.assertEqual(QgsProject.instance().addMapLayers([vl]), [vl])
        self.assertFalse(vl in QgsProject.instance().mapLayers(True).values())
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().validCount(), 0)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersAlreadyAdded(self):
        """ test that already added layers can't be readded to registry """
        QgsProject.instance().removeAllMapLayers()

        l1 = createLayer('test')
        self.assertEqual(QgsProject.instance().addMapLayers([l1]), [l1])
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)
        self.assertEqual(QgsProject.instance().addMapLayers([l1]), [])
        self.assertEqual(len(QgsProject.instance().mapLayersByName('test')), 1)
        self.assertEqual(QgsProject.instance().count(), 1)

        QgsProject.instance().removeAllMapLayers()

    def test_addMapLayersSignals(self):
        """ test that signals are correctly emitted when adding map layers"""
        QgsProject.instance().removeAllMapLayers()

        layer_was_added_spy = QSignalSpy(QgsProject.instance().layerWasAdded)
        layers_added_spy = QSignalSpy(QgsProject.instance().layersAdded)
        legend_layers_added_spy = QSignalSpy(QgsProject.instance().legendLayersAdded)

        l1 = createLayer('test')
        l2 = createLayer('test2')
        QgsProject.instance().addMapLayers([l1, l2])

        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layer_was_added_spy), 2)
        self.assertEqual(len(layers_added_spy), 1)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # layer not added to legend
        QgsProject.instance().addMapLayers([createLayer('test3'), createLayer('test4')], False)
        self.assertEqual(len(layer_was_added_spy), 4)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

        # try readding a layer already in the registry
        QgsProject.instance().addMapLayers([l1, l2])
        # should be no extra signals emitted
        self.assertEqual(len(layer_was_added_spy), 4)
        self.assertEqual(len(layers_added_spy), 2)
        self.assertEqual(len(legend_layers_added_spy), 1)

    def test_mapLayerById(self):
        """ test retrieving map layer by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        self.assertEqual(QgsProject.instance().mapLayer('bad'), None)
        self.assertEqual(QgsProject.instance().mapLayer(None), None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])

        self.assertEqual(QgsProject.instance().mapLayer('bad'), None)
        self.assertEqual(QgsProject.instance().mapLayer(None), None)
        self.assertEqual(QgsProject.instance().mapLayer(l1.id()), l1)
        self.assertEqual(QgsProject.instance().mapLayer(l2.id()), l2)

    def test_mapLayersByName(self):
        """ test retrieving map layer by name """
        p = QgsProject()

        # test no crash with empty registry
        self.assertEqual(p.mapLayersByName('bad'), [])
        self.assertEqual(p.mapLayersByName(None), [])

        l1 = createLayer('test')
        l2 = createLayer('test2')

        p.addMapLayers([l1, l2])

        self.assertEqual(p.mapLayersByName('bad'), [])
        self.assertEqual(p.mapLayersByName(None), [])
        self.assertEqual(p.mapLayersByName('test'), [l1])
        self.assertEqual(p.mapLayersByName('test2'), [l2])

        #duplicate name
        l3 = createLayer('test')
        p.addMapLayer(l3)
        self.assertEqual(set(p.mapLayersByName('test')), set([l1, l3]))

    def test_mapLayers(self):
        """ test retrieving map layers list """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        self.assertEqual(QgsProject.instance().mapLayers(), {})

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])

        self.assertEqual(QgsProject.instance().mapLayers(), {l1.id(): l1, l2.id(): l2})

    def test_removeMapLayersById(self):
        """ test removing map layers by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayers(['bad'])
        QgsProject.instance().removeMapLayers([None])

        l1 = createLayer('test')
        l2 = createLayer('test2')
        l3 = createLayer('test3')

        QgsProject.instance().addMapLayers([l1, l2, l3])
        self.assertEqual(QgsProject.instance().count(), 3)

        #remove bad layers
        QgsProject.instance().removeMapLayers(['bad'])
        self.assertEqual(QgsProject.instance().count(), 3)
        QgsProject.instance().removeMapLayers([None])
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove valid layers
        l1_id = l1.id()
        QgsProject.instance().removeMapLayers([l1_id])
        self.assertEqual(QgsProject.instance().count(), 2)
        # double remove
        QgsProject.instance().removeMapLayers([l1_id])
        self.assertEqual(QgsProject.instance().count(), 2)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove multiple
        QgsProject.instance().removeMapLayers([l2.id(), l3.id()])
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l4 = createLayer('test4')
        QgsProject.instance().removeMapLayers([l4.id()])
        self.assertFalse(sip.isdeleted(l4))

    # fails on qt5 due to removeMapLayers list type conversion - needs a PyName alias
    # added to removeMapLayers for QGIS 3.0
    @unittest.expectedFailure(QT_VERSION_STR[0] == '5')
    def test_removeMapLayersByLayer(self):
        """ test removing map layers by layer"""
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayers([None])

        l1 = createLayer('test')
        l2 = createLayer('test2')
        l3 = createLayer('test3')

        QgsProject.instance().addMapLayers([l1, l2, l3])
        self.assertEqual(QgsProject.instance().count(), 3)

        #remove bad layers
        QgsProject.instance().removeMapLayers([None])
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove valid layers
        QgsProject.instance().removeMapLayers([l1])
        self.assertEqual(QgsProject.instance().count(), 2)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove multiple
        QgsProject.instance().removeMapLayers([l2, l3])
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))
        self.assertTrue(sip.isdeleted(l3))

    def test_removeMapLayerById(self):
        """ test removing a map layer by ID """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayer('bad')
        QgsProject.instance().removeMapLayer(None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)

        #remove bad layers
        QgsProject.instance().removeMapLayer('bad')
        self.assertEqual(QgsProject.instance().count(), 2)
        QgsProject.instance().removeMapLayer(None)
        self.assertEqual(QgsProject.instance().count(), 2)

        # remove valid layers
        l1_id = l1.id()
        QgsProject.instance().removeMapLayer(l1_id)
        self.assertEqual(QgsProject.instance().count(), 1)
        # double remove
        QgsProject.instance().removeMapLayer(l1_id)
        self.assertEqual(QgsProject.instance().count(), 1)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove second layer
        QgsProject.instance().removeMapLayer(l2.id())
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3.id())
        self.assertFalse(sip.isdeleted(l3))

    def test_removeMapLayerByLayer(self):
        """ test removing a map layer by layer """
        QgsProject.instance().removeAllMapLayers()

        # test no crash with empty registry
        QgsProject.instance().removeMapLayer('bad')
        QgsProject.instance().removeMapLayer(None)

        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)

        #remove bad layers
        QgsProject.instance().removeMapLayer(None)
        self.assertEqual(QgsProject.instance().count(), 2)
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3)
        self.assertEqual(QgsProject.instance().count(), 2)

        # remove valid layers
        QgsProject.instance().removeMapLayer(l1)
        self.assertEqual(QgsProject.instance().count(), 1)

        # test that layer has been deleted
        self.assertTrue(sip.isdeleted(l1))

        # remove second layer
        QgsProject.instance().removeMapLayer(l2)
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertTrue(sip.isdeleted(l2))

        # try removing a layer not in the registry
        l3 = createLayer('test3')
        QgsProject.instance().removeMapLayer(l3)
        self.assertFalse(sip.isdeleted(l3))

    def test_removeAllMapLayers(self):
        """ test removing all map layers from registry """
        QgsProject.instance().removeAllMapLayers()
        l1 = createLayer('test')
        l2 = createLayer('test2')

        QgsProject.instance().addMapLayers([l1, l2])
        self.assertEqual(QgsProject.instance().count(), 2)
        QgsProject.instance().removeAllMapLayers()
        self.assertEqual(QgsProject.instance().count(), 0)
        self.assertEqual(QgsProject.instance().mapLayersByName('test'), [])
        self.assertEqual(QgsProject.instance().mapLayersByName('test2'), [])

    def test_addRemoveLayersSignals(self):
        """ test that signals are correctly emitted when removing map layers"""
        QgsProject.instance().removeAllMapLayers()

        layers_will_be_removed_spy = QSignalSpy(QgsProject.instance().layersWillBeRemoved)
        layer_will_be_removed_spy_str = QSignalSpy(QgsProject.instance().layerWillBeRemoved[str])
        layer_will_be_removed_spy_layer = QSignalSpy(QgsProject.instance().layerWillBeRemoved[QgsMapLayer])
        layers_removed_spy = QSignalSpy(QgsProject.instance().layersRemoved)
        layer_removed_spy = QSignalSpy(QgsProject.instance().layerRemoved)
        remove_all_spy = QSignalSpy(QgsProject.instance().removeAll)

        l1 = createLayer('l1')
        l2 = createLayer('l2')
        l3 = createLayer('l3')
        l4 = createLayer('l4')
        QgsProject.instance().addMapLayers([l1, l2, l3, l4])

        # remove 1 layer
        QgsProject.instance().removeMapLayer(l1)
        # can't seem to actually test the data which was emitted, so best we can do is test
        # the signal count
        self.assertEqual(len(layers_will_be_removed_spy), 1)
        self.assertEqual(len(layer_will_be_removed_spy_str), 1)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 1)
        self.assertEqual(len(layers_removed_spy), 1)
        self.assertEqual(len(layer_removed_spy), 1)
        self.assertEqual(len(remove_all_spy), 0)
        self.assertEqual(QgsProject.instance().count(), 3)

        # remove 2 layers at once
        QgsProject.instance().removeMapLayers([l2.id(), l3.id()])
        self.assertEqual(len(layers_will_be_removed_spy), 2)
        self.assertEqual(len(layer_will_be_removed_spy_str), 3)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 3)
        self.assertEqual(len(layers_removed_spy), 2)
        self.assertEqual(len(layer_removed_spy), 3)
        self.assertEqual(len(remove_all_spy), 0)
        self.assertEqual(QgsProject.instance().count(), 1)

        # remove all
        QgsProject.instance().removeAllMapLayers()
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

        #remove some layers which aren't in the registry
        QgsProject.instance().removeMapLayers(['asdasd'])
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

        l5 = createLayer('test5')
        QgsProject.instance().removeMapLayer(l5)
        self.assertEqual(len(layers_will_be_removed_spy), 3)
        self.assertEqual(len(layer_will_be_removed_spy_str), 4)
        self.assertEqual(len(layer_will_be_removed_spy_layer), 4)
        self.assertEqual(len(layers_removed_spy), 3)
        self.assertEqual(len(layer_removed_spy), 4)
        self.assertEqual(len(remove_all_spy), 1)

    def test_RemoveLayerShouldNotSegFault(self):
        QgsProject.instance().removeAllMapLayers()

        reg = QgsProject.instance()
        # Should not segfault
        reg.removeMapLayers(['not_exists'])
        reg.removeMapLayer('not_exists2')

        # check also that the removal of an unexistent layer does not insert a null layer
        for k, layer in list(reg.mapLayers().items()):
            assert(layer is not None)

    def testTakeLayer(self):
        # test taking ownership of a layer from the project
        l1 = createLayer('l1')
        l2 = createLayer('l2')
        p = QgsProject()

        # add one layer to project
        p.addMapLayer(l1)
        self.assertEqual(p.mapLayers(), {l1.id(): l1})
        self.assertEqual(l1.parent().parent(), p)

        # try taking some layers which don't exist in project
        self.assertFalse(p.takeMapLayer(None))
        self.assertFalse(p.takeMapLayer(l2))
        # but l2 should still exist..
        self.assertTrue(l2.isValid())

        # take layer from project
        self.assertEqual(p.takeMapLayer(l1), l1)
        self.assertFalse(p.mapLayers()) # no layers left
        # but l1 should still exist
        self.assertTrue(l1.isValid())
        # layer should have no parent now
        self.assertFalse(l1.parent())

        # destroy project
        p = None
        self.assertTrue(l1.isValid())

    def test_transactionsGroup(self):
        # Undefined transaction group (wrong provider key).
        QgsProject.instance().setAutoTransaction(True)
        noTg = QgsProject.instance().transactionGroup("provider-key", "database-connection-string")
        self.assertIsNone(noTg)

    def test_zip_new_project(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        # zip with existing file
        open(tmpFile, 'a').close()

        project = QgsProject()
        self.assertTrue(project.write(tmpFile))

        # zip with non existing file
        os.remove(tmpFile)

        project = QgsProject()
        self.assertTrue(project.write(tmpFile))
        self.assertTrue(os.path.isfile(tmpFile))

    def test_zip_invalid_path(self):
        project = QgsProject()
        self.assertFalse(project.write())
        self.assertFalse(project.write(""))
        self.assertFalse(project.write("/fake/test.zip"))

    def test_zip_filename(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        project = QgsProject()
        self.assertFalse(project.write())

        project.setFileName(tmpFile)
        self.assertTrue(project.write())
        self.assertTrue(os.path.isfile(tmpFile))

    def test_unzip_invalid_path(self):
        project = QgsProject()
        self.assertFalse(project.read())
        self.assertFalse(project.read(""))
        self.assertFalse(project.read("/fake/test.zip"))

    def test_zip_unzip(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgz".format(tmpDir.path())

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "lines.shp"), "lines", "ogr")
        project.addMapLayers([l0, l1])

        self.assertTrue(project.write(tmpFile))

        project2 = QgsProject()
        self.assertFalse(project2.isZipped())
        self.assertTrue(project2.fileName() == "")
        self.assertTrue(project2.read(tmpFile))
        self.assertTrue(project2.isZipped())
        self.assertTrue(project2.fileName() == tmpFile)
        layers = project2.mapLayers()

        self.assertEqual(len(layers.keys()), 2)
        self.assertTrue(layers[l0.id()].isValid(), True)
        self.assertTrue(layers[l1.id()].isValid(), True)

        project2.clear()
        self.assertFalse(project2.isZipped())

    def testUpgradeOtfFrom2x(self):
        """
        Test that upgrading a 2.x project correctly brings across project CRS and OTF transformation settings
        """
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'projects', 'test_memory_layer_proj.qgs'))
        self.assertTrue(prj.crs().isValid())
        self.assertEqual(prj.crs().authid(), 'EPSG:2056')

    def testRelativePaths(self):
        """
        Test whether paths to layer sources are stored as relative to the project path
        """
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())
        copyfile(os.path.join(TEST_DATA_DIR, "points.shp"), os.path.join(tmpDir.path(), "points.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.dbf"), os.path.join(tmpDir.path(), "points.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.shx"), os.path.join(tmpDir.path(), "points.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shp"), os.path.join(tmpDir.path(), "lines.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.dbf"), os.path.join(tmpDir.path(), "lines.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shx"), os.path.join(tmpDir.path(), "lines.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "landsat_4326.tif"), os.path.join(tmpDir.path(), "landsat_4326.tif"))

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(tmpDir.path(), "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(tmpDir.path(), "lines.shp"), "lines", "ogr")
        l2 = QgsRasterLayer(os.path.join(tmpDir.path(), "landsat_4326.tif"), "landsat", "gdal")
        self.assertTrue(l0.isValid())
        self.assertTrue(l1.isValid())
        self.assertTrue(l2.isValid())
        self.assertTrue(project.addMapLayers([l0, l1, l2]))
        self.assertTrue(project.write(tmpFile))
        del project

        with open(tmpFile, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="./lines.shp"' in content)
            self.assertTrue('source="./points.shp"' in content)
            self.assertTrue('source="./landsat_4326.tif"' in content)

        # Re-read the project and store absolute
        project = QgsProject()
        self.assertTrue(project.read(tmpFile))
        store = project.layerStore()
        self.assertEquals(set([l.name() for l in store.mapLayers().values()]), set(['lines', 'landsat', 'points']))
        project.writeEntryBool('Paths', '/Absolute', True)
        tmpFile2 = "{}/project2.qgs".format(tmpDir.path())
        self.assertTrue(project.write(tmpFile2))

        with open(tmpFile2, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="{}/lines.shp"'.format(tmpDir.path()) in content)
            self.assertTrue('source="{}/points.shp"'.format(tmpDir.path()) in content)
            self.assertTrue('source="{}/landsat_4326.tif"'.format(tmpDir.path()) in content)

        del project

    def testSymbolicLinkInProjectPath(self):
        """
        Test whether paths to layer sources relative to the project are stored correctly
        when project'name contains a symbolic link.
        In other words, test if project's and layers' names are correctly resolved.
        """
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())
        copyfile(os.path.join(TEST_DATA_DIR, "points.shp"), os.path.join(tmpDir.path(), "points.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.dbf"), os.path.join(tmpDir.path(), "points.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "points.shx"), os.path.join(tmpDir.path(), "points.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shp"), os.path.join(tmpDir.path(), "lines.shp"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.dbf"), os.path.join(tmpDir.path(), "lines.dbf"))
        copyfile(os.path.join(TEST_DATA_DIR, "lines.shx"), os.path.join(tmpDir.path(), "lines.shx"))
        copyfile(os.path.join(TEST_DATA_DIR, "landsat_4326.tif"), os.path.join(tmpDir.path(), "landsat_4326.tif"))

        project = QgsProject()

        l0 = QgsVectorLayer(os.path.join(tmpDir.path(), "points.shp"), "points", "ogr")
        l1 = QgsVectorLayer(os.path.join(tmpDir.path(), "lines.shp"), "lines", "ogr")
        l2 = QgsRasterLayer(os.path.join(tmpDir.path(), "landsat_4326.tif"), "landsat", "gdal")
        self.assertTrue(l0.isValid())
        self.assertTrue(l1.isValid())
        self.assertTrue(l2.isValid())
        self.assertTrue(project.addMapLayers([l0, l1, l2]))
        self.assertTrue(project.write(tmpFile))
        del project

        # Create symbolic link to previous project
        tmpDir2 = QTemporaryDir()
        symlinkDir = os.path.join(tmpDir2.path(), "dir")
        os.symlink(tmpDir.path(), symlinkDir)
        tmpFile = "{}/project.qgs".format(symlinkDir)

        # Open project from symmlink and force re-save.
        project = QgsProject()
        self.assertTrue(project.read(tmpFile))
        self.assertTrue(project.write(tmpFile))
        del project

        with open(tmpFile, 'r') as f:
            content = ''.join(f.readlines())
            self.assertTrue('source="./lines.shp"' in content)
            self.assertTrue('source="./points.shp"' in content)
            self.assertTrue('source="./landsat_4326.tif"' in content)

    def testHomePath(self):
        p = QgsProject()
        path_changed_spy = QSignalSpy(p.homePathChanged)
        self.assertFalse(p.homePath())
        self.assertFalse(p.presetHomePath())

        # simulate save file
        tmp_dir = QTemporaryDir()
        tmp_file = "{}/project.qgs".format(tmp_dir.path())
        with open(tmp_file, 'w') as f:
            pass
        p.setFileName(tmp_file)

        # home path should be file path
        self.assertEqual(p.homePath(), tmp_dir.path())
        self.assertFalse(p.presetHomePath())
        self.assertEqual(len(path_changed_spy), 1)

        # manually override home path
        p.setPresetHomePath('/tmp/my_path')
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)
        # check project scope
        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '/tmp/my_path')

        # no extra signal if path is unchanged
        p.setPresetHomePath('/tmp/my_path')
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)

        # setting file name should not affect home path is manually set
        tmp_file_2 = "{}/project/project2.qgs".format(tmp_dir.path())
        os.mkdir(tmp_dir.path() + '/project')
        with open(tmp_file_2, 'w') as f:
            pass
        p.setFileName(tmp_file_2)
        self.assertEqual(p.homePath(), '/tmp/my_path')
        self.assertEqual(p.presetHomePath(), '/tmp/my_path')
        self.assertEqual(len(path_changed_spy), 2)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '/tmp/my_path')

        # clear manual path
        p.setPresetHomePath('')
        self.assertEqual(p.homePath(), tmp_dir.path() + '/project')
        self.assertFalse(p.presetHomePath())
        self.assertEqual(len(path_changed_spy), 3)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), tmp_dir.path() + '/project')

        # relative path
        p.setPresetHomePath('../home')
        self.assertEqual(p.homePath(), tmp_dir.path() + '/home')
        self.assertEqual(p.presetHomePath(), '../home')
        self.assertEqual(len(path_changed_spy), 4)

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), tmp_dir.path() + '/home')

        # relative path, no filename
        p.setFileName('')
        self.assertEqual(p.homePath(), '../home')
        self.assertEqual(p.presetHomePath(), '../home')

        scope = QgsExpressionContextUtils.projectScope(p)
        self.assertEqual(scope.variable('project_home'), '../home')

    def testDirtyBlocker(self):
        # first test manual QgsProjectDirtyBlocker construction
        p = QgsProject()

        dirty_spy = QSignalSpy(p.isDirtyChanged)
        # ^ will do *whatever* it takes to discover the enemy's secret plans!

        # simple checks
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 1)
        self.assertEqual(dirty_spy[-1], [True])
        p.setDirty(True) # already dirty
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 1)
        p.setDirty(False)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 2)
        self.assertEqual(dirty_spy[-1], [False])
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 3)
        self.assertEqual(dirty_spy[-1], [True])

        # with a blocker
        blocker = QgsProjectDirtyBlocker(p)
        # blockers will allow cleaning projects
        p.setDirty(False)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        # but not dirtying!
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        # nested block
        blocker2 = QgsProjectDirtyBlocker(p)
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        del blocker2
        p.setDirty(True)
        self.assertFalse(p.isDirty())
        self.assertEqual(len(dirty_spy), 4)
        self.assertEqual(dirty_spy[-1], [False])
        del blocker
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 5)
        self.assertEqual(dirty_spy[-1], [True])

        # using python context manager
        with QgsProject.blockDirtying(p):
            # cleaning allowed
            p.setDirty(False)
            self.assertFalse(p.isDirty())
            self.assertEqual(len(dirty_spy), 6)
            self.assertEqual(dirty_spy[-1], [False])
            # but not dirtying!
            p.setDirty(True)
            self.assertFalse(p.isDirty())
            self.assertEqual(len(dirty_spy), 6)
            self.assertEqual(dirty_spy[-1], [False])

        # unblocked
        p.setDirty(True)
        self.assertTrue(p.isDirty())
        self.assertEqual(len(dirty_spy), 7)
        self.assertEqual(dirty_spy[-1], [True])

    def testCustomLayerOrderFrom2xProject(self):
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'layer_rendering_order_issue_qgis3.qgs'))

        layer_x = prj.mapLayers()['x20180406151213536']
        layer_y = prj.mapLayers()['y20180406151217017']

        # check layer order
        tree = prj.layerTreeRoot()
        self.assertEqual(tree.children()[0].layer(), layer_x)
        self.assertEqual(tree.children()[1].layer(), layer_y)
        self.assertTrue(tree.hasCustomLayerOrder())
        self.assertEqual(tree.customLayerOrder(), [layer_y, layer_x])
        self.assertEqual(tree.layerOrder(), [layer_y, layer_x])

    def testCustomLayerOrderFrom3xProject(self):
        prj = QgsProject.instance()
        prj.read(os.path.join(TEST_DATA_DIR, 'layer_rendering_order_qgis3_project.qgs'))

        layer_x = prj.mapLayers()['x20180406151213536']
        layer_y = prj.mapLayers()['y20180406151217017']

        # check layer order
        tree = prj.layerTreeRoot()
        self.assertEqual(tree.children()[0].layer(), layer_x)
        self.assertEqual(tree.children()[1].layer(), layer_y)
        self.assertTrue(tree.hasCustomLayerOrder())
        self.assertEqual(tree.customLayerOrder(), [layer_y, layer_x])
        self.assertEqual(tree.layerOrder(), [layer_y, layer_x])

    def testPalPropertiesReadWrite(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())

        s0 = QgsLabelingEngineSettings()
        s0.setNumCandidatePositions(3, 33, 333)

        p0 = QgsProject()
        p0.setFileName(tmpFile)
        p0.setLabelingEngineSettings(s0)
        p0.write()

        p1 = QgsProject()
        p1.read(tmpFile)

        s1 = p1.labelingEngineSettings()
        candidates = s1.numCandidatePositions()

        self.assertEqual(candidates[0], 3)
        self.assertEqual(candidates[1], 33)
        self.assertEqual(candidates[2], 333)

    def testLayerChangeDirtiesProject(self):
        """
        Test that making changes to certain layer properties results in dirty projects
        """
        p = QgsProject()
        l = QgsVectorLayer(os.path.join(TEST_DATA_DIR, "points.shp"), "points", "ogr")
        self.assertTrue(l.isValid())
        self.assertTrue(p.addMapLayers([l]))
        p.setDirty(False)

        l.setCrs(QgsCoordinateReferenceSystem('EPSG:3111'))
        self.assertTrue(p.isDirty())
        p.setDirty(False)

        l.setName('test')
        self.assertTrue(p.isDirty())
        p.setDirty(False)

        self.assertTrue(l.setSubsetString('class=\'a\''))
        self.assertTrue(p.isDirty())

    def testProjectTitleWithPeriod(self):
        tmpDir = QTemporaryDir()
        tmpFile = "{}/2.18.21.qgs".format(tmpDir.path())
        tmpFile2 = "{}/qgis-3.2.0.qgs".format(tmpDir.path())

        p0 = QgsProject()
        p0.setFileName(tmpFile)

        p1 = QgsProject()
        p1.setFileName(tmpFile2)

        self.assertEqual(p0.baseName(), '2.18.21')
        self.assertEqual(p1.baseName(), 'qgis-3.2.0')

    def testWriteEntry(self):

        tmpDir = QTemporaryDir()
        tmpFile = "{}/project.qgs".format(tmpDir.path())

        # zip with existing file
        project = QgsProject()
        query = 'select * from "sample DH" where "sample DH"."Elev" > 130 and "sample DH"."Elev" < 140'
        self.assertTrue(project.writeEntry('myscope', 'myentry', query))
        self.assertTrue(project.write(tmpFile))

        self.assertTrue(project.read(tmpFile))
        q, ok = project.readEntry('myscope', 'myentry')
        self.assertTrue(ok)
        self.assertEqual(q, query)

    def testDirtying(self):

        project = QgsProject()

        # writing a new entry should dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', True))
        self.assertTrue(project.isDirty())

        # over-writing a pre-existing entry with the same value should _not_ dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', True))
        self.assertFalse(project.isDirty())

        # over-writing a pre-existing entry with a different value should dirty the project
        project.setDirty(False)
        self.assertTrue(project.writeEntry('myscope', 'myentry', False))
        self.assertTrue(project.isDirty())

        # removing an existing entry should dirty the project
        project.setDirty(False)
        self.assertTrue(project.removeEntry('myscope', 'myentry'))
        self.assertTrue(project.isDirty())

        # removing a non-existing entry should _not_ dirty the project
        project.setDirty(False)
        self.assertTrue(project.removeEntry('myscope', 'myentry'))
        self.assertFalse(project.isDirty())

        # setting a project CRS with a new value should dirty the project
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
        project.setDirty(False)
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:3148'))
        self.assertTrue(project.isDirty())

        # setting a project CRS with the same project CRS should not dirty the project
        project.setDirty(False)
        project.setCrs(QgsCoordinateReferenceSystem('EPSG:3148'))
        self.assertFalse(project.isDirty())

    def testColorScheme(self):
        p = QgsProject.instance()
        spy = QSignalSpy(p.projectColorsChanged)
        p.setProjectColors([[QColor(255, 0, 0), 'red'], [QColor(0, 255, 0), 'green']])
        self.assertEqual(len(spy), 1)
        scheme = [s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme)][0]
        self.assertEqual([[c[0].name(), c[1]] for c in scheme.fetchColors()], [['#ff0000', 'red'], ['#00ff00', 'green']])
        # except color changed signal when clearing project
        p.clear()
        self.assertEqual(len(spy), 2)
        self.assertEqual([[c[0].name(), c[1]] for c in scheme.fetchColors()], [])

        # should be no signal on project destruction -- can cause a crash
        p = QgsProject()
        spy = QSignalSpy(p.projectColorsChanged)
        p.deleteLater()
        del p
        self.assertEqual(len(spy), 0)

    def testTransformContextSignalIsEmitted(self):
        """Test that when a project transform context changes a transformContextChanged signal is emitted"""

        p = QgsProject()
        spy = QSignalSpy(p.transformContextChanged)
        ctx = QgsCoordinateTransformContext()
        ctx.addSourceDestinationDatumTransform(QgsCoordinateReferenceSystem(4326), QgsCoordinateReferenceSystem(3857), 1234, 1235)
        p.setTransformContext(ctx)
        self.assertEqual(len(spy), 1)


if __name__ == '__main__':
    unittest.main()

# -*- 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. # This will get replaced with a git SHA1 when you do a git archive # NOQA Run once on class initialization. # see http://www.w3.org/TR/REC-xml/#d0e804 for a list of valid characters # all test tokens will be generated by prepending or inserting characters to this token # 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_() #\x01" # generate the characters that are allowed at the start of a token (and at every other position) # (0x10000, 0xEFFFF),   while actually valid, these are not yet accepted by makeKeyTokens_() # generate the characters that are only allowed inside a token, not at the start # test forbidden start characters # test forbidden inline characters # test each allowed start character # test each allowed inline character # if project has NO crs, then ellipsoid should always be none # valid key, valid int value # invalid key test retrieving global instance # register a layer to the singleton # check that the same instance is returned test adding individual map layers to registry # adding a second layer should leave existing layers intact test that already added layers can't be readded to registry test that invalid map layers can be added to registry test that signals are correctly emitted when adding map layer # can't seem to actually test the data which was emitted, so best we can do is test # the signal count # layer not added to legend # try readding a layer already in the registry # should be no extra signals emitted test adding multiple map layers to registry # adding more layers should leave existing layers intact test that invalid map layers can be added to registry test that already added layers can't be readded to registry test that signals are correctly emitted when adding map layers # can't seem to actually test the data which was emitted, so best we can do is test # the signal count # layer not added to legend # try readding a layer already in the registry # should be no extra signals emitted test retrieving map layer by ID # test no crash with empty registry test retrieving map layer by name # test no crash with empty registry #duplicate name test retrieving map layers list # test no crash with empty registry test removing map layers by ID # test no crash with empty registry #remove bad layers # remove valid layers # double remove # test that layer has been deleted # remove multiple # try removing a layer not in the registry # fails on qt5 due to removeMapLayers list type conversion - needs a PyName alias # added to removeMapLayers for QGIS 3.0 test removing map layers by layer # test no crash with empty registry #remove bad layers # remove valid layers # test that layer has been deleted # remove multiple test removing a map layer by ID # test no crash with empty registry #remove bad layers # remove valid layers # double remove # test that layer has been deleted # remove second layer # try removing a layer not in the registry test removing a map layer by layer # test no crash with empty registry #remove bad layers # remove valid layers # test that layer has been deleted # remove second layer # try removing a layer not in the registry test removing all map layers from registry test that signals are correctly emitted when removing map layers # remove 1 layer # can't seem to actually test the data which was emitted, so best we can do is test # the signal count # remove 2 layers at once # remove all #remove some layers which aren't in the registry # Should not segfault # check also that the removal of an unexistent layer does not insert a null layer # test taking ownership of a layer from the project # add one layer to project # try taking some layers which don't exist in project # but l2 should still exist.. # take layer from project # no layers left # but l1 should still exist # layer should have no parent now # destroy project # Undefined transaction group (wrong provider key). # zip with existing file # zip with non existing file Test that upgrading a 2.x project correctly brings across project CRS and OTF transformation settings Test whether paths to layer sources are stored as relative to the project path # Re-read the project and store absolute Test whether paths to layer sources relative to the project are stored correctly         when project'name contains a symbolic link.         In other words, test if project's and layers' names are correctly resolved. # Create symbolic link to previous project # Open project from symmlink and force re-save. # simulate save file # home path should be file path # manually override home path # check project scope # no extra signal if path is unchanged # setting file name should not affect home path is manually set # clear manual path # relative path # relative path, no filename # first test manual QgsProjectDirtyBlocker construction # ^ will do *whatever* it takes to discover the enemy's secret plans! # simple checks # already dirty # with a blocker # blockers will allow cleaning projects # but not dirtying! # nested block # using python context manager # cleaning allowed # but not dirtying! # unblocked # check layer order # check layer order Test that making changes to certain layer properties results in dirty projects # zip with existing file # writing a new entry should dirty the project # over-writing a pre-existing entry with the same value should _not_ dirty the project # over-writing a pre-existing entry with a different value should dirty the project # removing an existing entry should dirty the project # removing a non-existing entry should _not_ dirty the project # setting a project CRS with a new value should dirty the project # setting a project CRS with the same project CRS should not dirty the project # except color changed signal when clearing project # should be no signal on project destruction -- can cause a crash Test that when a project transform context changes a transformContextChanged signal is emitted