@@ -344,7 +344,25 @@ class SetClientFlagsCase(t.NamedTuple):
344344 SetClientFlagsCase (
345345 test_id = "disable_no_output_clear_pause" ,
346346 kwargs = {"no_output" : False , "pause_after" : 0 },
347- expected_flags = {"no-output=off" , "pause-after=none" },
347+ expected_flags = {"!no-output" , "!pause-after" },
348+ expect_run = True ,
349+ ),
350+ SetClientFlagsCase (
351+ test_id = "wait_exit_and_read_only" ,
352+ kwargs = {"wait_exit" : True , "read_only" : True },
353+ expected_flags = {"wait-exit" , "read-only" },
354+ expect_run = True ,
355+ ),
356+ SetClientFlagsCase (
357+ test_id = "clear_wait_exit" ,
358+ kwargs = {"wait_exit" : False },
359+ expected_flags = {"!wait-exit" },
360+ expect_run = True ,
361+ ),
362+ SetClientFlagsCase (
363+ test_id = "toggle_misc_flags" ,
364+ kwargs = {"ignore_size" : True , "active_pane" : False },
365+ expected_flags = {"ignore-size" , "!active-pane" },
348366 expect_run = True ,
349367 ),
350368 SetClientFlagsCase (
@@ -395,6 +413,141 @@ def fake_run(
395413 assert server_args == ()
396414
397415
416+ def test_set_client_flags_rejects_negative_pause () -> None :
417+ """pause_after must be non-negative."""
418+ engine = ControlModeEngine (start_threads = False )
419+ with pytest .raises (ValueError ):
420+ engine .set_client_flags (pause_after = - 1 )
421+
422+
423+ class PaneFlowCase (t .NamedTuple ):
424+ """Fixture for refresh-client -A flow control."""
425+
426+ test_id : str
427+ pane_id : str | int
428+ state : str
429+ expected_arg : str
430+
431+
432+ @pytest .mark .parametrize (
433+ "case" ,
434+ [
435+ PaneFlowCase (
436+ test_id = "resume_default" ,
437+ pane_id = "%1" ,
438+ state = "continue" ,
439+ expected_arg = "%1:continue" ,
440+ ),
441+ PaneFlowCase (
442+ test_id = "pause_pane" ,
443+ pane_id = 3 ,
444+ state = "pause" ,
445+ expected_arg = "3:pause" ,
446+ ),
447+ ],
448+ ids = lambda c : c .test_id ,
449+ )
450+ def test_set_pane_flow_builds_refresh_client (case : PaneFlowCase ) -> None :
451+ """set_pane_flow should build refresh-client -A args."""
452+ engine = ControlModeEngine (start_threads = False )
453+ calls : list [tuple [str , tuple [str , ...], tuple [str , ...]]] = []
454+
455+ class DummyCmd :
456+ stdout : t .ClassVar [list [str ]] = []
457+ stderr : t .ClassVar [list [str ]] = []
458+ returncode : t .ClassVar [int ] = 0
459+
460+ def fake_run (
461+ cmd : str ,
462+ cmd_args : t .Sequence [str | int ] | None = None ,
463+ server_args : t .Sequence [str | int ] | None = None ,
464+ timeout : float | None = None ,
465+ ) -> DummyCmd :
466+ cmd_args_tuple = tuple (str (a ) for a in (cmd_args or ()))
467+ server_args_tuple = tuple (str (a ) for a in (server_args or ()))
468+ calls .append ((cmd , cmd_args_tuple , server_args_tuple ))
469+ return DummyCmd ()
470+
471+ engine .run = fake_run # type: ignore[assignment]
472+
473+ engine .set_pane_flow (case .pane_id , state = case .state )
474+
475+ assert calls
476+ cmd , cmd_args , server_args = calls [0 ]
477+ assert cmd == "refresh-client"
478+ assert cmd_args == ("-A" , case .expected_arg )
479+ assert server_args == ()
480+
481+
482+ def test_set_pane_flow_validates_state () -> None :
483+ """Invalid flow state should raise."""
484+ engine = ControlModeEngine (start_threads = False )
485+ with pytest .raises (ValueError ):
486+ engine .set_pane_flow ("%1" , state = "bad" )
487+
488+
489+ class SubscribeCase (t .NamedTuple ):
490+ """Fixture for refresh-client -B subscription arguments."""
491+
492+ test_id : str
493+ name : str
494+ what : str | None
495+ format : str | None
496+ expected_args : tuple [str , ...]
497+
498+
499+ @pytest .mark .parametrize (
500+ "case" ,
501+ [
502+ SubscribeCase (
503+ test_id = "add_subscription" ,
504+ name = "focus" ,
505+ what = "%1" ,
506+ format = "#{pane_active}" ,
507+ expected_args = ("-B" , "focus:%1:#{pane_active}" ),
508+ ),
509+ SubscribeCase (
510+ test_id = "remove_subscription" ,
511+ name = "focus" ,
512+ what = None ,
513+ format = None ,
514+ expected_args = ("-B" , "focus" ),
515+ ),
516+ ],
517+ ids = lambda c : c .test_id ,
518+ )
519+ def test_subscribe_builds_refresh_client (case : SubscribeCase ) -> None :
520+ """Subscribe should wrap refresh-client -B calls."""
521+ engine = ControlModeEngine (start_threads = False )
522+ calls : list [tuple [str , tuple [str , ...], tuple [str , ...]]] = []
523+
524+ class DummyCmd :
525+ stdout : t .ClassVar [list [str ]] = []
526+ stderr : t .ClassVar [list [str ]] = []
527+ returncode : t .ClassVar [int ] = 0
528+
529+ def fake_run (
530+ cmd : str ,
531+ cmd_args : t .Sequence [str | int ] | None = None ,
532+ server_args : t .Sequence [str | int ] | None = None ,
533+ timeout : float | None = None ,
534+ ) -> DummyCmd :
535+ cmd_args_tuple = tuple (str (a ) for a in (cmd_args or ()))
536+ server_args_tuple = tuple (str (a ) for a in (server_args or ()))
537+ calls .append ((cmd , cmd_args_tuple , server_args_tuple ))
538+ return DummyCmd ()
539+
540+ engine .run = fake_run # type: ignore[assignment]
541+
542+ engine .subscribe (case .name , what = case .what , fmt = case .format )
543+
544+ assert calls
545+ cmd , cmd_args , server_args = calls [0 ]
546+ assert cmd == "refresh-client"
547+ assert cmd_args == case .expected_args
548+ assert server_args == ()
549+
550+
398551class ScriptedStdin :
399552 """Fake stdin that can optionally raise BrokenPipeError on write."""
400553
0 commit comments