Files
cc-connect/agent/tmux/tmux_test.go
T
2026-06-02 23:14:41 +08:00

164 lines
4.1 KiB
Go

package tmux
import (
"context"
"testing"
"time"
)
func TestExtractNew(t *testing.T) {
tests := []struct {
name string
baseline string
current string
want string
}{
{
name: "no change",
baseline: "foo\nbar",
current: "foo\nbar",
want: "",
},
{
name: "empty baseline",
baseline: "",
current: "hello",
want: "hello",
},
{
name: "content grew (fast path)",
baseline: "foo\nbar",
current: "foo\nbar\nbaz",
want: "baz",
},
{
name: "new line after prompt",
baseline: "user@host:~$ ",
current: "user@host:~$ ls\nfile1\nfile2\nuser@host:~$ ",
want: "ls\nfile1\nfile2\nuser@host:~$ ",
},
{
name: "anchor overlap",
baseline: "line1\nline2\nline3\nline4\nline5",
current: "line3\nline4\nline5\nnew1\nnew2",
want: "new1\nnew2",
},
{
name: "fully scrolled - return all current",
baseline: "old1\nold2\nold3",
current: "new1\nnew2\nnew3",
want: "new1\nnew2\nnew3",
},
{
name: "TUI redrawn - shared frame, response replaces prompt",
baseline: "╭─ Claude ─╮\n\n>",
current: "╭─ Claude ─╮\n\nThe answer is 42.\n\n>",
want: "The answer is 42.",
},
{
name: "TUI redrawn - multi-line response",
baseline: "header\n\n>",
current: "header\n\nLine one.\nLine two.\n\n>",
want: "Line one.\nLine two.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := extractNew(tt.baseline, tt.current)
if got != tt.want {
t.Errorf("extractNew() = %q, want %q", got, tt.want)
}
})
}
}
func TestNormalizeCapture(t *testing.T) {
tests := []struct {
name string
raw string
want string
}{
{
name: "strip trailing spaces per line",
raw: "hello \nworld \n",
want: "hello\nworld",
},
{
name: "strip ANSI color codes",
raw: "\x1b[32mgreen\x1b[0m normal",
want: "green normal",
},
{
name: "strip OSC sequence",
raw: "\x1b]0;title\x07prompt$ ",
want: "prompt$",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := normalizeCapture(tt.raw)
if got != tt.want {
t.Errorf("normalizeCapture() = %q, want %q", got, tt.want)
}
})
}
}
func TestNewAgentValidation(t *testing.T) {
// Missing session name should fail
_, err := New(map[string]any{})
if err == nil {
t.Error("expected error when session is empty")
}
// With session name but tmux not in PATH - may fail on systems without tmux,
// so we just verify the session check happens before the tmux PATH check.
}
// TestResolveTargetUniquePerWorkDir verifies that two workDirs sharing the same
// basename but different parent paths never map to the same tmux window target.
func TestResolveTargetUniquePerWorkDir(t *testing.T) {
a := &Agent{sessionName: "mywork", pane: "0"}
target1, win1 := a.resolveTarget("mywork", "0", "/repo/a/app")
target2, win2 := a.resolveTarget("mywork", "0", "/repo/b/app")
if target1 == target2 {
t.Errorf("resolveTarget: collision — /repo/a/app and /repo/b/app both produced %q", target1)
}
if win1 == win2 {
t.Errorf("uniqueWindowName: collision — /repo/a/app and /repo/b/app both produced %q", win1)
}
}
// TestResolveTargetStable verifies that the same workDir always yields the same target.
func TestResolveTargetStable(t *testing.T) {
a := &Agent{sessionName: "mywork", pane: "0"}
t1, w1 := a.resolveTarget("mywork", "0", "/repo/a/app")
t2, w2 := a.resolveTarget("mywork", "0", "/repo/a/app")
if t1 != t2 || w1 != w2 {
t.Errorf("resolveTarget not deterministic: got %q/%q then %q/%q", t1, w1, t2, w2)
}
}
// TestNewTmuxSessionWorkDir verifies that the workDir is stored in the session so
// that file attachments are saved relative to the workspace, not to ".".
func TestNewTmuxSessionWorkDir(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
s, err := newTmuxSession(ctx, "sess:win", "sid1", "", 200*time.Millisecond, false, nil, "/tmp/workspace")
if err != nil {
t.Fatal(err)
}
defer s.Close()
if s.workDir != "/tmp/workspace" {
t.Errorf("workDir = %q, want /tmp/workspace", s.workDir)
}
}