Skip to content

Commit e030df6

Browse files
committed
tests: increase coverage from 72% to 82%
- Add tests/test_replay_scanner.py (replay_scanner.py 0% → 95%): parse_events, scan_to_flat, bucket_events, apply_events (all event types + edge cases), _render_replay_frame_worker (with log_scale) - Add tests/test_git_scanner.py (git_scanner.py 64% → 94%): git_log blank lines and invalid timestamps, git_initial_files edge cases (non-blob, excluded, bad size, missing tab), _blob_sizes, git_apply_diff for all status types (A/M/D/R/C) and excluded paths, build_node_tree depth limiting and size accumulation, _render_frame_worker, build_tree_from_git - Extend tests/test_watch_animate.py (watch.py 67% → 96%): SVG output path, log=True rendering, exception handling in _regenerate, _record_event with bytes paths, debounce=0 immediate regeneration, flush thread join, all four event handlers (on_created/deleted/modified/moved) including directory-ignored case - Extend tests/test_cli.py: replay command (basic, missing log, bad extension, --total-duration), _proportional_durations unit tests
1 parent 5cfde69 commit e030df6

4 files changed

Lines changed: 1007 additions & 0 deletions

File tree

tests/test_cli.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,147 @@ def test_read_meta_mp4(tmp_path: Path) -> None:
504504
assert result.exit_code == 0
505505
for key in ("Date", "Software", "URL", "Python", "OS", "Command"):
506506
assert f"{key}:" in result.output
507+
508+
509+
# ---------------------------------------------------------------------------
510+
# _proportional_durations
511+
# ---------------------------------------------------------------------------
512+
513+
514+
def test_proportional_durations_basic() -> None:
515+
from dirplot.main import _proportional_durations
516+
517+
durations = _proportional_durations([1.0, 2.0, 3.0], total_ms=6000)
518+
assert sum(durations) == 6000
519+
assert len(durations) == 3
520+
assert all(d >= 200 for d in durations)
521+
522+
523+
def test_proportional_durations_floor_applied() -> None:
524+
"""Very small gaps are raised to floor_ms; total still sums to target."""
525+
from dirplot.main import _proportional_durations
526+
527+
# Many tiny gaps and one large gap
528+
gaps = [0.01] * 10 + [100.0]
529+
durations = _proportional_durations(gaps, total_ms=5000)
530+
assert sum(durations) == 5000
531+
assert all(d >= 200 for d in durations)
532+
533+
534+
def test_proportional_durations_all_equal() -> None:
535+
from dirplot.main import _proportional_durations
536+
537+
durations = _proportional_durations([1.0, 1.0, 1.0, 1.0], total_ms=4000)
538+
assert sum(durations) == 4000
539+
540+
541+
# ---------------------------------------------------------------------------
542+
# replay command
543+
# ---------------------------------------------------------------------------
544+
545+
546+
def _write_jsonl(path: Path, events: list[dict]) -> None:
547+
import json
548+
549+
path.write_text("\n".join(json.dumps(e) for e in events) + "\n")
550+
551+
552+
def test_replay_basic(tmp_path: Path) -> None:
553+
"""replay renders a JSONL event log to an APNG."""
554+
root = tmp_path / "root"
555+
root.mkdir()
556+
(root / "a.py").write_bytes(b"x" * 500)
557+
(root / "b.py").write_bytes(b"x" * 300)
558+
559+
log = tmp_path / "events.jsonl"
560+
_write_jsonl(
561+
log,
562+
[
563+
{"timestamp": 1.0, "type": "created", "path": str(root / "a.py")},
564+
{"timestamp": 2.0, "type": "modified", "path": str(root / "b.py")},
565+
{"timestamp": 70.0, "type": "modified", "path": str(root / "a.py")},
566+
],
567+
)
568+
out = tmp_path / "replay.apng"
569+
result = runner.invoke(
570+
app,
571+
[
572+
"replay",
573+
str(log),
574+
"--output",
575+
str(out),
576+
"--size",
577+
"200x150",
578+
"--workers",
579+
"1",
580+
],
581+
)
582+
assert result.exit_code == 0, result.output
583+
assert out.exists()
584+
585+
586+
def test_replay_missing_log(tmp_path: Path) -> None:
587+
result = runner.invoke(
588+
app,
589+
[
590+
"replay",
591+
str(tmp_path / "missing.jsonl"),
592+
"--output",
593+
str(tmp_path / "out.apng"),
594+
"--size",
595+
"200x150",
596+
],
597+
)
598+
assert result.exit_code == 1
599+
600+
601+
def test_replay_bad_output_extension(tmp_path: Path) -> None:
602+
log = tmp_path / "events.jsonl"
603+
log.write_text("{}\n")
604+
result = runner.invoke(
605+
app,
606+
[
607+
"replay",
608+
str(log),
609+
"--output",
610+
str(tmp_path / "out.gif"),
611+
"--size",
612+
"200x150",
613+
],
614+
)
615+
assert result.exit_code == 1
616+
617+
618+
def test_replay_total_duration(tmp_path: Path) -> None:
619+
"""replay with --total-duration exercises _proportional_durations."""
620+
root = tmp_path / "root"
621+
root.mkdir()
622+
(root / "f.py").write_bytes(b"x" * 200)
623+
624+
log = tmp_path / "events.jsonl"
625+
_write_jsonl(
626+
log,
627+
[
628+
{"timestamp": 0.0, "type": "created", "path": str(root / "f.py")},
629+
{"timestamp": 100.0, "type": "modified", "path": str(root / "f.py")},
630+
{"timestamp": 300.0, "type": "modified", "path": str(root / "f.py")},
631+
],
632+
)
633+
out = tmp_path / "replay.apng"
634+
result = runner.invoke(
635+
app,
636+
[
637+
"replay",
638+
str(log),
639+
"--output",
640+
str(out),
641+
"--size",
642+
"200x150",
643+
"--total-duration",
644+
"3",
645+
"--workers",
646+
"1",
647+
],
648+
)
649+
assert result.exit_code == 0, result.output
650+
assert out.exists()

0 commit comments

Comments
 (0)