diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-08 16:31:40 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-08 16:31:40 +0200 |
| commit | c802ba5803de1a53749bb5c4ecbc0159fceb385f (patch) | |
| tree | 02e612286f36bc6c65563bc33cf53639817d2db1 /internal/tmuxedit/agent_test.go | |
| parent | 887d7bc186db90c3903851b0f1db2d24df5d7a7b (diff) | |
refactor tmuxedit to Agent interface with cursor/claude/config implementations
Replace monolithic AgentConfig struct with an Agent interface backed by
baseAgent defaults and separate implementations for cursor (box-drawing
extraction, bulk backspace clearing) and claude (section-scoped extraction
with continuation lines, vim clearing). Simple agents (amp, aider) and
user-defined agents use configAgent with baseAgent defaults.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/tmuxedit/agent_test.go')
| -rw-r--r-- | internal/tmuxedit/agent_test.go | 284 |
1 files changed, 47 insertions, 237 deletions
diff --git a/internal/tmuxedit/agent_test.go b/internal/tmuxedit/agent_test.go index 7ad1274..3673d70 100644 --- a/internal/tmuxedit/agent_test.go +++ b/internal/tmuxedit/agent_test.go @@ -2,12 +2,8 @@ package tmuxedit import ( "testing" - - "codeberg.org/snonux/hexai/internal/appconfig" ) -func boolP(b bool) *bool { return &b } - func TestDetectAgent(t *testing.T) { agents := builtinAgents() tests := []struct { @@ -28,8 +24,8 @@ func TestDetectAgent(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := detectAgent(tt.content, agents) - if got.Name != tt.want { - t.Errorf("detectAgent() = %q, want %q", got.Name, tt.want) + if got.Name() != tt.want { + t.Errorf("detectAgent() = %q, want %q", got.Name(), tt.want) } }) } @@ -50,260 +46,74 @@ func TestFindAgentByName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := findAgentByName(tt.name, agents) - if got.Name != tt.want { - t.Errorf("findAgentByName(%q) = %q, want %q", tt.name, got.Name, tt.want) - } - }) - } -} - -func TestExtractPrompt(t *testing.T) { - tests := []struct { - name string - content string - agent AgentConfig - want string - }{ - { - name: "claude prompt", - content: "────\n❯ hello world\n────", - agent: builtinAgents()[1], // claude - want: "hello world", - }, - { - name: "cursor prompt with box and arrow", - content: "Cursor Agent\n │ → fix the bug INSERT │", - agent: builtinAgents()[0], // cursor - want: "fix the bug", - }, - { - name: "cursor prompt without arrow", - content: "Cursor Agent\n │ fix the bug │", - agent: builtinAgents()[0], // cursor - want: "fix the bug", - }, - { - name: "cursor prompt strips follow-up", - content: "Cursor\n │ → Add a follow-up │", - agent: builtinAgents()[0], // cursor - want: "", - }, - { - name: "cursor multi-line prompt", - content: " │ → first line of prompt │\n │ second line here │\n │ third line end │", - agent: builtinAgents()[0], // cursor - want: "first line of prompt\nsecond line here\nthird line end", - }, - { - name: "cursor multi-line with noise", - content: " │ → fix the bug INSERT │\n │ also refactor tests │", - agent: builtinAgents()[0], // cursor - want: "fix the bug\nalso refactor tests", - }, - { - name: "cursor multi-box takes last box only", - content: " ┌──────────────┐\n" + - " │ $ git push │\n" + - " └──────────────┘\n" + - " ┌──────────────┐\n" + - " │ Run command? │\n" + - " │ → Yes (enter) │\n" + - " │ No (esc) │\n" + - " └──────────────┘\n" + - " ┌──────────────┐\n" + - " │ → hello world │\n" + - " └──────────────┘\n", - agent: builtinAgents()[0], // cursor - want: "hello world", - }, - { - name: "cursor multi-box multi-line prompt", - content: " ┌──────────────┐\n" + - " │ $ git push │\n" + - " └──────────────┘\n" + - " ┌──────────────┐\n" + - " │ → first line │\n" + - " │ second line │\n" + - " │ third line │\n" + - " └──────────────┘\n", - agent: builtinAgents()[0], // cursor - want: "first line\nsecond line\nthird line", - }, - { - name: "no pattern", - content: "some text", - agent: genericAgent(), - want: "", - }, - { - name: "no match", - content: "no prompt here", - agent: builtinAgents()[1], // claude - want: "", - }, - { - name: "invalid regex", - content: "> test", - agent: AgentConfig{PromptPattern: "[invalid"}, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := extractPrompt(tt.content, tt.agent) - if got != tt.want { - t.Errorf("extractPrompt() = %q, want %q", got, tt.want) + if got.Name() != tt.want { + t.Errorf("findAgentByName(%q) = %q, want %q", tt.name, got.Name(), tt.want) } }) } } -func TestStripNoise(t *testing.T) { - tests := []struct { - name string - text string - patterns []string - want string - }{ - {"no patterns", "hello world", nil, "hello world"}, - {"strip INSERT", "fix the bug INSERT", []string{"INSERT"}, "fix the bug"}, - {"strip multiple", "INSERT fix the bug Add a follow-up", []string{"INSERT", "Add a follow-up"}, "fix the bug"}, - {"strip to empty", "INSERT", []string{"INSERT"}, ""}, +func TestDetectAgent_InvalidRegex(t *testing.T) { + agents := []Agent{ + &configAgent{baseAgent{name: "bad", detectPattern: "[invalid"}}, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := stripNoise(tt.text, tt.patterns) - if got != tt.want { - t.Errorf("stripNoise() = %q, want %q", got, tt.want) - } - }) + got := detectAgent("anything", agents) + if got.Name() != "generic" { + t.Errorf("expected generic fallback for invalid regex, got %q", got.Name()) } } -func TestResolveAgents_MergeOverride(t *testing.T) { - cfgAgents := []appconfig.TmuxEditAgentCfg{ - { - Name: "claude", - DisplayName: "My Claude", - ClearFirst: boolP(false), - }, - } - agents := resolveAgents(cfgAgents) - var claude AgentConfig - for _, a := range agents { - if a.Name == "claude" { - claude = a - break - } - } - if claude.DisplayName != "My Claude" { - t.Errorf("DisplayName = %q, want My Claude", claude.DisplayName) - } - if claude.ClearFirst { - t.Error("ClearFirst should be false after override") - } - // DetectPattern should be preserved from builtin - if claude.DetectPattern == "" { - t.Error("DetectPattern should be preserved from builtin") +func TestGenericAgent(t *testing.T) { + g := genericAgent() + if g.Name() != "generic" { + t.Errorf("Name = %q, want generic", g.Name()) } } -func TestResolveAgents_MergeAllFields(t *testing.T) { - cfgAgents := []appconfig.TmuxEditAgentCfg{ - { - Name: "claude", - DisplayName: "Custom Claude", - DetectPattern: "(?i)custom-claude", - PromptPattern: `>\s+(.*)$`, - StripPatterns: []string{"NOISE"}, - ClearFirst: boolP(true), - ClearKeys: "C-k", - NewlineKeys: "C-Enter", - SubmitKeys: "C-m", - }, - } - agents := resolveAgents(cfgAgents) - var a AgentConfig - for _, ag := range agents { - if ag.Name == "claude" { - a = ag - break - } - } - if a.DetectPattern != "(?i)custom-claude" { - t.Errorf("DetectPattern = %q", a.DetectPattern) - } - if a.PromptPattern != `>\s+(.*)$` { - t.Errorf("PromptPattern = %q", a.PromptPattern) - } - if len(a.StripPatterns) != 1 || a.StripPatterns[0] != "NOISE" { - t.Errorf("StripPatterns = %v", a.StripPatterns) - } - if a.ClearKeys != "C-k" { - t.Errorf("ClearKeys = %q", a.ClearKeys) - } - if a.NewlineKeys != "C-Enter" { - t.Errorf("NewlineKeys = %q", a.NewlineKeys) - } - if a.SubmitKeys != "C-m" { - t.Errorf("SubmitKeys = %q", a.SubmitKeys) +func TestBaseAgent_SendText_Empty(t *testing.T) { + b := &baseAgent{newlineKeys: "S-Enter"} + err := b.SendText("%1", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) } } -func TestResolveAgents_AddNew(t *testing.T) { - cfgAgents := []appconfig.TmuxEditAgentCfg{ - { - Name: "custom", - DisplayName: "Custom Agent", - DetectPattern: "(?i)custom", - PromptPattern: `>\s*(.+)$`, - ClearFirst: boolP(true), - }, - } - agents := resolveAgents(cfgAgents) - found := false - for _, a := range agents { - if a.Name == "custom" { - found = true - if a.DisplayName != "Custom Agent" { - t.Errorf("DisplayName = %q, want Custom Agent", a.DisplayName) - } - if !a.ClearFirst { - t.Error("ClearFirst should be true") - } - } - } - if !found { - t.Error("custom agent not found in resolved agents") +func TestBaseAgent_ClearInput_Disabled(t *testing.T) { + b := &baseAgent{clearFirst: false, clearKeys: "C-u"} + err := b.ClearInput("%1") + if err != nil { + t.Fatalf("unexpected error: %v", err) } } -func TestAgentFromConfig_DefaultDisplayName(t *testing.T) { - cfg := appconfig.TmuxEditAgentCfg{ - Name: "test", - } - a := agentFromConfig(cfg) - if a.DisplayName != "test" { - t.Errorf("DisplayName = %q, want test (defaulted from Name)", a.DisplayName) +func TestBaseAgent_ExtractPrompt_NoPattern(t *testing.T) { + b := &baseAgent{} + got := b.ExtractPrompt("some content") + if got != "" { + t.Errorf("expected empty, got %q", got) } } -func TestDetectAgent_InvalidRegex(t *testing.T) { - agents := []AgentConfig{ - {Name: "bad", DetectPattern: "[invalid"}, - } - got := detectAgent("anything", agents) - if got.Name != "generic" { - t.Errorf("expected generic fallback for invalid regex, got %q", got.Name) +func TestBaseAgent_ExtractPrompt_InvalidRegex(t *testing.T) { + b := &baseAgent{promptPat: "[invalid"} + got := b.ExtractPrompt("> test") + if got != "" { + t.Errorf("expected empty for invalid regex, got %q", got) } } -func TestGenericAgent(t *testing.T) { - g := genericAgent() - if g.Name != "generic" { - t.Errorf("Name = %q, want generic", g.Name) - } - if g.SubmitKeys != "Enter" { - t.Errorf("SubmitKeys = %q, want Enter", g.SubmitKeys) +func TestConfigurable_Interface(t *testing.T) { + // Verify that all agent types implement Configurable + agents := builtinAgents() + for _, a := range agents { + c, ok := a.(Configurable) + if !ok { + t.Errorf("agent %q does not implement Configurable", a.Name()) + continue + } + base := c.Base() + if base.name != a.Name() { + t.Errorf("Base().name = %q, want %q", base.name, a.Name()) + } } } |
