Skip to content

Commit b8f920a

Browse files
test: add _resolve_version arg checks and _expand_flame unit tests
Verify exact subprocess.run arguments in _resolve_version tests to kill string and kwarg mutations. Add _expand_flame unit tests for gap distribution, style application, and boundary conditions. Add heat indicator wave character tests for _build_fire_art. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f4d3ee commit b8f920a

2 files changed

Lines changed: 185 additions & 2 deletions

File tree

tests/test_fireplace_visual.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
_FLAME_PALETTES,
99
_MIN_FLAME_ROWS,
1010
_build_fire_art,
11+
_expand_flame,
1112
_rgbw_to_style,
1213
)
1314

@@ -304,6 +305,92 @@ def test_all_palettes_defined(self):
304305
# ---------------------------------------------------------------------------
305306

306307

308+
# ---------------------------------------------------------------------------
309+
# _expand_flame – gap distribution
310+
# ---------------------------------------------------------------------------
311+
312+
313+
class TestExpandFlame:
314+
"""Tests for _expand_flame gap distribution."""
315+
316+
def test_basic_gap_distribution(self):
317+
"""Gaps are distributed proportionally among atoms."""
318+
atoms = [("A", 1), ("B", 1), ("C", 0)]
319+
result = _expand_flame(atoms, 13, "red")
320+
plain = result.plain
321+
# 3 chars of atoms, 10 chars of gap, split 5/5/0
322+
assert len(plain) == 13
323+
assert plain.startswith("A")
324+
assert plain.endswith("C")
325+
326+
def test_style_applied(self):
327+
"""Atoms get the specified style."""
328+
atoms = [("AB", 0)]
329+
result = _expand_flame(atoms, 2, "bright_red")
330+
styles = {str(s.style) for s in result._spans}
331+
assert "bright_red" in styles
332+
333+
def test_gap_weight_zero_no_space(self):
334+
"""Atom with weight=0 gets no trailing gap."""
335+
atoms = [("A", 1), ("B", 0)]
336+
result = _expand_flame(atoms, 6, "red")
337+
plain = result.plain
338+
# Expect "A" + 4 spaces + "B" (no trailing space)
339+
assert plain.endswith("B")
340+
assert " " * 4 in plain
341+
342+
def test_exact_width_no_gaps(self):
343+
"""When body_width equals atom chars, no gaps added."""
344+
atoms = [("AB", 1), ("CD", 0)]
345+
result = _expand_flame(atoms, 4, "red")
346+
assert result.plain == "ABCD"
347+
348+
def test_remaining_gap_decreases(self):
349+
"""Each atom consumes its proportional share of remaining gap."""
350+
atoms = [("X", 2), ("Y", 1), ("Z", 0)]
351+
result = _expand_flame(atoms, 12, "red")
352+
plain = result.plain
353+
assert len(plain) == 12
354+
# X gets 2/3 of 9 gap = 6, Y gets 1/1 of 3 = 3
355+
assert plain == "X" + " " * 6 + "Y" + " " * 3 + "Z"
356+
357+
def test_body_width_smaller_than_chars(self):
358+
"""When body_width < total atom chars, total_gap=0."""
359+
atoms = [("ABCD", 1), ("EF", 0)]
360+
result = _expand_flame(atoms, 3, "red")
361+
assert result.plain == "ABCDEF" # no gaps
362+
363+
def test_gap_spaces_not_other_chars(self):
364+
"""Gaps between atoms are regular spaces, not other characters."""
365+
atoms = [("A", 1), ("B", 0)]
366+
result = _expand_flame(atoms, 6, "red")
367+
plain = result.plain
368+
gap = plain[1:-1]
369+
assert gap == " " * len(gap)
370+
371+
372+
# ---------------------------------------------------------------------------
373+
# _build_fire_art – heat rows
374+
# ---------------------------------------------------------------------------
375+
376+
377+
class TestBuildFireArtHeat:
378+
"""Tests for heat indicator rows in _build_fire_art."""
379+
380+
def test_heat_on_shows_wave_chars(self):
381+
"""Heat indicators include wave characters when heat_on=True."""
382+
text = _build_fire_art(50, 20, heat_on=True)
383+
plain = text.plain
384+
assert "\u2248" in plain or "~" in plain
385+
386+
def test_heat_off_no_wave_chars(self):
387+
"""No wave characters when heat_on=False."""
388+
text = _build_fire_art(50, 20, heat_on=False)
389+
plain = text.plain
390+
assert "\u2248" not in plain
391+
assert "~" not in plain
392+
393+
307394
def _style_at(text, offset: int) -> str:
308395
"""Return the style string applied to the character at *offset*."""
309396
for span in text._spans:

tests/test_tui_actions.py

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,9 +1228,18 @@ def test_returns_version_when_tag_matches(self):
12281228
from flameconnect import __version__
12291229

12301230
tag_result = MagicMock(returncode=0, stdout=f"v{__version__}\n")
1231-
with patch("flameconnect.tui.app.subprocess.run", return_value=tag_result):
1231+
with patch(
1232+
"flameconnect.tui.app.subprocess.run", return_value=tag_result
1233+
) as mock_run:
12321234
result = _resolve_version()
12331235
assert result == f"v{__version__}"
1236+
# Verify exact subprocess call
1237+
mock_run.assert_called_once_with(
1238+
["git", "describe", "--tags", "--exact-match", "HEAD"],
1239+
capture_output=True,
1240+
text=True,
1241+
timeout=2,
1242+
)
12341243

12351244
def test_returns_short_hash_when_no_tag(self):
12361245
"""When no matching tag, return the short git hash."""
@@ -1241,9 +1250,30 @@ def test_returns_short_hash_when_no_tag(self):
12411250
with patch(
12421251
"flameconnect.tui.app.subprocess.run",
12431252
side_effect=[tag_result, hash_result, status_result],
1244-
):
1253+
) as mock_run:
12451254
result = _resolve_version()
12461255
assert result == "abc1234"
1256+
# Verify all three subprocess calls
1257+
assert mock_run.call_count == 3
1258+
calls = mock_run.call_args_list
1259+
assert calls[0].args[0] == [
1260+
"git",
1261+
"describe",
1262+
"--tags",
1263+
"--exact-match",
1264+
"HEAD",
1265+
]
1266+
assert calls[1].args[0] == [
1267+
"git",
1268+
"rev-parse",
1269+
"--short",
1270+
"HEAD",
1271+
]
1272+
assert calls[2].args[0] == ["git", "status", "--porcelain"]
1273+
for c in calls:
1274+
assert c.kwargs["capture_output"] is True
1275+
assert c.kwargs["text"] is True
1276+
assert c.kwargs["timeout"] == 2
12471277

12481278
def test_returns_dirty_hash_when_uncommitted(self):
12491279
"""When working tree is dirty, append -dirty to the hash."""
@@ -1258,6 +1288,33 @@ def test_returns_dirty_hash_when_uncommitted(self):
12581288
result = _resolve_version()
12591289
assert result == "abc1234-dirty"
12601290

1291+
def test_clean_status_no_dirty_suffix(self):
1292+
"""Clean status output should NOT append -dirty."""
1293+
tag_result = MagicMock(returncode=128, stdout="")
1294+
hash_result = MagicMock(returncode=0, stdout="abc1234\n")
1295+
status_result = MagicMock(returncode=0, stdout="")
1296+
1297+
with patch(
1298+
"flameconnect.tui.app.subprocess.run",
1299+
side_effect=[tag_result, hash_result, status_result],
1300+
):
1301+
result = _resolve_version()
1302+
assert result == "abc1234"
1303+
assert "-dirty" not in result
1304+
1305+
def test_status_nonzero_returncode_no_dirty(self):
1306+
"""Non-zero status returncode should not append -dirty."""
1307+
tag_result = MagicMock(returncode=128, stdout="")
1308+
hash_result = MagicMock(returncode=0, stdout="abc1234\n")
1309+
status_result = MagicMock(returncode=1, stdout="M some_file.py\n")
1310+
1311+
with patch(
1312+
"flameconnect.tui.app.subprocess.run",
1313+
side_effect=[tag_result, hash_result, status_result],
1314+
):
1315+
result = _resolve_version()
1316+
assert result == "abc1234"
1317+
12611318
def test_returns_version_when_hash_fails(self):
12621319
"""When git rev-parse fails, fall back to v{version}."""
12631320
from flameconnect import __version__
@@ -1283,6 +1340,45 @@ def test_returns_version_on_exception(self):
12831340
result = _resolve_version()
12841341
assert result == f"v{__version__}"
12851342

1343+
def test_tag_returncode_nonzero_falls_through(self):
1344+
"""Non-zero tag returncode means no tag match, proceed to hash."""
1345+
tag_result = MagicMock(returncode=1, stdout="")
1346+
hash_result = MagicMock(returncode=0, stdout="def5678\n")
1347+
status_result = MagicMock(returncode=0, stdout="")
1348+
1349+
with patch(
1350+
"flameconnect.tui.app.subprocess.run",
1351+
side_effect=[tag_result, hash_result, status_result],
1352+
):
1353+
result = _resolve_version()
1354+
assert result == "def5678"
1355+
1356+
def test_tag_matches_but_version_not_in_output(self):
1357+
"""Tag returncode=0 but version not in stdout falls through."""
1358+
tag_result = MagicMock(returncode=0, stdout="v99.99.99\n")
1359+
hash_result = MagicMock(returncode=0, stdout="aaa1111\n")
1360+
status_result = MagicMock(returncode=0, stdout="")
1361+
1362+
with patch(
1363+
"flameconnect.tui.app.subprocess.run",
1364+
side_effect=[tag_result, hash_result, status_result],
1365+
):
1366+
result = _resolve_version()
1367+
assert result == "aaa1111"
1368+
1369+
def test_stdout_stripped(self):
1370+
"""Trailing whitespace/newlines in stdout are stripped."""
1371+
tag_result = MagicMock(returncode=128, stdout="")
1372+
hash_result = MagicMock(returncode=0, stdout=" bbb2222 \n")
1373+
status_result = MagicMock(returncode=0, stdout="")
1374+
1375+
with patch(
1376+
"flameconnect.tui.app.subprocess.run",
1377+
side_effect=[tag_result, hash_result, status_result],
1378+
):
1379+
result = _resolve_version()
1380+
assert result == "bbb2222"
1381+
12861382

12871383
# ---------------------------------------------------------------------------
12881384
# FireplaceCommandsProvider

0 commit comments

Comments
 (0)