aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-cli/tests')
-rw-r--r--packages/meshbay-cli/tests/test_cli_actions.py13
-rw-r--r--packages/meshbay-cli/tests/test_clipboard.py58
-rw-r--r--packages/meshbay-cli/tests/test_interactive.py79
3 files changed, 150 insertions, 0 deletions
diff --git a/packages/meshbay-cli/tests/test_cli_actions.py b/packages/meshbay-cli/tests/test_cli_actions.py
index 5443d9b..240ac67 100644
--- a/packages/meshbay-cli/tests/test_cli_actions.py
+++ b/packages/meshbay-cli/tests/test_cli_actions.py
@@ -203,6 +203,19 @@ def test_a_usage_error_is_exit_1_not_the_sign_in_code(capsys):
+def test_help_prints_what_help_prints(capsys):
+ assert cli.main(["--help"]) == 0
+ whole = capsys.readouterr().out
+ assert main("help") == 0
+ assert capsys.readouterr().out == whole
+ assert cli.main(["chat", "--help"]) == 0
+ chat = capsys.readouterr().out
+ assert main("help", "chat") == 0
+ assert capsys.readouterr().out == chat
+ assert main("help", "no-such-action") == 1
+ assert "No action 'no-such-action'" in capsys.readouterr().err
+
+
def test_groups_lists_each_group_with_its_state(hub, capsys):
hub.groups.append({"id": "g2", "name": "Club/Photos", "owner_username": "bob-test",
"node_online": False, "visibility": "public", "is_admin": False,
diff --git a/packages/meshbay-cli/tests/test_clipboard.py b/packages/meshbay-cli/tests/test_clipboard.py
new file mode 100644
index 0000000..6296317
--- /dev/null
+++ b/packages/meshbay-cli/tests/test_clipboard.py
@@ -0,0 +1,58 @@
+"""
+The chat's clipboard: the platform's tools, run for real, and nothing when none
+works. Fake `wl-copy`/`wl-paste` stand in for wl-clipboard, sharing one file.
+"""
+
+import shutil
+import sys
+import time
+
+import pytest
+from meshbay_cli.tui import clipboard
+
+pytestmark = pytest.mark.skipif(sys.platform in ("win32", "darwin"),
+ reason="the Wayland and X11 tools")
+# Found before a test leaves only the fake tools on PATH.
+CAT, SLEEP = shutil.which("cat"), shutil.which("sleep")
+
+
+@pytest.fixture
+def wayland(tmp_path, monkeypatch):
+ held = tmp_path / "held"
+ bin_dir = tmp_path / "bin"
+ bin_dir.mkdir()
+ for name, body in (("wl-copy", f'{CAT} > "{held}"'), ("wl-paste", f'{CAT} "{held}"')):
+ tool = bin_dir / name
+ tool.write_text(f"#!/bin/sh\n{body}\n")
+ tool.chmod(0o755)
+ monkeypatch.setenv("PATH", str(bin_dir))
+ monkeypatch.setenv("WAYLAND_DISPLAY", "wayland-0")
+ monkeypatch.delenv("DISPLAY", raising=False)
+ return held
+
+
+async def test_what_is_copied_is_what_is_pasted(wayland):
+ assert await clipboard.write_text("héllo — 你好\nsecond") is True
+ assert wayland.read_text(encoding="utf-8") == "héllo — 你好\nsecond"
+ assert await clipboard.read_text() == "héllo — 你好\nsecond"
+
+
+async def test_no_tool_is_none_not_an_error(tmp_path, monkeypatch):
+ monkeypatch.setenv("PATH", str(tmp_path))
+ monkeypatch.setenv("WAYLAND_DISPLAY", "wayland-0")
+ monkeypatch.setenv("DISPLAY", ":0")
+ assert await clipboard.read_text() is None
+ assert await clipboard.write_text("x") is False
+
+
+async def test_a_failing_tool_is_skipped(wayland):
+ (wayland.parent / "bin" / "wl-paste").write_text("#!/bin/sh\nexit 1\n")
+ assert await clipboard.read_text() is None
+
+
+async def test_a_hanging_tool_is_given_up(wayland, monkeypatch):
+ (wayland.parent / "bin" / "wl-paste").write_text(f"#!/bin/sh\nexec {SLEEP} 30\n")
+ monkeypatch.setattr(clipboard, "TIMEOUT", 0.3)
+ started = time.monotonic()
+ assert await clipboard.read_text() is None
+ assert 0.3 <= time.monotonic() - started < 5
diff --git a/packages/meshbay-cli/tests/test_interactive.py b/packages/meshbay-cli/tests/test_interactive.py
index fa7e96c..5b9d9b0 100644
--- a/packages/meshbay-cli/tests/test_interactive.py
+++ b/packages/meshbay-cli/tests/test_interactive.py
@@ -208,6 +208,17 @@ async def test_delete_removes_what_is_yours_and_asks_first(world):
assert any("select them on the right" in n.message for n in app._notifications)
+async def _right_click(pilot, widget, offset=(0, 0)) -> None:
+ """Through App.on_event, as the terminal driver sends it; the pilot's own
+ clicks skip it."""
+ from textual import events
+ from textual.pilot import _get_mouse_message_arguments
+ args = _get_mouse_message_arguments(widget, offset, button=3)
+ for cls in (events.MouseDown, events.MouseUp):
+ pilot.app.post_message(cls(**args))
+ await pilot.pause()
+
+
def _log_text(screen) -> str:
from textual.widgets import RichLog
return "\n".join("".join(seg.text for seg in strip)
@@ -285,3 +296,71 @@ async def test_the_interactive_chat_joins_talks_leaves_and_comes_back(world, mon
await _until(pilot, lambda: isinstance(app.screen, ChatGroupsScreen))
await pilot.press("q")
assert app.return_code in (0, None)
+
+
+async def test_a_right_click_copies_from_the_chat_and_pastes_into_the_line(world, monkeypatch):
+ """The selection, else the line under the pointer; pasted as one line."""
+ from meshbay_cli.tui import clipboard
+ from meshbay_cli.tui.chat import ChatGroupsScreen, ChatInput, ChatLog, ChatRoomScreen
+ hub, node, _ = world
+ written, held = [], {"text": None}
+
+ async def write_text(text):
+ written.append(text)
+ return True
+
+ async def read_text():
+ return held["text"]
+ monkeypatch.setattr(clipboard, "write_text", write_text)
+ monkeypatch.setattr(clipboard, "read_text", read_text)
+ node.say("earlier, from bob")
+ assert await asyncio.to_thread(cli.main, ["--hub", "https://hub.test", "-u", USERNAME,
+ "--password", PASSPHRASE, "login"]) == 0
+
+ app = MeshBayApp(context.Context(cli.build_parser().parse_args(
+ ["--hub", "https://hub.test"])), ChatGroupsScreen)
+ async with app.run_test(size=(120, 30)) as pilot:
+ await _until(pilot, lambda: isinstance(app.screen, ChatGroupsScreen))
+ await pilot.pause(0.3)
+ await pilot.press("enter")
+ await _until(pilot, lambda: isinstance(app.screen, ChatRoomScreen))
+ room = app.screen
+ log = room.query_one(ChatLog)
+ await _until(pilot, lambda: "<bob-test> earlier, from bob" in _log_text(room))
+ y = next(i for i, line in enumerate(log.lines) if "earlier, from bob" in line.text)
+ line = log.lines[y].text
+
+ # Nothing selected: the line under the pointer.
+ await _right_click(pilot, log, (4, y))
+ await _until(pilot, lambda: written)
+ assert written[-1] == line.rstrip() and app.clipboard == written[-1]
+ assert "<bob-test> earlier, from bob" in written[-1]
+
+ # Selected with the mouse: that text, and only it.
+ x = line.index("bob-test")
+ await pilot.mouse_down(log, offset=(1 + x, y))
+ await pilot.hover(log, offset=(x + len("bob-test"), y))
+ await pilot.mouse_up(log, offset=(x + len("bob-test"), y))
+ await pilot.pause(0.1)
+ assert log.text_selection is not None
+ await _right_click(pilot, log, (1, y + 3))
+ await _until(pilot, lambda: len(written) == 2)
+ assert written[-1] == "bob-test"
+
+ # Pasted into the line written in, at the cursor, lines side by side.
+ field = room.query_one(ChatInput)
+ field.value = "say: "
+ field.cursor_position = len(field.value)
+ held["text"] = "first line\nsecond line\n"
+ await _right_click(pilot, field, (2, 0))
+ await _until(pilot, lambda: field.value != "say: ")
+ assert field.value == "say: first line second line"
+
+ # No tool to read the system clipboard: what was copied here.
+ held["text"] = None
+ field.value = ""
+ await _right_click(pilot, field, (2, 0))
+ await _until(pilot, lambda: field.value)
+ assert field.value == "bob-test"
+ assert app.screen is room # a right-click left nothing behind
+ await pilot.press("escape")