mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
Replace str.ljust-based columns with east_asian_width-aware padding so `hermes sessions list` and inline /history session picks line up when titles/previews mix Chinese and ASCII. Share one formatter via print_sessions_table in hermes_cli/main.py. test(cli): add coverage for display-width helpers in tests/hermes_cli/test_session_display_width.py.
133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
"""Tests for CJK-aware session list column padding (hermes_cli.main display helpers)."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from hermes_cli.main import (
|
|
_fit_display_width,
|
|
_pad_display_right,
|
|
_text_display_width,
|
|
print_sessions_table,
|
|
)
|
|
|
|
|
|
class TestTextDisplayWidth:
|
|
def test_empty(self) -> None:
|
|
assert _text_display_width("") == 0
|
|
|
|
def test_ascii(self) -> None:
|
|
assert _text_display_width("abc") == 3
|
|
|
|
def test_wide_cjk(self) -> None:
|
|
# Typical Han characters are wide (W) — two terminal cells each.
|
|
assert _text_display_width("中文") == 4
|
|
|
|
def test_mixed(self) -> None:
|
|
assert _text_display_width("a中") == 1 + 2
|
|
|
|
|
|
class TestFitDisplayWidth:
|
|
def test_no_truncation_when_fits(self) -> None:
|
|
assert _fit_display_width("hello", 10) == "hello"
|
|
|
|
def test_truncates_with_ellipsis(self) -> None:
|
|
out = _fit_display_width("abcdefghij", 5)
|
|
assert out.endswith("…")
|
|
assert _text_display_width(out) <= 5
|
|
|
|
def test_cjk_truncation_respects_cells(self) -> None:
|
|
s = "一二三四五六"
|
|
out = _fit_display_width(s, 5)
|
|
assert out.endswith("…")
|
|
assert _text_display_width(out) <= 5
|
|
|
|
def test_max_cells_zero_returns_empty(self) -> None:
|
|
assert _fit_display_width("hello", 0) == ""
|
|
|
|
|
|
class TestPadDisplayRight:
|
|
def test_pads_ascii_to_width(self) -> None:
|
|
assert _pad_display_right("hi", 8) == "hi "
|
|
assert _text_display_width(_pad_display_right("hi", 8)) == 8
|
|
|
|
def test_pads_mixed_to_display_width(self) -> None:
|
|
# "a" + two wide chars = 1 + 4 = 5 cells; pad to 8 → 3 spaces
|
|
got = _pad_display_right("a中文", 8)
|
|
assert _text_display_width(got) == 8
|
|
assert got.startswith("a中文")
|
|
|
|
def test_truncates_before_pad_when_too_long(self) -> None:
|
|
long = "x" * 80
|
|
got = _pad_display_right(long, 8)
|
|
assert _text_display_width(got) == 8
|
|
assert got.endswith("…")
|
|
|
|
|
|
class TestPrintSessionsTable:
|
|
"""Smoke + fixed-clock integration so Last Active is deterministic."""
|
|
|
|
def test_with_titles_prints_header_and_row(self, capsys) -> None:
|
|
fixed_now = 1_700_000_000.0
|
|
with patch("hermes_cli.main._time.time", return_value=fixed_now):
|
|
print_sessions_table(
|
|
[
|
|
{
|
|
"title": "Rust 项目",
|
|
"preview": "分析当前",
|
|
"last_active": fixed_now - 400,
|
|
"id": "20260415_testsession",
|
|
}
|
|
],
|
|
has_titles=True,
|
|
)
|
|
out = capsys.readouterr().out
|
|
assert "Title" in out and "Preview" in out and "Last Active" in out
|
|
assert "Rust 项目" in out
|
|
assert "20260415_testsession" in out
|
|
assert "6m ago" in out
|
|
lines = [ln for ln in out.splitlines() if ln.strip()]
|
|
assert len(lines) >= 3
|
|
|
|
def test_without_titles_includes_source(self, capsys) -> None:
|
|
fixed_now = 1_700_000_000.0
|
|
with patch("hermes_cli.main._time.time", return_value=fixed_now):
|
|
print_sessions_table(
|
|
[
|
|
{
|
|
"preview": "hello",
|
|
"last_active": fixed_now - 90,
|
|
"source": "cli",
|
|
"id": "id_only",
|
|
}
|
|
],
|
|
has_titles=False,
|
|
)
|
|
out = capsys.readouterr().out
|
|
assert "Preview" in out and "Src" in out
|
|
assert "cli" in out and "id_only" in out
|
|
|
|
def test_indent_prefix(self, capsys) -> None:
|
|
fixed_now = 1_700_000_000.0
|
|
with patch("hermes_cli.main._time.time", return_value=fixed_now):
|
|
print_sessions_table(
|
|
[{"title": "t", "preview": "p", "last_active": fixed_now, "id": "i"}],
|
|
has_titles=True,
|
|
indent=" ",
|
|
)
|
|
out = capsys.readouterr().out
|
|
for line in out.splitlines():
|
|
if line.strip():
|
|
assert line.startswith(" ")
|
|
|
|
def test_row_columns_have_expected_display_widths(self) -> None:
|
|
"""Guardrail: titled row layout matches fixed column budgets + spaces."""
|
|
title = _pad_display_right("ColdStore Rust 项目", 32)
|
|
preview = _pad_display_right("分析当前项目", 40)
|
|
last = _pad_display_right("6m ago", 13)
|
|
sid = "20260415_081027_6b979d"
|
|
line = f"{title} {preview} {last} {sid}"
|
|
assert _text_display_width(title) == 32
|
|
assert _text_display_width(preview) == 40
|
|
assert _text_display_width(last) == 13
|
|
# Single space between padded columns, then unbounded id.
|
|
assert line == f"{title} {preview} {last} {sid}"
|
|
|