|
1 | 1 | """Tests for feature-flagged Tiny Actor Grid preview (#1271).""" |
| 2 | +import json |
2 | 3 | import os |
3 | 4 | import sys |
4 | 5 |
|
|
11 | 12 | sys.path.insert(0, _lib_dir) |
12 | 13 |
|
13 | 14 | from buddy_renderer import display_width |
14 | | -from tiny_actor_preview import is_tiny_actors_enabled, render_actor_preview |
| 15 | +from tiny_actor_preview import ( |
| 16 | + is_tiny_actors_enabled, |
| 17 | + render_actor_preview, |
| 18 | + _load_agent_eye, |
| 19 | + _AGENTS_DIR, |
| 20 | +) |
15 | 21 |
|
16 | 22 | # --------------------------------------------------------------------------- |
17 | 23 | # is_tiny_actors_enabled |
@@ -80,8 +86,9 @@ def test_unknown_mode_returns_none(self): |
80 | 86 | def test_preview_contains_agent_faces(self): |
81 | 87 | result = render_actor_preview("PLAN") |
82 | 88 | assert result is not None |
83 | | - # Should contain face-like patterns (eye+mouth+eye) |
84 | | - assert "\u25cf" in result or "o" in result # default eye glyphs |
| 89 | + # Should contain real agent eye glyphs loaded from JSON |
| 90 | + # security-specialist has visual.eye = "◮" |
| 91 | + assert "\u25ee" in result or "\u25cf" in result # real or default eye glyphs |
85 | 92 |
|
86 | 93 | def test_preview_contains_moderator(self): |
87 | 94 | result = render_actor_preview("PLAN") |
@@ -133,3 +140,104 @@ def _boom(mode): |
133 | 140 | monkeypatch.setattr(tiny_actor_preview, "get_cast_preset", _boom) |
134 | 141 | result = render_actor_preview("PLAN") |
135 | 142 | assert result is None |
| 143 | + |
| 144 | + |
| 145 | +# --------------------------------------------------------------------------- |
| 146 | +# _load_agent_eye — agent visual.eye loading |
| 147 | +# --------------------------------------------------------------------------- |
| 148 | + |
| 149 | + |
| 150 | +class TestLoadAgentEye: |
| 151 | + """Loading visual.eye glyphs from agent JSON files (#1301).""" |
| 152 | + |
| 153 | + def test_known_agent_returns_eye_glyph(self): |
| 154 | + """A known agent like security-specialist returns its visual.eye.""" |
| 155 | + eye = _load_agent_eye("security-specialist") |
| 156 | + assert eye is not None |
| 157 | + # Verify it matches the actual JSON file |
| 158 | + agent_file = _AGENTS_DIR / "security-specialist.json" |
| 159 | + data = json.loads(agent_file.read_text(encoding="utf-8")) |
| 160 | + assert eye == data["visual"]["eye"] |
| 161 | + |
| 162 | + def test_unknown_agent_returns_none(self): |
| 163 | + """An agent ID with no matching JSON falls back to None.""" |
| 164 | + eye = _load_agent_eye("nonexistent-agent-xyz") |
| 165 | + assert eye is None |
| 166 | + |
| 167 | + def test_malformed_json_returns_none(self, tmp_path, monkeypatch): |
| 168 | + """A malformed agent JSON doesn't break — returns None.""" |
| 169 | + import tiny_actor_preview |
| 170 | + |
| 171 | + bad_dir = tmp_path / "agents" |
| 172 | + bad_dir.mkdir() |
| 173 | + (bad_dir / "broken-agent.json").write_text("{invalid json", encoding="utf-8") |
| 174 | + monkeypatch.setattr(tiny_actor_preview, "_AGENTS_DIR", bad_dir) |
| 175 | + eye = _load_agent_eye("broken-agent") |
| 176 | + assert eye is None |
| 177 | + |
| 178 | + def test_missing_visual_block_returns_none(self, tmp_path, monkeypatch): |
| 179 | + """An agent JSON without visual block returns None.""" |
| 180 | + import tiny_actor_preview |
| 181 | + |
| 182 | + no_visual_dir = tmp_path / "agents" |
| 183 | + no_visual_dir.mkdir() |
| 184 | + (no_visual_dir / "no-visual.json").write_text( |
| 185 | + json.dumps({"name": "Test Agent"}), encoding="utf-8" |
| 186 | + ) |
| 187 | + monkeypatch.setattr(tiny_actor_preview, "_AGENTS_DIR", no_visual_dir) |
| 188 | + eye = _load_agent_eye("no-visual") |
| 189 | + assert eye is None |
| 190 | + |
| 191 | + def test_empty_eye_string_returns_none(self, tmp_path, monkeypatch): |
| 192 | + """An agent with empty visual.eye string returns None.""" |
| 193 | + import tiny_actor_preview |
| 194 | + |
| 195 | + empty_dir = tmp_path / "agents" |
| 196 | + empty_dir.mkdir() |
| 197 | + (empty_dir / "empty-eye.json").write_text( |
| 198 | + json.dumps({"visual": {"eye": ""}}), encoding="utf-8" |
| 199 | + ) |
| 200 | + monkeypatch.setattr(tiny_actor_preview, "_AGENTS_DIR", empty_dir) |
| 201 | + eye = _load_agent_eye("empty-eye") |
| 202 | + assert eye is None |
| 203 | + |
| 204 | + |
| 205 | +# --------------------------------------------------------------------------- |
| 206 | +# render_actor_preview — real eye glyphs (#1301) |
| 207 | +# --------------------------------------------------------------------------- |
| 208 | + |
| 209 | + |
| 210 | +class TestRenderActorPreviewEyeGlyphs: |
| 211 | + """Rendered preview uses real agent eye glyphs from JSON.""" |
| 212 | + |
| 213 | + @pytest.fixture(autouse=True) |
| 214 | + def _enable_flag(self, monkeypatch): |
| 215 | + monkeypatch.setenv("CODINGBUDDY_TINY_ACTORS", "1") |
| 216 | + |
| 217 | + def test_buddy_still_uses_moderator_face(self): |
| 218 | + """Buddy moderator card keeps its hardcoded ◕ eye, not loaded from JSON.""" |
| 219 | + result = render_actor_preview("PLAN") |
| 220 | + assert result is not None |
| 221 | + assert "\u25d5\u203f\u25d5" in result # ◕‿◕ |
| 222 | + |
| 223 | + def test_specialist_uses_real_eye_glyph(self): |
| 224 | + """Security specialist card uses ◮ from its visual.eye, not default ●.""" |
| 225 | + result = render_actor_preview("PLAN") |
| 226 | + assert result is not None |
| 227 | + # security-specialist visual.eye = "◮" |
| 228 | + sec_eye = _load_agent_eye("security-specialist") |
| 229 | + assert sec_eye is not None |
| 230 | + assert sec_eye in result |
| 231 | + |
| 232 | + def test_all_modes_load_real_glyphs(self): |
| 233 | + """All preset modes produce output with non-default eye glyphs.""" |
| 234 | + from tiny_actor_card import DEFAULT_EYE |
| 235 | + |
| 236 | + for mode in ("PLAN", "EVAL", "AUTO", "SHIP"): |
| 237 | + result = render_actor_preview(mode) |
| 238 | + assert result is not None, f"Mode {mode} should render" |
| 239 | + # At least one real eye glyph should appear (not just default ●) |
| 240 | + # Buddy uses ◕ which is not DEFAULT_EYE, so that alone satisfies this |
| 241 | + # but primary/specialist agents should also have their own glyphs |
| 242 | + lines = result.split("\n") |
| 243 | + assert len(lines) > 0 |
0 commit comments