import unittest from unittest.mock import MagicMock import sys from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent sys.path.append(str(PROJECT_ROOT.parent)) class DummyBase: def _get_data_dict(self): if not hasattr(self, "_data") or self._data is None: self._data = {} return self._data def __getitem__(self, key): return self._get_data_dict()[key] def __setitem__(self, key, value): self._get_data_dict()[key] = value def __contains__(self, key): return key in self._get_data_dict() def get(self, key, default=None): return self._get_data_dict().get(key, default) class DummyObject: def __init__(self, name="TestObj", obj_type='MESH'): self.name = name self.type = obj_type mock_types = MagicMock() mock_types.NodeSocket = type("NodeSocket", (DummyBase,), {}) mock_types.Node = DummyBase mock_types.NodeCustomGroup = type("NodeCustomGroup", (DummyBase,), {}) mock_types.NodeTree = type("NodeTree", (DummyBase,), {}) mock_types.PropertyGroup = type("PropertyGroup", (DummyBase,), {}) mock_types.Operator = type("Operator", (DummyBase,), {}) mock_types.Panel = type("Panel", (DummyBase,), {}) mock_types.Menu = type("Menu", (DummyBase,), {}) mock_types.Object = DummyObject mock_types.Collection = type("Collection", (DummyBase,), {}) if 'bpy' not in sys.modules: mock_bpy = MagicMock() mock_bpy.types = mock_types sys.modules['bpy'] = mock_bpy sys.modules['bpy.types'] = mock_types sys.modules['bpy.props'] = MagicMock() sys.modules['bpy.utils'] = MagicMock() sys.modules['bpy.utils.previews'] = MagicMock() sys.modules['bpy.app'] = MagicMock() sys.modules['bpy.app.handlers'] = MagicMock() import bpy class TestNodeSocket: def __init__(self, name="", identifier="", node=None): self.name = name self.identifier = identifier or name self.is_linked = False self.links = [] self.node = node def __repr__(self): return f"" class TestSocketCollection(list): def __init__(self, node=None): super().__init__() self.node = node def new(self, type_name, name, identifier=""): sock = TestNodeSocket(name, identifier or name, node=self.node) self.append(sock) return sock def remove(self, sock): if sock in self: super().remove(sock) def get(self, name, default=None): for item in self: if item.name == name or item.identifier == name: return item return default def find(self, name): for i, item in enumerate(self): if item.name == name or item.identifier == name: return i return -1 def __contains__(self, item): if isinstance(item, str): return any(sock.name == item or sock.identifier == item for sock in self) return super().__contains__(item) def move(self, from_idx, to_idx): item = self.pop(from_idx) self.insert(to_idx, item) class DummyCollection: def __init__(self, name="TestCollection", objects=None): self.name = name self.objects = objects or [] from NodeSlicer.core.nodeslicer.nodes.inputs import SLICENODE_CollectionObjects class TestCollectionObjectsSorting(unittest.TestCase): def setUp(self): self.node = SLICENODE_CollectionObjects() self.node.inputs = TestSocketCollection(self.node) self.node.outputs = TestSocketCollection(self.node) def test_outputs_sorted_alphabetically(self): # Create collection with objects in non-alphabetical order obj_z = DummyObject("Zebra", 'MESH') obj_a = DummyObject("Apple", 'MESH') obj_m = DummyObject("Mango", 'MESH') obj_camera = DummyObject("Camera", 'CAMERA') # Should be ignored collection = DummyCollection("Fruits", [obj_z, obj_a, obj_camera, obj_m]) self.node.collection = collection self.node._update_outputs() output_names = [o.name for o in self.node.outputs] self.assertEqual(output_names, ["Apple", "Mango", "Zebra"]) def test_dynamic_add_and_reorder(self): # Initial collection with B and D obj_b = DummyObject("Banana", 'MESH') obj_d = DummyObject("Date", 'MESH') collection = DummyCollection("Fruits", [obj_b, obj_d]) self.node.collection = collection self.node._update_outputs() self.assertEqual([o.name for o in self.node.outputs], ["Banana", "Date"]) # Add Apple (which should be placed at the beginning) and Cherry (between Banana and Date) obj_a = DummyObject("Apple", 'MESH') obj_c = DummyObject("Cherry", 'MESH') collection.objects.extend([obj_a, obj_c]) self.node._update_outputs() self.assertEqual([o.name for o in self.node.outputs], ["Apple", "Banana", "Cherry", "Date"]) def test_remove_object(self): obj_a = DummyObject("Apple", 'MESH') obj_b = DummyObject("Banana", 'MESH') obj_c = DummyObject("Cherry", 'MESH') collection = DummyCollection("Fruits", [obj_a, obj_b, obj_c]) self.node.collection = collection self.node._update_outputs() self.assertEqual([o.name for o in self.node.outputs], ["Apple", "Banana", "Cherry"]) # Remove Banana collection.objects = [obj_a, obj_c] self.node._update_outputs() self.assertEqual([o.name for o in self.node.outputs], ["Apple", "Cherry"]) def test_evaluate_output(self): obj_a = DummyObject("Apple", 'MESH') bpy.data.objects = {"Apple": obj_a} from NodeSlicer.core.nodeslicer.nodes import inputs as inp_module inp_module.bpy.data.objects = {"Apple": obj_a} collection = DummyCollection("Fruits", [obj_a]) self.node.collection = collection self.node._update_outputs() result = self.node.evaluate_output("Apple") self.assertEqual(result, obj_a) result_nonexistent = self.node.evaluate_output("NonExistent") self.assertIsNone(result_nonexistent) if __name__ == '__main__': unittest.main()