diff options
Diffstat (limited to 'packages/meshbay-cli/tests')
| -rw-r--r-- | packages/meshbay-cli/tests/test_cli_actions.py | 13 | ||||
| -rw-r--r-- | packages/meshbay-cli/tests/test_clipboard.py | 58 | ||||
| -rw-r--r-- | packages/meshbay-cli/tests/test_interactive.py | 79 |
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") |