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) 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 = type("Object", (DummyBase,), {}) mock_types.Mesh = type("Mesh", (DummyBase,), {}) 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() # Socket and node testing mocks class TestNodeSocket: def __init__(self, name="", identifier="", node=None): self.name = name self.identifier = identifier or name self.is_linked = False self.links = [] self.default = None self.default_value = "" self.value = "" self.hide = False self.display_shape = "" self.node = node 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 from NodeSlicer.infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input from NodeSlicer.core.nodeslicer.nodes.gcode import SLICENODE_JoinGcode class TestMultiGroupsManualValue(unittest.TestCase): def test_join_gcode_multi_groups_manual_value(self): node = SLICENODE_JoinGcode() node.inputs = TestSocketCollection(node) node.outputs = TestSocketCollection(node) node.add_output("NodeSocketString", "Gcode") node.add_multi_socket_group("NodeSocketString", name="Gcode") # Initial state should have 1 input socket self.assertEqual(len(node.inputs), 1) # Entering a manual value in socket 0 node.inputs[0].value = "G1 X10 Y10 F1200" node._update_multi_groups() # Should trigger adding socket 1 self.assertEqual(len(node.inputs), 2) # Entering a manual value in socket 1 node.inputs[1].value = "G1 X20 Y20" node._update_multi_groups() # Should trigger adding socket 2 self.assertEqual(len(node.inputs), 3) # Evaluate output should join manual values joined = node.evaluate_output("Gcode") self.assertEqual(joined, "G1 X10 Y10 F1200\nG1 X20 Y20") # Clearing socket 1 manual value node.inputs[1].value = "" node._update_multi_groups() # Trailing inactive sockets cleaned up self.assertEqual(len(node.inputs), 2) def test_join_gcode_default_value_trigger(self): node = SLICENODE_JoinGcode() node.inputs = TestSocketCollection(node) node.outputs = TestSocketCollection(node) node.add_output("NodeSocketString", "Gcode") node.add_multi_socket_group("NodeSocketString", name="Gcode") # Set default_value on native socket node.inputs[0].default_value = "M104 S200" node._update_multi_groups() self.assertEqual(len(node.inputs), 2) self.assertEqual(node.evaluate_output("Gcode"), "M104 S200") if __name__ == "__main__": unittest.main()