"""Tests for Rich dashboard display.""" from io import StringIO from rich.console import Console from gitea_dashboard.collector import RepoData from gitea_dashboard.display import ( render_dashboard, ) def _make_console(): """Create a console that captures output for testing. highlight=False desactive le highlighting automatique de Rich qui fragmente les nombres et noms avec des codes ANSI. """ buf = StringIO() return Console(file=buf, force_terminal=True, width=120, highlight=False), buf def _make_repo( name="my-repo", full_name="admin/my-repo", description="A repo", open_issues=3, is_fork=False, is_archived=False, is_mirror=False, latest_release=None, milestones=None, last_commit_date=None, ): """Build a RepoData for testing.""" return RepoData( name=name, full_name=full_name, description=description, open_issues=open_issues, is_fork=is_fork, is_archived=is_archived, is_mirror=is_mirror, latest_release=latest_release, milestones=milestones if milestones is not None else [], last_commit_date=last_commit_date, ) class TestRenderDashboardTable: """Test the main repos table rendering.""" def test_basic_repo_displayed(self): """Repo name and issues count appear in output.""" console, buf = _make_console() repos = [ _make_repo( name="mon-projet", open_issues=3, latest_release={"tag_name": "v1.2.0", "published_at": "2026-03-08T10:00:00Z"}, ), ] render_dashboard(repos, console=console) output = buf.getvalue() assert "mon-projet" in output assert "v1.2.0" in output def test_repo_without_release_shows_dash(self): """Repo with no release shows a dash character.""" console, buf = _make_console() repos = [_make_repo(name="no-release", latest_release=None)] render_dashboard(repos, console=console) output = buf.getvalue() assert "no-release" in output # The dash character for "no release" assert "\u2014" in output or "—" in output def test_fork_indicator(self): """Fork repos show [F] indicator.""" console, buf = _make_console() repos = [_make_repo(name="forked-repo", is_fork=True)] render_dashboard(repos, console=console) output = buf.getvalue() assert "forked-repo" in output assert "[F]" in output def test_archive_indicator(self): """Archived repos show [A] indicator.""" console, buf = _make_console() repos = [_make_repo(name="old-repo", is_archived=True)] render_dashboard(repos, console=console) output = buf.getvalue() assert "[A]" in output def test_mirror_indicator(self): """Mirror repos show [M] indicator.""" console, buf = _make_console() repos = [_make_repo(name="mirror-repo", is_mirror=True)] render_dashboard(repos, console=console) output = buf.getvalue() assert "[M]" in output def test_multiple_indicators(self): """Repo with multiple flags shows all indicators.""" console, buf = _make_console() repos = [_make_repo(name="special", is_fork=True, is_archived=True)] render_dashboard(repos, console=console) output = buf.getvalue() assert "[F]" in output assert "[A]" in output def test_issues_zero(self): """Repos with 0 issues display 0.""" console, buf = _make_console() repos = [_make_repo(name="clean-repo", open_issues=0)] render_dashboard(repos, console=console) output = buf.getvalue() assert "0" in output def test_multiple_repos(self): """Multiple repos all appear in the output.""" console, buf = _make_console() repos = [ _make_repo(name="repo-alpha", open_issues=1), _make_repo(name="repo-beta", open_issues=5), ] render_dashboard(repos, console=console) output = buf.getvalue() assert "repo-alpha" in output assert "repo-beta" in output class TestRenderDashboardLastCommit: """Test the last commit column rendering.""" def test_last_commit_column_displayed(self): """Column 'Dernier commit' appears in the table.""" console, buf = _make_console() repos = [_make_repo(name="projet", last_commit_date="2026-03-10T14:30:00Z")] render_dashboard(repos, console=console) output = buf.getvalue() assert "Dernier commit" in output def test_last_commit_shows_relative_date(self): """Last commit date is shown as relative date.""" console, buf = _make_console() repos = [_make_repo(name="projet", last_commit_date="2026-03-10T14:30:00Z")] render_dashboard(repos, console=console) output = buf.getvalue() # Should show some relative date (il y a Xj, etc.) assert "il y a" in output or "aujourd'hui" in output def test_last_commit_none_shows_dash(self): """Repo without commit shows dash.""" console, buf = _make_console() repos = [_make_repo(name="vide", last_commit_date=None)] render_dashboard(repos, console=console) output = buf.getvalue() assert "\u2014" in output or "—" in output class TestRenderDashboardMilestones: """Test the milestones section rendering.""" def test_milestones_displayed(self): """Repos with milestones show milestone info.""" console, buf = _make_console() repos = [ _make_repo( name="mon-projet", milestones=[ { "title": "v2.0", "open_issues": 2, "closed_issues": 3, "due_on": "2026-04-01T00:00:00Z", }, ], ), ] render_dashboard(repos, console=console) output = buf.getvalue() assert "v2.0" in output assert "3/5" in output # closed/total assert "60%" in output def test_milestone_without_due_date(self): """Milestone without due_on omits the deadline.""" console, buf = _make_console() repos = [ _make_repo( name="projet", milestones=[ { "title": "backlog", "open_issues": 5, "closed_issues": 1, "due_on": None, }, ], ), ] render_dashboard(repos, console=console) output = buf.getvalue() assert "backlog" in output assert "1/6" in output def test_no_milestones_section_when_none(self): """When no repos have milestones, milestone section is absent.""" console, buf = _make_console() repos = [_make_repo(name="simple", milestones=[])] render_dashboard(repos, console=console) output = buf.getvalue() assert "Milestones" not in output class TestRenderDashboardEmpty: """Test empty repo list handling.""" def test_empty_list_shows_message(self): """Empty repo list shows informative message.""" console, buf = _make_console() render_dashboard([], console=console) output = buf.getvalue() assert "Aucun repo" in output class TestColorizeMilestoneDue: """Test _colorize_milestone_due function.""" def test_overdue(self): """Past due date returns 'red'.""" from gitea_dashboard.display import _colorize_milestone_due assert _colorize_milestone_due("2020-01-01T00:00:00Z") == "red" def test_soon(self): """Due date within 7 days returns 'yellow'.""" from datetime import datetime, timedelta, timezone from gitea_dashboard.display import _colorize_milestone_due soon = datetime.now(timezone.utc) + timedelta(days=3) assert _colorize_milestone_due(soon.isoformat()) == "yellow" def test_ok(self): """Due date more than 7 days away returns 'green'.""" from datetime import datetime, timedelta, timezone from gitea_dashboard.display import _colorize_milestone_due future = datetime.now(timezone.utc) + timedelta(days=15) assert _colorize_milestone_due(future.isoformat()) == "green" def test_no_due(self): """No due date returns empty string.""" from gitea_dashboard.display import _colorize_milestone_due assert _colorize_milestone_due(None) == "" class TestSortRepos: """Test _sort_repos function.""" def test_sort_by_name(self): """Sorts alphabetically by name (case-insensitive).""" from gitea_dashboard.display import _sort_repos repos = [ _make_repo(name="Charlie"), _make_repo(name="alpha"), _make_repo(name="Bravo"), ] result = _sort_repos(repos, "name") assert [r.name for r in result] == ["alpha", "Bravo", "Charlie"] def test_sort_by_issues(self): """Sorts by issues count descending.""" from gitea_dashboard.display import _sort_repos repos = [ _make_repo(name="low", open_issues=1), _make_repo(name="high", open_issues=10), _make_repo(name="mid", open_issues=5), ] result = _sort_repos(repos, "issues") assert [r.name for r in result] == ["high", "mid", "low"] def test_sort_by_release(self): """Sorts by release date descending; repos without release last.""" from gitea_dashboard.display import _sort_repos repos = [ _make_repo(name="no-rel", latest_release=None), _make_repo( name="old", latest_release={"tag_name": "v1.0", "published_at": "2025-01-01T00:00:00Z"}, ), _make_repo( name="new", latest_release={"tag_name": "v2.0", "published_at": "2026-03-01T00:00:00Z"}, ), ] result = _sort_repos(repos, "release") assert [r.name for r in result] == ["new", "old", "no-rel"] def test_sort_by_activity(self): """Sorts by last commit date descending; repos without commit last.""" from gitea_dashboard.display import _sort_repos repos = [ _make_repo(name="inactive", last_commit_date=None), _make_repo(name="old-commit", last_commit_date="2025-06-01T00:00:00Z"), _make_repo(name="recent", last_commit_date="2026-03-10T00:00:00Z"), ] result = _sort_repos(repos, "activity") assert [r.name for r in result] == ["recent", "old-commit", "inactive"]