Skip to content

Commit 8f43a21

Browse files
committed
ControlModeEngine(test[flags]): cover client flags, flow, subscriptions
why: Guard new control-mode helper API with deterministic unit coverage. what: - Expand set_client_flags cases to tmux !flag semantics and new flags - Validate pause-after non-negative and bad flow states - Add set_pane_flow and subscribe/unsubscribe argument construction tests
1 parent d49e030 commit 8f43a21

File tree

1 file changed

+154
-1
lines changed

1 file changed

+154
-1
lines changed

tests/test_control_mode_engine.py

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
398551
class ScriptedStdin:
399552
"""Fake stdin that can optionally raise BrokenPipeError on write."""
400553

0 commit comments

Comments
 (0)