@@ -354,8 +354,16 @@ def test_load_gate(self, manager, qtbot) -> None:
354354 assert manager .load_gate () is True
355355 assert blocker .args == ["MMU_LOAD" ]
356356
357+ # Happy-Hare drops MMU_LOAD when disabled or unhomed (mmu.py:6936).
357358 @pytest .mark .parametrize (
358- "overrides" , [None , {"filament_pos" : 10 }, {"filament_pos" : 0 , "gate" : - 1 }]
359+ "overrides" ,
360+ [
361+ None ,
362+ {"filament_pos" : 10 },
363+ {"filament_pos" : 0 , "gate" : - 1 },
364+ {"filament_pos" : 0 , "enabled" : False },
365+ {"filament_pos" : 0 , "is_homed" : False },
366+ ],
359367 )
360368 def test_load_gate_refused (self , manager , qtbot , overrides ) -> None :
361369 if overrides is not None :
@@ -364,10 +372,21 @@ def test_load_gate_refused(self, manager, qtbot, overrides) -> None:
364372 assert manager .load_gate () is False
365373
366374 def test_unload (self , manager , qtbot ) -> None :
375+ manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
367376 with qtbot .waitSignal (manager .run_gcode_signal ) as blocker :
368377 assert manager .unload () is True
369378 assert blocker .args == ["MMU_UNLOAD" ]
370379
380+ # Happy-Hare drops MMU_UNLOAD when disabled or already unloaded (mmu.py:6981).
381+ @pytest .mark .parametrize (
382+ "overrides" , [None , {"filament_pos" : 0 }, {"enabled" : False }]
383+ )
384+ def test_unload_refused (self , manager , qtbot , overrides ) -> None :
385+ if overrides is not None :
386+ manager .update_mmu_state (_status (** overrides ))
387+ with qtbot .assertNotEmitted (manager .run_gcode_signal ):
388+ assert manager .unload () is False
389+
371390 def test_eject_gate (self , manager , qtbot ) -> None :
372391 manager .update_mmu_state (_status (filament_pos = 0 ))
373392 with qtbot .waitSignal (manager .run_gcode_signal ) as blocker :
@@ -398,7 +417,13 @@ def test_check_gate_allows_an_empty_gate(self, manager, qtbot) -> None:
398417
399418 # MMU_CHECK_GATE runs a full unload first when filament is loaded.
400419 @pytest .mark .parametrize (
401- "overrides" , [None , {"filament_pos" : 10 }, {"filament_pos" : 0 , "gate" : - 2 }]
420+ "overrides" ,
421+ [
422+ None ,
423+ {"filament_pos" : 10 },
424+ {"filament_pos" : 0 , "gate" : - 2 },
425+ {"filament_pos" : 0 , "is_homed" : False },
426+ ],
402427 )
403428 def test_check_gate_refused (self , manager , qtbot , overrides ) -> None :
404429 if overrides is not None :
@@ -582,44 +607,6 @@ def test_false_when_mmu_disabled(self, manager_with_cfg, qtbot) -> None:
582607 assert mgr .is_amu_active () is False
583608
584609
585- class TestSpoolManFetch :
586- def test_noop_when_mmu_state_none (self , manager , qtbot ) -> None :
587- with qtbot .assertNotEmitted (manager .spool_fetched ):
588- manager .fetch_spool (0 , 42 )
589- manager ._ws .api .get_spool .assert_not_called ()
590-
591- def test_noop_when_spoolman_off (self , manager , qtbot ) -> None :
592- status = {** _FULL_STATUS_WITH_SPOOLMAN , "spoolman_support" : "off" }
593- manager .update_mmu_state (status )
594- with qtbot .assertNotEmitted (manager .spool_fetched ):
595- manager .fetch_spool (0 , 42 )
596- manager ._ws .api .get_spool .assert_not_called ()
597-
598- def test_emit_spool_fetched_on_sucess (self , manager , qtbot ) -> None :
599- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
600- spool_data = {"id" : 42 , "filament" : {"name" : "PLA" }, "used_weight" : 50.0 }
601- manager .fetch_spool (0 , 42 )
602- callback = manager ._ws .api .get_spool .call_args .args [1 ]
603- with qtbot .waitSignal (manager .spool_fetched ) as blocker :
604- callback (spool_data )
605- assert blocker .args [0 ] == 0
606- assert blocker .args [1 ] == spool_data
607-
608- def test_no_emit_on_ws_failure (self , manager , qtbot ) -> None :
609- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
610- manager .fetch_spool (0 , 42 )
611- callback = manager ._ws .api .get_spool .call_args .args [1 ]
612- with qtbot .assertNotEmitted (manager .spool_fetched ):
613- callback (None )
614-
615- # -1 is Happy-Hare's "no spool", not a Spoolman id.
616- def test_noop_when_spool_id_unset (self , manager , qtbot ) -> None :
617- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
618- with qtbot .assertNotEmitted (manager .spool_fetched ):
619- manager .fetch_spool (0 , - 1 )
620- manager ._ws .api .get_spool .assert_not_called ()
621-
622-
623610class TestUpdateSpoolWeight :
624611 # gates[-1] wraps, so an unselected gate hit the last gate's spool.
625612 @pytest .mark .parametrize ("gate" , [- 1 , - 2 , 2 , 99 ])
@@ -689,18 +676,7 @@ def test_load_cell_updates_are_ignored(self, manager, qtbot) -> None:
689676 assert not hasattr (manager , "gate_weight_updated" )
690677
691678
692- class TestApplySpoolData :
693- # Happy-Hare's _spoolman_sync owns the gate map; writing it here races it.
694- def test_emits_no_gcode (self , manager , qtbot ) -> None :
695- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
696- with qtbot .assertNotEmitted (manager .run_gcode_signal ):
697- manager ._apply_spool_data (0 , _SPOOL_DATA )
698-
699- # A spool callback can land after on_klippy_state() dropped the state.
700- def test_noop_when_mmu_state_none (self , manager ) -> None :
701- manager ._apply_spool_data (0 , _SPOOL_DATA )
702- assert manager .get_state () is None
703-
679+ class TestGcodeQuoting :
704680 # User-typed Spoolman text must survive Klipper's shlex parse as one value.
705681 @pytest .mark .parametrize (
706682 ("name" , "color" ),
@@ -726,51 +702,6 @@ def test_emitted_gcode_survives_klipper_parser(
726702 assert params ["COLOR" ] == color .lstrip ("#" )
727703 assert params ["SPOOLID" ] == "42"
728704
729- def test_triggered_by_spool_fetched_signal (self , manager , qtbot ) -> None :
730- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
731- manager .fetch_spool (0 , 42 )
732- callback = manager ._ws .api .get_spool .call_args .args [1 ]
733- with qtbot .waitSignal (manager .run_gcode_signal ) as blocker :
734- callback ({** _SPOOL_DATA , "remaining_weight" : 1500.0 })
735- assert blocker .args == ["MMU_GATE_MAP GATE=0 SPEED=33 TEMP=0 QUIET=1" ]
736-
737- def test_payload_without_id_is_dropped (self , manager , qtbot ) -> None :
738- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
739- with qtbot .assertNotEmitted (manager .run_gcode_signal ):
740- manager ._apply_spool_data (0 , {"remaining_weight" : 1500.0 })
741-
742- @staticmethod
743- def _speed_gcode (manager , remaining_weight , speed_override ) -> list [str ]:
744- manager .update_mmu_state (_status (gate_speed_override = [speed_override , 100 ]))
745- data = {k : v for k , v in _SPOOL_DATA .items () if k != "remaining_weight" }
746- if remaining_weight is not None :
747- data ["remaining_weight" ] = remaining_weight
748- signals = []
749- manager .run_gcode_signal .connect (signals .append )
750- manager ._apply_spool_data (0 , data )
751- return signals
752-
753- @pytest .mark .parametrize ("remaining_weight" , [1000.1 , 1500.0 , 5000.0 ])
754- def test_heavy_spool_slows_the_gear (self , manager , remaining_weight ) -> None :
755- assert self ._speed_gcode (manager , remaining_weight , 100 ) == [
756- "MMU_GATE_MAP GATE=0 SPEED=33 TEMP=0 QUIET=1"
757- ]
758-
759- @pytest .mark .parametrize ("remaining_weight" , [0.0 , 950.0 , 1000.0 ])
760- def test_light_spool_restores_full_speed (self , manager , remaining_weight ) -> None :
761- assert self ._speed_gcode (manager , remaining_weight , 33 ) == [
762- "MMU_GATE_MAP GATE=0 SPEED=100 TEMP=0 QUIET=1"
763- ]
764-
765- @pytest .mark .parametrize (
766- ("remaining_weight" , "speed_override" ),
767- [(None , 100 ), (None , 33 ), (950.0 , 100 ), (1500.0 , 33 )],
768- )
769- def test_speed_already_right_emits_nothing (
770- self , manager , remaining_weight , speed_override
771- ) -> None :
772- assert self ._speed_gcode (manager , remaining_weight , speed_override ) == []
773-
774705
775706class TestAssignSpool :
776707 @staticmethod
@@ -787,50 +718,11 @@ def test_maps_gate_from_payload(self, manager) -> None:
787718 "SPOOLID=42 TEMP=215 QUIET=1"
788719 ]
789720
790- # The picker already holds the payload, so no Spoolman round trip.
791- def test_applies_payload_without_refetch (self , manager ) -> None :
792- self ._gcode (manager , 1 , _SPOOL_DATA )
793- manager ._ws .api .get_spool .assert_not_called ()
794-
795- def test_heavy_spool_slows_the_gear (self , manager ) -> None :
796- gcode = self ._gcode (manager , 1 , {** _SPOOL_DATA , "remaining_weight" : 1500.0 })
797- assert gcode [1 :] == ["MMU_GATE_MAP GATE=1 SPEED=33 TEMP=0 QUIET=1" ]
798-
799721 # Spoolman sends null for an unset extruder temp.
800722 def test_null_temp_lets_happy_hare_default_it (self , manager ) -> None :
801723 filament = {** _SPOOL_DATA ["filament" ], "settings_extruder_temp" : None }
802724 (gcode ,) = self ._gcode (manager , 1 , {** _SPOOL_DATA , "filament" : filament })
803725 assert "TEMP" not in gcode
804726
805727 def test_unselected_gate_emits_nothing (self , manager ) -> None :
806- heavy = {** _SPOOL_DATA , "remaining_weight" : 1500.0 }
807- assert self ._gcode (manager , - 1 , heavy ) == []
808-
809-
810- class TestSetGateSpoolAutoFetch :
811- def test_calls_fetch_spool_when_activate (self , manager ) -> None :
812- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
813- manager .set_gate_spool (0 , 42 )
814- manager ._ws .api .get_spool .assert_called_once ()
815- assert manager ._ws .api .get_spool .call_args .args [0 ] == 42
816-
817- def test_skips_fetch_when_spool_id_minus_one (self , manager ) -> None :
818- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
819- manager .set_gate_spool (0 , - 1 )
820- manager ._ws .api .get_spool .assert_not_called ()
821-
822- def test_skips_fetch_when_no_state (self , manager ) -> None :
823- manager .set_gate_spool (0 , 42 )
824- manager ._ws .api .get_spool .assert_not_called ()
825-
826- def test_skips_fetch_when_spoolman_off (self , manager ) -> None :
827- status = {** _FULL_STATUS_WITH_SPOOLMAN , "spoolman_support" : "off" }
828- manager .update_mmu_state (status )
829- manager .set_gate_spool (0 , 42 )
830- manager ._ws .api .get_spool .assert_not_called ()
831-
832- # No MMU_GATE_MAP was sent, so caching a spool for it would lie.
833- def test_skips_fetch_when_gate_unselected (self , manager ) -> None :
834- manager .update_mmu_state (_FULL_STATUS_WITH_SPOOLMAN )
835- manager .set_gate_spool (- 1 , 42 )
836- manager ._ws .api .get_spool .assert_not_called ()
728+ assert self ._gcode (manager , - 1 , _SPOOL_DATA ) == []
0 commit comments