def test_replaceRegion_found_last(self):
        """Test the replaceRegion method when the region matches the last in the list."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        regionToMatch = RectangularRegion(x1=10,
                                          y1=10,
                                          x2=20,
                                          y2=20,
                                          id="matchId")
        otherRegion = RectangularRegion(x1=20,
                                        y1=20,
                                        x2=30,
                                        y2=30,
                                        id="otherId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="matchId")

        unit.addRegion(otherRegion)
        unit.addRegion(regionToMatch)

        unit.replaceRegion(newRegion)

        self.assertEqual(
            unit.excludedRegions, [otherRegion, newRegion],
            "The excluded regions should be updated by replaceRegion if the ID is found (last)"
        )
    def test_deleteRegion_found_middle(self):
        """Test the deleteRegion method when the region is neither first nor last in the list."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        firstRegion = RectangularRegion(x1=10,
                                        y1=10,
                                        x2=20,
                                        y2=20,
                                        id="firstId")
        findRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="findId")
        lastRegion = RectangularRegion(x1=20, y1=20, x2=30, y2=30, id="lastId")

        unit.addRegion(firstRegion)
        unit.addRegion(findRegion)
        unit.addRegion(lastRegion)

        self.assertTrue(
            unit.deleteRegion("findId"),
            "deleteRegion should return True when the region is found and removed (middle)"
        )
        self.assertEqual(
            unit.excludedRegions, [firstRegion, lastRegion],
            "The excluded regions should be updated by deleteRegion if the ID is found (middle)"
        )
    def test_isAnyPointExcluded_unmatchedPairs(self):
        """Test the isAnyPointExcluded method when an odd number of arguments are provided."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)

        with self.assertRaises(IndexError):
            unit.isAnyPointExcluded(0)
    def test_isAnyPointExcluded_middleExcluded(self):
        """Test the isAnyPointExcluded method a point other than the first or last is excluded."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)

        self.assertTrue(unit.isAnyPointExcluded(10, 10, 0, 0, 20, 20),
                        "(0,0) should be excluded (middle)")
    def test_isAnyPointExcluded_lastExcluded(self):
        """Test the isAnyPointExcluded method when only the last point is excluded."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)

        self.assertTrue(unit.isAnyPointExcluded(10, 10, 0, 0),
                        "(0,0) should be excluded (last)")
    def test_isAnyPointExcluded_noArguments(self):
        """Test the isAnyPointExcluded method when no arguments are provided."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)

        self.assertFalse(
            unit.isAnyPointExcluded(),
            "isAnyPointExcluded should return false when passed no arguments")
    def test_isAnyPointExcluded_oneRegion(self):
        """Test the isAnyPointExcluded method when one region is defined."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)

        self.assertTrue(unit.isAnyPointExcluded(0, 0),
                        "(0,0) should be excluded (one region)")
        self.assertFalse(unit.isAnyPointExcluded(10, 10),
                         "(10,10) should NOT be excluded (one region)")
    def test_isAnyPointExcluded_exclusionDisabled(self):
        """Test the isAnyPointExcluded method when exclusion is disabled."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        unit.addRegion(aRegion)
        unit.disableExclusion("Disable for test")

        self.assertFalse(unit.isAnyPointExcluded(0, 0),
                         "(0,0) should NOT be excluded (exclusion disabled)")
        self.assertFalse(
            unit.isAnyPointExcluded(10, 10),
            "(10,10) should NOT be excluded (exclusion disabled)")
    def test_deleteRegion_notFound(self):
        """Test the deleteRegion method when the specified region is not found."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        aRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="anId")
        unit.addRegion(aRegion)

        self.assertFalse(
            unit.deleteRegion("notFound"),
            "deleteRegion should return False when the region is not found")
        self.assertEqual(
            unit.excludedRegions, [aRegion],
            "The excluded regions should not be modified by deleteRegion if the ID was not found"
        )
    def test_replaceRegion_notFound(self):
        """Test the replaceRegion method when the region is not found."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        existingRegion = RectangularRegion(x1=10,
                                           y1=10,
                                           x2=20,
                                           y2=20,
                                           id="otherId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="someId")

        unit.addRegion(existingRegion)

        with self.assertRaises(ValueError):
            unit.replaceRegion(newRegion)
    def test_deleteRegion_found_single(self):
        """Test the deleteRegion method when the specified region is the only one defined."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        findRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="findId")
        unit.addRegion(findRegion)

        self.assertTrue(
            unit.deleteRegion("findId"),
            "deleteRegion should return True when the region is found and removed (single)"
        )
        self.assertEqual(
            unit.excludedRegions, [],
            "The excluded regions should be updated by deleteRegion if the ID is found (single)"
        )
    def test_addRegion_idExists(self):
        """Test the addRegion method when the ID already exists."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        aRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="anId")
        conflictingRegion = RectangularRegion(x1=10,
                                              y1=10,
                                              x2=20,
                                              y2=20,
                                              id="anId")

        unit.addRegion(aRegion)

        with self.assertRaises(ValueError):
            unit.addRegion(conflictingRegion)
    def test_isAnyPointExcluded_unitMultiplier(self):
        """Test the isAnyPointExcluded method honors the unit multipler in effect."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)
        unit.position.setUnitMultiplier(INCH_TO_MM_FACTOR)
        aRegion = RectangularRegion(x1=INCH_TO_MM_FACTOR,
                                    y1=INCH_TO_MM_FACTOR,
                                    x2=2 * INCH_TO_MM_FACTOR,
                                    y2=2 * INCH_TO_MM_FACTOR)
        unit.addRegion(aRegion)

        self.assertTrue(unit.isAnyPointExcluded(1, 1),
                        "(1 inch, 1 inch) should be excluded")
        self.assertTrue(unit.isAnyPointExcluded(2, 2),
                        "(2 inch, 2 inch) should be excluded")
        self.assertFalse(unit.isAnyPointExcluded(2.1, 2),
                         "(2.1 inch, 2 inch) should NOT be excluded")
    def test_replaceRegion_found_single(self):
        """Test the replaceRegion method when the region matches the only one defined."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        regionToMatch = RectangularRegion(x1=10,
                                          y1=10,
                                          x2=20,
                                          y2=20,
                                          id="matchId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="matchId")

        unit.addRegion(regionToMatch)

        unit.replaceRegion(newRegion)

        self.assertEqual(
            unit.excludedRegions, [newRegion],
            "The excluded regions should be updated by replaceRegion if the ID is found (single)"
        )
    def test_replaceRegion_found_mustContain_contained(self):
        """Test the replaceRegion method when mustContainOldRegion is True and a match is found."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        regionToMatch = RectangularRegion(x1=10,
                                          y1=10,
                                          x2=20,
                                          y2=20,
                                          id="matchId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="matchId")

        unit.addRegion(regionToMatch)

        unit.replaceRegion(newRegion, True)

        self.assertEqual(
            unit.excludedRegions, [newRegion],
            "The excluded regions should be updated by replaceRegion if the ID is found (contained)"
        )
    def test_isPointExcluded_multipleRegions(self):
        """Test the isPointExcluded method when multiple regions are defined."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        aRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5)
        anotherRegion = CircularRegion(cx=20, cy=20, r=10)

        unit.addRegion(aRegion)
        unit.addRegion(anotherRegion)

        self.assertTrue(unit.isPointExcluded(0, 0),
                        "(0,0) should be excluded (mult regions)")
        self.assertFalse(unit.isPointExcluded(10, 10),
                         "(10,10) should NOT be excluded (mult regions)")

        self.assertTrue(unit.isPointExcluded(20, 20),
                        "(20,20) should be excluded (mult regions)")
        self.assertFalse(unit.isPointExcluded(30, 10),
                         "(30,10) should NOT be excluded (mult regions)")
    def test_replaceRegion_found_mustContain_notContained(self):
        """Test the replaceRegion method when mustContainOldRegion is True and no match is found."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        regionToMatch = RectangularRegion(x1=10,
                                          y1=10,
                                          x2=20,
                                          y2=20,
                                          id="matchId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=5, y2=5, id="matchId")

        unit.addRegion(regionToMatch)

        with self.assertRaises(ValueError):
            unit.replaceRegion(newRegion, True)

        self.assertEqual(
            unit.excludedRegions, [regionToMatch],
            "The excluded regions should not be modified by replaceRegion (match, not contained)"
        )
    def test_replaceRegion_found_middle(self):
        """Test the replaceRegion method when the matched region not first nor last in the list."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        firstRegion = RectangularRegion(x1=20,
                                        y1=20,
                                        x2=30,
                                        y2=30,
                                        id="firstId")
        regionToMatch = RectangularRegion(x1=10,
                                          y1=10,
                                          x2=20,
                                          y2=20,
                                          id="matchId")
        lastRegion = RectangularRegion(x1=30, y1=30, x2=40, y2=40, id="lastId")
        newRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="matchId")

        unit.addRegion(firstRegion)
        unit.addRegion(regionToMatch)
        unit.addRegion(lastRegion)

        unit.replaceRegion(newRegion)

        self.assertEqual(
            unit.excludedRegions, [firstRegion, newRegion, lastRegion],
            "The excluded regions should be updated by replaceRegion if the ID is found (last)"
        )
    def test_deleteRegion_found_last(self):
        """Test the deleteRegion method when the specified region is last in the list."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        findRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="findId")
        otherRegion = RectangularRegion(x1=10,
                                        y1=10,
                                        x2=20,
                                        y2=20,
                                        id="otherId")

        unit.addRegion(otherRegion)
        unit.addRegion(findRegion)

        self.assertTrue(
            unit.deleteRegion("findId"),
            "deleteRegion should return True when the region is found and removed (last)"
        )
        self.assertEqual(
            unit.excludedRegions, [otherRegion],
            "The excluded regions should be updated by deleteRegion if the ID is found (last)"
        )
    def test_addRegion_newId(self):
        """Test the addRegion method when the ID has not yet been added."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        aRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="anId")
        otherRegion = RectangularRegion(x1=10,
                                        y1=10,
                                        x2=20,
                                        y2=20,
                                        id="otherId")

        unit.addRegion(aRegion)

        self.assertEqual(
            unit.excludedRegions, [aRegion],
            "The list of excluded regions should contain the new region")

        unit.addRegion(otherRegion)

        self.assertEqual(
            unit.excludedRegions, [aRegion, otherRegion],
            "The list of excluded regions should contain both regions")
    def test_getRegion_exists(self):
        """Test the getRegion method when a matching region has been defined."""
        mockLogger = mock.Mock()
        unit = ExcludeRegionState(mockLogger)

        aRegion = RectangularRegion(x1=0, y1=0, x2=100, y2=100, id="anId")
        otherRegion = RectangularRegion(x1=10,
                                        y1=10,
                                        x2=20,
                                        y2=20,
                                        id="otherId")

        unit.addRegion(aRegion)
        unit.addRegion(otherRegion)

        self.assertIs(
            unit.getRegion("anId"), aRegion,
            "getRegion return the region matching the id when such a region is defined"
        )

        self.assertIs(
            unit.getRegion("otherId"), otherRegion,
            "getRegion return the region matching the id when such a region is defined"
        )