chore: import upstream snapshot with attribution
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
"""Tests for Rich rendering helpers used by the interactive shell."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import re
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
from rich.console import Console
|
||||
|
||||
from surfaces.interactive_shell.runtime.core.state import SpinnerState
|
||||
from surfaces.interactive_shell.ui.components.rendering import (
|
||||
_repl_write_buffer,
|
||||
print_repl_json,
|
||||
refresh_welcome_poster,
|
||||
repl_print,
|
||||
repl_render_launch_poster,
|
||||
repl_table,
|
||||
)
|
||||
from surfaces.interactive_shell.ui.streaming.console import StreamingConsole
|
||||
from surfaces.interactive_shell.ui.tables import (
|
||||
render_integrations_table,
|
||||
render_mcp_table,
|
||||
)
|
||||
|
||||
|
||||
def test_repl_table_minimal_box() -> None:
|
||||
t = repl_table(title="T")
|
||||
assert t.title == "T"
|
||||
|
||||
|
||||
def test_print_repl_json_tty_uses_single_buffered_write(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
class _FakeStdout:
|
||||
def __init__(self) -> None:
|
||||
self.writes: list[str] = []
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
self.writes.append(text)
|
||||
return len(text)
|
||||
|
||||
def flush(self) -> None:
|
||||
return None
|
||||
|
||||
def isatty(self) -> bool:
|
||||
return True
|
||||
|
||||
fake_stdout = _FakeStdout()
|
||||
monkeypatch.setattr("sys.stdout", fake_stdout)
|
||||
|
||||
console = Console(file=fake_stdout, force_terminal=True, width=80)
|
||||
print_repl_json(console, '{"ok": true}')
|
||||
|
||||
assert len(fake_stdout.writes) == 1
|
||||
rendered = re.sub(r"\x1b\[[0-9;]*m", "", fake_stdout.writes[0])
|
||||
assert rendered.startswith("\r\n")
|
||||
assert '"ok": true' in rendered
|
||||
|
||||
|
||||
def test_render_integrations_table_empty_shows_hint() -> None:
|
||||
buf = io.StringIO()
|
||||
console = Console(file=buf, force_terminal=False)
|
||||
render_integrations_table(console, [])
|
||||
assert "opensre onboard" in buf.getvalue()
|
||||
|
||||
|
||||
def test_repl_print_resets_before_each_line(monkeypatch) -> None:
|
||||
resets: list[bool] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.choice_menu.prepare_repl_output_line",
|
||||
lambda: resets.append(True),
|
||||
)
|
||||
|
||||
buf = io.StringIO()
|
||||
console = Console(file=buf, force_terminal=False, width=80)
|
||||
repl_print(console, "line one")
|
||||
repl_print(console, "line two")
|
||||
|
||||
assert len(resets) == 2
|
||||
|
||||
|
||||
def test_repl_print_does_not_double_prepare_with_streaming_console(monkeypatch) -> None:
|
||||
resets: list[bool] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.choice_menu.prepare_repl_output_line",
|
||||
lambda: resets.append(True),
|
||||
)
|
||||
|
||||
console = StreamingConsole(
|
||||
SpinnerState(),
|
||||
threading.Event(),
|
||||
file=io.StringIO(),
|
||||
force_terminal=False,
|
||||
width=80,
|
||||
)
|
||||
repl_print(console, "line")
|
||||
|
||||
assert len(resets) == 1
|
||||
|
||||
|
||||
def test_repl_print_streaming_console_prepares_tty_once_when_interactive(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class _FakeStdout:
|
||||
def __init__(self) -> None:
|
||||
self.writes: list[str] = []
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
self.writes.append(text)
|
||||
return len(text)
|
||||
|
||||
def flush(self) -> None:
|
||||
return None
|
||||
|
||||
def isatty(self) -> bool:
|
||||
return True
|
||||
|
||||
fake_stdout = _FakeStdout()
|
||||
monkeypatch.setattr("sys.stdout", fake_stdout)
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.choice_menu.repl_tty_interactive",
|
||||
lambda: True,
|
||||
)
|
||||
|
||||
console = StreamingConsole(
|
||||
SpinnerState(),
|
||||
threading.Event(),
|
||||
file=io.StringIO(),
|
||||
force_terminal=False,
|
||||
width=80,
|
||||
)
|
||||
repl_print(console, "line")
|
||||
|
||||
assert fake_stdout.writes == ["\r\n", "\r"]
|
||||
|
||||
|
||||
def test_repl_render_launch_poster_uses_crlf_on_tty(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
class _FakeStdout:
|
||||
def __init__(self) -> None:
|
||||
self.writes: list[str] = []
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
self.writes.append(text)
|
||||
return len(text)
|
||||
|
||||
def flush(self) -> None:
|
||||
return None
|
||||
|
||||
def isatty(self) -> bool:
|
||||
return True
|
||||
|
||||
fake_stdout = _FakeStdout()
|
||||
monkeypatch.setattr("sys.stdout", fake_stdout)
|
||||
|
||||
from platform.terminal.theme import set_active_theme
|
||||
|
||||
set_active_theme("blue")
|
||||
console = Console(
|
||||
file=fake_stdout,
|
||||
force_terminal=True,
|
||||
highlight=False,
|
||||
color_system="truecolor",
|
||||
width=120,
|
||||
)
|
||||
repl_render_launch_poster(console, theme_notice="blue")
|
||||
|
||||
written = "".join(fake_stdout.writes)
|
||||
assert "theme set:" in written
|
||||
assert "blue" in written
|
||||
assert "38;2;168;212;255" in written
|
||||
assert "185;237;175" not in written
|
||||
assert "opensre" in written
|
||||
assert "Welcome back" in written
|
||||
assert "\r\n" in written
|
||||
# REPL path must not emit bare \\n (causes double-spaced splash under patch_stdout).
|
||||
assert "\r" not in written.replace("\r\n", "")
|
||||
|
||||
|
||||
def test_repl_write_buffer_strips_only_escaped_cpr_sequences(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class _FakeStdout:
|
||||
def __init__(self) -> None:
|
||||
self.writes: list[str] = []
|
||||
|
||||
def write(self, text: str) -> int:
|
||||
self.writes.append(text)
|
||||
return len(text)
|
||||
|
||||
def flush(self) -> None:
|
||||
return None
|
||||
|
||||
fake_stdout = _FakeStdout()
|
||||
monkeypatch.setattr("sys.stdout", fake_stdout)
|
||||
|
||||
_repl_write_buffer("\x1b[1;1Rtheme set: pink 12;5R\r\n")
|
||||
|
||||
written = "".join(fake_stdout.writes)
|
||||
assert "theme set: pink" in written
|
||||
assert "12;5R" in written
|
||||
assert "\x1b[1;1R" not in written
|
||||
|
||||
|
||||
def test_refresh_welcome_poster_drains_cpr_after_clear(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
drains: list[str] = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.rendering.repl_clear_screen",
|
||||
lambda: drains.append("clear"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.cpr_stdin.drain_stale_cpr_bytes",
|
||||
lambda: drains.append("drain"),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"surfaces.interactive_shell.ui.components.rendering.repl_render_launch_poster",
|
||||
lambda *_args, **_kwargs: drains.append("render"),
|
||||
)
|
||||
|
||||
console = Console(file=io.StringIO(), force_terminal=False)
|
||||
refresh_welcome_poster(console)
|
||||
|
||||
assert drains == ["clear", "drain", "render"]
|
||||
|
||||
|
||||
def test_render_integrations_table_renders_content(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
"""print_repl_table on a non-TTY console writes via console.print to stdout.
|
||||
|
||||
The cursor-reset (prepare_repl_output_line) is no longer called from the
|
||||
table rendering path; on a real TTY the blank line and \\r\\n normalisation
|
||||
are folded into a single sys.stdout.write call in print_repl_table.
|
||||
"""
|
||||
console = Console(force_terminal=False, width=80)
|
||||
render_integrations_table(
|
||||
console,
|
||||
[
|
||||
{
|
||||
"service": "grafana",
|
||||
"source": "local store",
|
||||
"status": "passed",
|
||||
"detail": "Connected to https://example.grafana.net",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert "grafana" in capsys.readouterr().out
|
||||
|
||||
|
||||
def test_render_integrations_table_sorts_services_and_includes_mcp(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
console = Console(force_terminal=False, width=80)
|
||||
render_integrations_table(
|
||||
console,
|
||||
[
|
||||
{"service": "sentry", "source": "-", "status": "missing", "detail": "missing"},
|
||||
{"service": "github", "source": "-", "status": "missing", "detail": "missing"},
|
||||
{"service": "datadog", "source": "env", "status": "passed", "detail": "ok"},
|
||||
],
|
||||
)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert output.index("datadog") < output.index("github") < output.index("sentry")
|
||||
assert "github" in output
|
||||
|
||||
|
||||
def test_render_mcp_table_renders_content(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
console = Console(force_terminal=False, width=80)
|
||||
render_mcp_table(
|
||||
console,
|
||||
[
|
||||
{
|
||||
"service": "github",
|
||||
"source": "local store",
|
||||
"status": "configured",
|
||||
"detail": "Connected",
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
assert "github" in capsys.readouterr().out
|
||||
Reference in New Issue
Block a user