"""TDD — ANSI color in muse read text output. RC-1 _format_op insert returns green-prefixed line when is_tty=True RC-2 _format_op delete returns red-prefixed line when is_tty=True RC-3 _format_op patch/replace returns yellow-prefixed line when is_tty=True RC-4 _format_op rename returns cyan-prefixed line when is_tty=True RC-5 Non-TTY integration: no ANSI codes anywhere in muse read output RC-6 Fallback path A/D/M lines carry color when is_tty=True via _color_op_line RC-7 Summary labels (Directories/Files/Symbols) are bold in tty mode """ from __future__ import annotations import datetime import json import pathlib from collections.abc import Mapping import pytest from tests.cli_test_helper import CliRunner from muse.core.paths import muse_dir, ref_path from muse.core.object_store import write_object from muse.core.ids import hash_commit, hash_snapshot from muse.core.commits import CommitRecord, write_commit from muse.core.snapshots import SnapshotRecord, write_snapshot from muse.core.types import blob_id runner = CliRunner() _GREEN = "\033[32m" _RED = "\033[31m" _YELLOW = "\033[33m" _CYAN = "\033[36m" _BOLD = "\033[1m" _RESET = "\033[0m" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _init_repo(path: pathlib.Path) -> pathlib.Path: dot = muse_dir(path) for d in ("commits", "snapshots", "objects", "refs/heads", "code"): (dot / d).mkdir(parents=True, exist_ok=True) (dot / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") (dot / "repo.json").write_text( json.dumps({"repo_id": "color-test", "domain": "code"}), encoding="utf-8", ) return path def _make_commit( root: pathlib.Path, files: Mapping[str, bytes], parent: str | None = None, directories: list[str] | None = None, ) -> str: manifest: dict[str, str] = {} for rel, content in files.items(): oid = blob_id(content) write_object(root, oid, content) manifest[rel] = oid snap_id = hash_snapshot(manifest, directories or []) write_snapshot(root, SnapshotRecord( snapshot_id=snap_id, manifest=manifest, directories=directories or [] )) committed_at = datetime.datetime.now(datetime.timezone.utc) commit_id = hash_commit( parent_ids=[parent] if parent else [], snapshot_id=snap_id, message="test", committed_at_iso=committed_at.isoformat(), ) write_commit(root, CommitRecord( commit_id=commit_id, branch="main", snapshot_id=snap_id, message="test", committed_at=committed_at, parent_commit_id=parent, )) ref_path(root, "main").write_text(commit_id, encoding="utf-8") return commit_id def _env(root: pathlib.Path) -> Mapping[str, str]: return {"MUSE_REPO_ROOT": str(root)} # --------------------------------------------------------------------------- # RC-1/2/3/4 _format_op color by op type # --------------------------------------------------------------------------- class TestFormatOpColor: def test_insert_is_green(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "insert", "address": "new.py", "content_id": "", "content_summary": ""}, is_tty=True) assert any(_GREEN in l for l in lines), f"insert must produce green line: {lines}" def test_delete_is_red(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "delete", "address": "old.py", "content_id": "", "content_summary": ""}, is_tty=True) assert any(_RED in l for l in lines), f"delete must produce red line: {lines}" def test_replace_is_yellow(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "replace", "address": "f.py", "old_content_id": "", "new_content_id": ""}, is_tty=True) assert any(_YELLOW in l for l in lines), f"replace must produce yellow line: {lines}" def test_rename_is_cyan(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "rename", "address": "new.py", "from_address": "old.py"}, is_tty=True) assert any(_CYAN in l for l in lines), f"rename must produce cyan line: {lines}" def test_patch_is_yellow(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "patch", "address": "f.py", "child_ops": [], "child_summary": ""}, is_tty=True) assert any(_YELLOW in l for l in lines), f"patch must produce yellow line: {lines}" def test_no_color_when_not_tty(self) -> None: from muse.cli.commands.read import _format_op lines = _format_op({"op": "insert", "address": "new.py", "content_id": "", "content_summary": ""}, is_tty=False) assert not any("\033[" in l for l in lines), f"no ANSI without tty: {lines}" # --------------------------------------------------------------------------- # RC-6 Fallback path color helper # --------------------------------------------------------------------------- class TestColorOpLine: def test_color_op_line_a_is_green(self) -> None: from muse.cli.commands.read import _color_op_line line = _color_op_line("A", "test.py", is_tty=True) assert _GREEN in line, f"A line must be green: {line!r}" def test_color_op_line_d_is_red(self) -> None: from muse.cli.commands.read import _color_op_line line = _color_op_line("D", "test.py", is_tty=True) assert _RED in line, f"D line must be red: {line!r}" def test_color_op_line_m_is_yellow(self) -> None: from muse.cli.commands.read import _color_op_line line = _color_op_line("M", "test.py", is_tty=True) assert _YELLOW in line, f"M line must be yellow: {line!r}" def test_color_op_line_no_color_non_tty(self) -> None: from muse.cli.commands.read import _color_op_line line = _color_op_line("A", "test.py", is_tty=False) assert "\033[" not in line, f"no ANSI without tty: {line!r}" # --------------------------------------------------------------------------- # RC-5 Non-TTY integration: no ANSI in muse read output # --------------------------------------------------------------------------- class TestNoColorIntegration: def test_no_ansi_in_plain_output(self, tmp_path: pathlib.Path) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n", "new.py": b"x=1\n"}, parent=c1) out = runner.invoke(None, ["read"], env=_env(root)).output assert "\033[" not in out, f"ANSI codes must not appear in non-TTY output:\n{repr(out)}" def test_no_ansi_in_plain_fallback_path(self, tmp_path: pathlib.Path) -> None: root = _init_repo(tmp_path) c1 = _make_commit(root, {"readme.md": b"# hi\n"}) _make_commit(root, {"readme.md": b"# hi\n"}, parent=c1, directories=["mydir"]) out = runner.invoke(None, ["read"], env=_env(root)).output assert "\033[" not in out, f"ANSI codes must not appear in non-TTY fallback output:\n{repr(out)}"