nodeslicer/tests/test_collection_objects.py
2026-09-25 13:21:10 +03:00

178 lines
6 KiB
Python

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"<TestNodeSocket name={self.name!r} identifier={self.identifier!r}>"
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}
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()