package main import ( "bytes" "strings" "testing" "time" ) func TestDisplaySessions_WithSessions(t *testing.T) { sessions := []Session{ { Process: Process{PID: 100, Cwd: "/home/user/project-a"}, State: Working, CwdPath: "/home/user/project-a", GitBranch: "feat-login", Preview: "Implementing auth...", }, { Process: Process{PID: 200, Cwd: "/home/user/project-b"}, State: NeedsInput, CwdPath: "/home/user/project-b", GitBranch: "main", Preview: "What should I do next?", }, } var buf bytes.Buffer DisplaySessions(&buf, sessions, false) output := buf.String() for _, want := range []string{ "/home/user/project-a", "feat-login", "Working", "Implementing auth...", "/home/user/project-b", "main", "Needs Input", "What should I do next?", } { if !strings.Contains(output, want) { t.Errorf("output missing %q", want) } } } func TestDisplaySessions_NoColor(t *testing.T) { sessions := []Session{ { Process: Process{PID: 100, Cwd: "/tmp"}, State: Working, CwdPath: "/tmp", GitBranch: "main", }, } var buf bytes.Buffer DisplaySessions(&buf, sessions, true) output := buf.String() if strings.Contains(output, "\033") { t.Errorf("noColor=true but output contains ANSI escape: %q", output) } if !strings.Contains(output, "Working") { t.Error("output missing state label") } } func TestDisplaySessions_Empty(t *testing.T) { var buf bytes.Buffer DisplaySessions(&buf, nil, false) output := buf.String() want := "No active Claude Code sessions found.\n" if output != want { t.Errorf("output = %q, want %q", output, want) } } func TestDisplaySessions_WorktreeDiffers(t *testing.T) { sessions := []Session{ { Process: Process{PID: 100, Cwd: "/home/user/repo/src"}, State: Working, CwdPath: "/home/user/repo/src", Worktree: "/home/user/repo", }, } var buf bytes.Buffer DisplaySessions(&buf, sessions, true) output := buf.String() if !strings.Contains(output, "[worktree: /home/user/repo]") { t.Errorf("output should show worktree when different from cwd: %q", output) } } func TestDisplaySessions_NoBranch(t *testing.T) { sessions := []Session{ { Process: Process{PID: 100, Cwd: "/tmp"}, State: Unknown, CwdPath: "/tmp", }, } var buf bytes.Buffer DisplaySessions(&buf, sessions, true) output := buf.String() if strings.Contains(output, "(") { t.Errorf("no branch should mean no parentheses: %q", output) } } // --- DisplaySessionInfos tests --- func TestDisplayWithWorkspace(t *testing.T) { sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/pierre/Code/vibe/vmux", GitBranch: "feat-auth", State: "Working", Workspace: "3", }, } var buf bytes.Buffer DisplaySessionInfos(&buf, sessions, true, time.Now()) output := buf.String() if !strings.Contains(output, "[ws:3]") { t.Errorf("output should contain [ws:3]: %q", output) } } func TestDisplayWithLabel(t *testing.T) { sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/user/project", State: "Working", Label: "review MR", }, } var buf bytes.Buffer DisplaySessionInfos(&buf, sessions, true, time.Now()) output := buf.String() if !strings.Contains(output, `"review MR"`) { t.Errorf("output should contain quoted label: %q", output) } } func TestDisplayWithWaitingSince(t *testing.T) { threeMinAgo := time.Now().Add(-3 * time.Minute) sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/user/project", State: "Needs Input", WaitingSince: &threeMinAgo, }, } var buf bytes.Buffer now := time.Now() DisplaySessionInfos(&buf, sessions, true, now) output := buf.String() if !strings.Contains(output, "depuis 3 min") { t.Errorf("output should contain waiting duration: %q", output) } } func TestDisplayWithoutOptionalFields(t *testing.T) { sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/user/project", State: "Working", }, } var buf bytes.Buffer DisplaySessionInfos(&buf, sessions, true, time.Now()) output := buf.String() if strings.Contains(output, "[ws:") { t.Errorf("should not show workspace when empty: %q", output) } if strings.Contains(output, `"`) { t.Errorf("should not show quotes when no label: %q", output) } if strings.Contains(output, "depuis") { t.Errorf("should not show waiting when nil: %q", output) } } func TestDisplaySessionInfosEmpty(t *testing.T) { var buf bytes.Buffer DisplaySessionInfos(&buf, nil, false, time.Now()) output := buf.String() want := "No active Claude Code sessions found.\n" if output != want { t.Errorf("output = %q, want %q", output, want) } } func TestDisplayWithPreview(t *testing.T) { sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/user/project", State: "Needs Input", Preview: "What should I do?", }, } var buf bytes.Buffer DisplaySessionInfos(&buf, sessions, true, time.Now()) output := buf.String() if !strings.Contains(output, "What should I do?") { t.Errorf("output should contain preview: %q", output) } } func TestFormatDuration(t *testing.T) { tests := []struct { d time.Duration want string }{ {30 * time.Second, "< 1 min"}, {3 * time.Minute, "3 min"}, {65 * time.Minute, "1 h 5 min"}, {2 * time.Hour, "2 h"}, {2*time.Hour + 30*time.Minute, "2 h 30 min"}, } for _, tt := range tests { got := formatDuration(tt.d) if got != tt.want { t.Errorf("formatDuration(%v) = %q, want %q", tt.d, got, tt.want) } } } func TestDisplayFullLine(t *testing.T) { threeMinAgo := time.Now().Add(-3 * time.Minute) sessions := []SessionInfo{ { PID: 100, SessionID: "sess-1", Cwd: "/home/pierre/Code/vibe/vmux", GitBranch: "feat/auth", State: "Needs Input", Workspace: "3", Label: "review MR !456", WaitingSince: &threeMinAgo, Preview: "preview line 1\npreview line 2", }, } var buf bytes.Buffer DisplaySessionInfos(&buf, sessions, true, time.Now()) output := buf.String() for _, want := range []string{ "[Needs Input]", "/home/pierre/Code/vibe/vmux", "(feat/auth)", "[ws:3]", `"review MR !456"`, "(depuis 3 min)", "preview line 1", "preview line 2", } { if !strings.Contains(output, want) { t.Errorf("output missing %q in: %q", want, output) } } }