Files
superwork/tui/internal/tmux/tmux_test.go
T
2026-06-23 05:02:15 +08:00

309 lines
7.7 KiB
Go

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)
}
}