package tmux_test import ( "fmt" "os" "strings" "testing" "superwork-tui/internal/tmux" ) // capturedRun records the args passed to the injected runner. type capturedRun struct { calls [][]string out []byte err error } func (c *capturedRun) runner(args ...string) ([]byte, error) { c.calls = append(c.calls, append([]string(nil), args...)) return c.out, c.err } func assertArgs(t *testing.T, got, want []string) { t.Helper() if len(got) != len(want) { t.Fatalf("arg count: got %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("arg[%d]: got %q, want %q (full: %v)", i, got[i], want[i], got) } } } // ── unit tests (runner-injected) ────────────────────────────────────────────── func TestHasSession_found(t *testing.T) { c := &capturedRun{out: nil, err: nil} tmux.SetRunner(c.runner) defer tmux.ResetRunner() ok, err := tmux.HasSession("mysession") if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Fatal("expected true") } assertArgs(t, c.calls[0], []string{"has-session", "-t", "=mysession"}) } func TestHasSession_notFound(t *testing.T) { c := &capturedRun{out: nil, err: fmt.Errorf("exit status 1")} tmux.SetRunner(c.runner) defer tmux.ResetRunner() ok, err := tmux.HasSession("nosuch") if err != nil { t.Fatalf("unexpected error: %v", err) } if ok { t.Fatal("expected false") } } func TestEnsureSession_creates(t *testing.T) { c := &capturedRun{} tmux.SetRunner(c.runner) defer tmux.ResetRunner() // first call = has-session → not found; second call = new-session c.err = fmt.Errorf("exit status 1") called := 0 tmux.SetRunner(func(args ...string) ([]byte, error) { called++ c.calls = append(c.calls, append([]string(nil), args...)) if called == 1 { return nil, fmt.Errorf("exit status 1") // has-session: not found } return nil, nil // new-session: ok }) defer tmux.ResetRunner() if err := tmux.EnsureSession("mysession"); err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"has-session", "-t", "=mysession"}) assertArgs(t, c.calls[1], []string{"new-session", "-d", "-s", "mysession"}) } func TestEnsureSession_alreadyExists(t *testing.T) { called := 0 tmux.SetRunner(func(args ...string) ([]byte, error) { called++ return nil, nil // has-session: found }) defer tmux.ResetRunner() if err := tmux.EnsureSession("existing"); err != nil { t.Fatalf("unexpected error: %v", err) } if called != 1 { t.Fatalf("expected 1 call (has-session only), got %d", called) } } func TestNewWindow_withCommand(t *testing.T) { c := &capturedRun{} tmux.SetRunner(c.runner) defer tmux.ResetRunner() err := tmux.NewWindow(tmux.NewWindowOpts{ Session: "s", Name: "123-impl", Dir: "/some/path", Command: "bash", }) if err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"new-window", "-t", "s:", "-n", "123-impl", "-c", "/some/path", "bash"}) } func TestNewWindow_noCommand(t *testing.T) { c := &capturedRun{} tmux.SetRunner(c.runner) defer tmux.ResetRunner() err := tmux.NewWindow(tmux.NewWindowOpts{ Session: "s", Name: "124-brain", Dir: "/other", }) if err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"new-window", "-t", "s:", "-n", "124-brain", "-c", "/other"}) } func TestSelectWindow(t *testing.T) { c := &capturedRun{} tmux.SetRunner(c.runner) defer tmux.ResetRunner() if err := tmux.SelectWindow("s", "win"); err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"select-window", "-t", "s:win"}) } func TestSendKeys_withEnter(t *testing.T) { var calls [][]string tmux.SetRunner(func(args ...string) ([]byte, error) { calls = append(calls, append([]string(nil), args...)) return nil, nil }) defer tmux.ResetRunner() if err := tmux.SendKeys("s", "win", "echo hi", true); err != nil { t.Fatalf("unexpected error: %v", err) } if len(calls) != 2 { t.Fatalf("expected 2 calls, got %d", len(calls)) } assertArgs(t, calls[0], []string{"send-keys", "-t", "s:win", "-l", "--", "echo hi"}) assertArgs(t, calls[1], []string{"send-keys", "-t", "s:win", "Enter"}) } func TestSendKeys_noEnter(t *testing.T) { var calls [][]string tmux.SetRunner(func(args ...string) ([]byte, error) { calls = append(calls, append([]string(nil), args...)) return nil, nil }) defer tmux.ResetRunner() if err := tmux.SendKeys("s", "win", "some text", false); err != nil { t.Fatalf("unexpected error: %v", err) } if len(calls) != 1 { t.Fatalf("expected 1 call, got %d", len(calls)) } assertArgs(t, calls[0], []string{"send-keys", "-t", "s:win", "-l", "--", "some text"}) } func TestKillWindow(t *testing.T) { c := &capturedRun{} tmux.SetRunner(c.runner) defer tmux.ResetRunner() if err := tmux.KillWindow("s", "win"); err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"kill-window", "-t", "s:win"}) } func TestListWindows(t *testing.T) { c := &capturedRun{out: []byte("win1\nwin2\nwin3\n")} tmux.SetRunner(c.runner) defer tmux.ResetRunner() wins, err := tmux.ListWindows("s") if err != nil { t.Fatalf("unexpected error: %v", err) } assertArgs(t, c.calls[0], []string{"list-windows", "-t", "s", "-F", "#{window_name}"}) if len(wins) != 3 || wins[0] != "win1" || wins[1] != "win2" || wins[2] != "win3" { t.Fatalf("unexpected windows: %v", wins) } } func TestWindowExists_true(t *testing.T) { tmux.SetRunner(func(args ...string) ([]byte, error) { return []byte("win1\ntarget\nwin3\n"), nil }) defer tmux.ResetRunner() ok, err := tmux.WindowExists("s", "target") if err != nil { t.Fatalf("unexpected error: %v", err) } if !ok { t.Fatal("expected true") } } func TestWindowExists_false(t *testing.T) { tmux.SetRunner(func(args ...string) ([]byte, error) { return []byte("win1\nwin2\n"), nil }) defer tmux.ResetRunner() ok, err := tmux.WindowExists("s", "ghost") if err != nil { t.Fatalf("unexpected error: %v", err) } if ok { t.Fatal("expected false") } } // ── integration test ────────────────────────────────────────────────────────── func TestIntegration(t *testing.T) { if !tmux.Available() { t.Skip("tmux not in PATH") } session := fmt.Sprintf("swtest-%d", os.Getpid()) t.Cleanup(func() { // best-effort cleanup; ignore error (session may already be gone) _ = tmux.KillSession(session) }) if err := tmux.EnsureSession(session); err != nil { t.Fatalf("EnsureSession: %v", err) } const winName = "testwin" if err := tmux.NewWindow(tmux.NewWindowOpts{Session: session, Name: winName, Dir: "/tmp"}); err != nil { t.Fatalf("NewWindow: %v", err) } wins, err := tmux.ListWindows(session) if err != nil { t.Fatalf("ListWindows: %v", err) } found := false for _, w := range wins { if w == winName { found = true break } } if !found { t.Fatalf("ListWindows: %q not in %v", winName, wins) } ok, err := tmux.WindowExists(session, winName) if err != nil { t.Fatalf("WindowExists: %v", err) } if !ok { t.Fatal("WindowExists: expected true") } if err := tmux.SendKeys(session, winName, "echo hello", true); err != nil { t.Fatalf("SendKeys: %v", err) } if err := tmux.KillWindow(session, winName); err != nil { t.Fatalf("KillWindow: %v", err) } ok, err = tmux.WindowExists(session, winName) if err != nil { t.Fatalf("WindowExists after kill: %v", err) } if ok { t.Fatal("WindowExists after kill: expected false") } // verify the session name contains our prefix (sanity check on naming) if !strings.HasPrefix(session, "swtest-") { t.Fatalf("unexpected session name: %s", session) } }