|
| 1 | +"""Unit tests for BlocksScreen.devices.amu.manager.""" |
| 2 | + |
| 3 | +import pytest |
| 4 | +from unittest.mock import patch |
| 5 | + |
| 6 | +from BlocksScreen.devices.amu.manager import AMUManager |
| 7 | +from BlocksScreen.devices.amu.models import MMUState |
| 8 | + |
| 9 | +_COMMENTED_CFG = """\ |
| 10 | +#[include mmu/base/*.cfg] |
| 11 | +#[include mmu/optional/client_macros.cfg] |
| 12 | +#[include filament_manager.cfg] |
| 13 | +""" |
| 14 | + |
| 15 | +_UNCOMMENTED_CFG = """\ |
| 16 | +[include mmu/base/*.cfg] |
| 17 | +[include mmu/optional/client_macros.cfg] |
| 18 | +[include filament_manager.cfg] |
| 19 | +""" |
| 20 | + |
| 21 | +_FULL_STATUS: dict = { |
| 22 | + "enabled": True, |
| 23 | + "is_homed": True, |
| 24 | + "num_gates": 2, |
| 25 | + "tool": 0, |
| 26 | + "gate": 0, |
| 27 | + "filament": "Loaded", |
| 28 | + "action": "Idle", |
| 29 | + "print_state": "printing", |
| 30 | + "reason_for_pause": "", |
| 31 | + "gate_status": [1, -1], |
| 32 | + "gate_material": ["PLA", ""], |
| 33 | + "gate_color": ["ff0000", ""], |
| 34 | + "gate_color_rgb": [(1.0, 0.0, 0.0), (0.0, 0.0, 0.0)], |
| 35 | + "gate_spool_id": [42, -1], |
| 36 | + "ttg_map": [0, 1], |
| 37 | +} |
| 38 | + |
| 39 | + |
| 40 | +@pytest.fixture |
| 41 | +def manager(tmp_path, qapp): |
| 42 | + """AMUManager with no config file (patched to a non-existent path).""" |
| 43 | + missing = tmp_path / "nonexistent.cfg" |
| 44 | + with patch("BlocksScreen.devices.amu.manager.CONFIG_PATH", missing): |
| 45 | + yield AMUManager() |
| 46 | + |
| 47 | + |
| 48 | +@pytest.fixture |
| 49 | +def manager_with_cfg(tmp_path, qapp): |
| 50 | + """AMUManager pointing at a temp printer.cfg with commented includes.""" |
| 51 | + cfg = tmp_path / "printer.cfg" |
| 52 | + cfg.write_text(_COMMENTED_CFG) |
| 53 | + with patch("BlocksScreen.devices.amu.manager.CONFIG_PATH", cfg): |
| 54 | + yield AMUManager(), cfg |
| 55 | + |
| 56 | + |
| 57 | +class TestAMUManagerInit: |
| 58 | + def test_initial_state_is_none(self, manager): |
| 59 | + assert manager.get_state() is None |
| 60 | + |
| 61 | + def test_initial_amu_inactive(self, manager): |
| 62 | + assert manager.is_amu_active() is False |
| 63 | + |
| 64 | + def test_no_config_file_sets_filename_none(self, manager): |
| 65 | + assert manager._config_filename is None |
| 66 | + |
| 67 | + |
| 68 | +class TestToggleAMUSystem: |
| 69 | + def test_no_config_emits_false(self, manager, qtbot): |
| 70 | + with qtbot.waitSignal(manager.amu_toggled) as blocker: |
| 71 | + manager.toggle_amu_system(True) |
| 72 | + assert blocker.args == [False] |
| 73 | + |
| 74 | + def test_same_state_emits_false(self, manager_with_cfg, qtbot): |
| 75 | + mgr, _ = manager_with_cfg |
| 76 | + # _amu_state starts False; toggling to False again → no change |
| 77 | + with qtbot.waitSignal(mgr.amu_toggled) as blocker: |
| 78 | + mgr.toggle_amu_system(False) |
| 79 | + assert blocker.args == [False] |
| 80 | + |
| 81 | + def test_activate_uncomments_includes(self, manager_with_cfg, qtbot): |
| 82 | + mgr, cfg = manager_with_cfg |
| 83 | + with qtbot.waitSignal(mgr.amu_toggled) as blocker: |
| 84 | + mgr.toggle_amu_system(True) |
| 85 | + assert blocker.args == [True] |
| 86 | + assert cfg.read_text() == _UNCOMMENTED_CFG |
| 87 | + |
| 88 | + def test_deactivate_comments_includes(self, manager_with_cfg, qtbot): |
| 89 | + mgr, cfg = manager_with_cfg |
| 90 | + cfg.write_text(_UNCOMMENTED_CFG) |
| 91 | + mgr._amu_state = True # align internal state with file |
| 92 | + with qtbot.waitSignal(mgr.amu_toggled) as blocker: |
| 93 | + mgr.toggle_amu_system(False) |
| 94 | + assert blocker.args == [True] |
| 95 | + assert cfg.read_text() == _COMMENTED_CFG |
| 96 | + |
| 97 | + def test_activate_sets_amu_active(self, manager_with_cfg, qtbot): |
| 98 | + mgr, _ = manager_with_cfg |
| 99 | + with qtbot.waitSignal(mgr.amu_toggled): |
| 100 | + mgr.toggle_amu_system(True) |
| 101 | + assert mgr.is_amu_active() is True |
| 102 | + |
| 103 | + |
| 104 | +class TestUpdateMMUState: |
| 105 | + def test_first_call_emits_state(self, manager, qtbot): |
| 106 | + with qtbot.waitSignal(manager.mmu_state_changed) as blocker: |
| 107 | + manager.update_mmu_state(_FULL_STATUS) |
| 108 | + assert isinstance(blocker.args[0], MMUState) |
| 109 | + |
| 110 | + def test_first_call_stores_state(self, manager): |
| 111 | + manager.update_mmu_state(_FULL_STATUS) |
| 112 | + state = manager.get_state() |
| 113 | + assert state is not None |
| 114 | + assert state.num_gates == 2 |
| 115 | + assert state.enabled is True |
| 116 | + |
| 117 | + def test_diff_updates_scalar(self, manager, qtbot): |
| 118 | + manager.update_mmu_state(_FULL_STATUS) |
| 119 | + with qtbot.waitSignal(manager.mmu_state_changed) as blocker: |
| 120 | + manager.update_mmu_state({"tool": 1, "filament": "Unloaded"}) |
| 121 | + state = blocker.args[0] |
| 122 | + assert state.tool == 1 |
| 123 | + assert state.filament == "Unloaded" |
| 124 | + |
| 125 | + def test_diff_preserves_unchanged_fields(self, manager): |
| 126 | + manager.update_mmu_state(_FULL_STATUS) |
| 127 | + manager.update_mmu_state({"tool": 1}) |
| 128 | + assert manager.get_state().num_gates == 2 |
| 129 | + assert manager.get_state().gates[0].material == "PLA" |
| 130 | + |
| 131 | + def test_each_call_emits_signal(self, manager, qtbot): |
| 132 | + manager.update_mmu_state(_FULL_STATUS) |
| 133 | + with qtbot.waitSignal(manager.mmu_state_changed): |
| 134 | + manager.update_mmu_state({"tool": 1}) |
| 135 | + |
| 136 | + |
| 137 | +class TestGcodeSignals: |
| 138 | + def test_set_gate_info(self, manager, qtbot): |
| 139 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 140 | + manager.set_gate_info(0, "PLA", "ff0000", 42) |
| 141 | + assert blocker.args == ["MMU_GATE_MAP gate=0 MATERIAL=PLA COLOR=ff0000 SPOOLID=42"] |
| 142 | + |
| 143 | + def test_set_gate_material(self, manager, qtbot): |
| 144 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 145 | + manager.set_gate_material(1, "PETG") |
| 146 | + assert blocker.args == ["MMU_GATE_MAP gate=1 MATERIAL=PETG"] |
| 147 | + |
| 148 | + def test_set_gate_color(self, manager, qtbot): |
| 149 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 150 | + manager.set_gate_color(2, "00ff00") |
| 151 | + assert blocker.args == ["MMU_GATE_MAP gate=2 COLOR=00ff00"] |
| 152 | + |
| 153 | + def test_set_gate_spool(self, manager, qtbot): |
| 154 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 155 | + manager.set_gate_spool(0, 7) |
| 156 | + assert blocker.args == ["MMU_GATE_MAP gate=0 SPOOLID=7"] |
| 157 | + |
| 158 | + def test_home_mmu(self, manager, qtbot): |
| 159 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 160 | + manager.home_mmu() |
| 161 | + assert blocker.args == ["MMU_HOME"] |
| 162 | + |
| 163 | + def test_reset_mmu(self, manager, qtbot): |
| 164 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 165 | + manager.reset_mmu() |
| 166 | + assert blocker.args == ["MMU_RESET"] |
| 167 | + |
| 168 | + def test_load_gate(self, manager, qtbot): |
| 169 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 170 | + manager.load_gate(3) |
| 171 | + assert blocker.args == ["MMU_SELECT gate=3\nMMU_LOAD"] |
| 172 | + |
| 173 | + def test_unload(self, manager, qtbot): |
| 174 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 175 | + manager.unload() |
| 176 | + assert blocker.args == ["MMU_UNLOAD"] |
| 177 | + |
| 178 | + def test_eject_gate(self, manager, qtbot): |
| 179 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 180 | + manager.eject_gate(1) |
| 181 | + assert blocker.args == ["MMU_EJECT GATE=1"] |
| 182 | + |
| 183 | + def test_eject_all_gates(self, manager, qtbot): |
| 184 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 185 | + manager.eject_all_gates(3) |
| 186 | + assert blocker.args == ["MMU_EJECT GATE=0\nMMU_EJECT GATE=1\nMMU_EJECT GATE=2"] |
| 187 | + |
| 188 | + def test_select_tool(self, manager, qtbot): |
| 189 | + with qtbot.waitSignal(manager.run_gcode_signal) as blocker: |
| 190 | + manager.select_tool(2) |
| 191 | + assert blocker.args == ["MMU_CHANGE_TOOL TOOL=2"] |
0 commit comments