204 lines
6.0 KiB
Go
204 lines
6.0 KiB
Go
package tui
|
|
|
|
import "testing"
|
|
|
|
func TestTriggerCompletion(t *testing.T) {
|
|
t.Run("slash_triggers_command", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.triggerCompletion("/")
|
|
|
|
if !m.isCompletionActive() {
|
|
t.Error("/ should activate completion")
|
|
}
|
|
if m.completionState.Kind != "command" {
|
|
t.Errorf("expected kind 'command', got %q", m.completionState.Kind)
|
|
}
|
|
if m.overlay != OverlayCompletion {
|
|
t.Errorf("expected OverlayCompletion, got %d", m.overlay)
|
|
}
|
|
if len(m.completionState.AllItems) == 0 {
|
|
t.Error("should have completion items for /")
|
|
}
|
|
})
|
|
|
|
t.Run("at_triggers_attachments_with_multiselect", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.triggerCompletion("@")
|
|
|
|
// @ triggers agent/file completion.
|
|
// It may or may not find matches depending on agents + cwd.
|
|
// If agents exist, it should activate.
|
|
if m.isCompletionActive() {
|
|
if m.completionState.Kind != "attachments" {
|
|
t.Errorf("expected kind 'attachments', got %q", m.completionState.Kind)
|
|
}
|
|
if m.completionState.Selected == nil {
|
|
t.Error("attachments should initialize Selected map")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("hash_triggers_skills", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.triggerCompletion("#")
|
|
|
|
if !m.isCompletionActive() {
|
|
t.Error("# should activate completion for skills")
|
|
}
|
|
if m.completionState.Kind != "skills" {
|
|
t.Errorf("expected kind 'skills', got %q", m.completionState.Kind)
|
|
}
|
|
if m.completionState.Selected == nil {
|
|
t.Error("skills should initialize Selected map")
|
|
}
|
|
})
|
|
|
|
t.Run("no_matches_stays_inactive", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.triggerCompletion("/zzzznonexistent")
|
|
|
|
if m.isCompletionActive() {
|
|
t.Error("should not activate with no matches")
|
|
}
|
|
})
|
|
|
|
t.Run("plain_text_no_trigger", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.triggerCompletion("hello")
|
|
|
|
if m.isCompletionActive() {
|
|
t.Error("plain text should not trigger completion")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestAcceptCompletion(t *testing.T) {
|
|
t.Run("single_select", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
items := []Completion{
|
|
{Label: "/help", Insert: "/help "},
|
|
{Label: "/clear", Insert: "/clear "},
|
|
}
|
|
m.completionState = newCompletionState("command", items, false)
|
|
m.overlay = OverlayCompletion
|
|
m.completionState.Index = 0
|
|
|
|
m.acceptCompletion()
|
|
|
|
if m.input.Value() != "/help " {
|
|
t.Errorf("expected '/help ', got %q", m.input.Value())
|
|
}
|
|
if m.isCompletionActive() {
|
|
t.Error("should be inactive after accept")
|
|
}
|
|
if m.overlay != OverlayNone {
|
|
t.Error("overlay should be OverlayNone")
|
|
}
|
|
})
|
|
|
|
t.Run("multi_select_with_selections", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
items := []Completion{
|
|
{Label: "@a", Insert: "@a "},
|
|
{Label: "@b", Insert: "@b "},
|
|
{Label: "@c", Insert: "@c "},
|
|
}
|
|
m.completionState = newCompletionState("attachments", items, true)
|
|
m.overlay = OverlayCompletion
|
|
m.completionState.Index = 0
|
|
m.completionState.Selected[1] = true
|
|
|
|
m.acceptCompletion()
|
|
|
|
if m.input.Value() != "@b " {
|
|
t.Errorf("expected '@b ', got %q", m.input.Value())
|
|
}
|
|
if m.isCompletionActive() {
|
|
t.Error("should be inactive after accept")
|
|
}
|
|
})
|
|
|
|
t.Run("multi_select_empty_fallback", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
items := []Completion{
|
|
{Label: "@x", Insert: "@x "},
|
|
{Label: "@y", Insert: "@y "},
|
|
}
|
|
m.completionState = newCompletionState("attachments", items, true)
|
|
m.overlay = OverlayCompletion
|
|
m.completionState.Index = 1
|
|
|
|
m.acceptCompletion()
|
|
|
|
if m.input.Value() != "@y " {
|
|
t.Errorf("expected '@y ' as fallback, got %q", m.input.Value())
|
|
}
|
|
})
|
|
|
|
t.Run("inactive_noop", func(t *testing.T) {
|
|
m := newTestModel(t)
|
|
m.completionState = nil
|
|
m.input.SetValue("original")
|
|
|
|
m.acceptCompletion()
|
|
|
|
if m.input.Value() != "original" {
|
|
t.Errorf("inactive accept should be noop, got %q", m.input.Value())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCloseCompletion(t *testing.T) {
|
|
m := newTestModel(t)
|
|
items := []Completion{{Label: "test"}}
|
|
m.completionState = newCompletionState("command", items, true)
|
|
m.completionState.Index = 5
|
|
m.completionState.Selected[0] = true
|
|
m.overlay = OverlayCompletion
|
|
|
|
m.closeCompletion()
|
|
|
|
if m.isCompletionActive() {
|
|
t.Error("completionState should be nil")
|
|
}
|
|
if m.overlay != OverlayNone {
|
|
t.Errorf("overlay should be OverlayNone, got %d", m.overlay)
|
|
}
|
|
}
|
|
|
|
func TestFilterCompletions(t *testing.T) {
|
|
items := []Completion{
|
|
{Label: "/help"},
|
|
{Label: "/clear"},
|
|
{Label: "/model"},
|
|
}
|
|
|
|
t.Run("empty_query_returns_all", func(t *testing.T) {
|
|
filtered := FilterCompletions(items, "")
|
|
if len(filtered) != 3 {
|
|
t.Errorf("expected 3, got %d", len(filtered))
|
|
}
|
|
})
|
|
|
|
t.Run("filters_by_substring", func(t *testing.T) {
|
|
filtered := FilterCompletions(items, "el")
|
|
if len(filtered) != 2 {
|
|
t.Errorf("expected 2 (help, model), got %d", len(filtered))
|
|
}
|
|
})
|
|
|
|
t.Run("case_insensitive", func(t *testing.T) {
|
|
filtered := FilterCompletions(items, "HELP")
|
|
if len(filtered) != 1 {
|
|
t.Errorf("expected 1, got %d", len(filtered))
|
|
}
|
|
})
|
|
|
|
t.Run("no_match", func(t *testing.T) {
|
|
filtered := FilterCompletions(items, "zzz")
|
|
if len(filtered) != 0 {
|
|
t.Errorf("expected 0, got %d", len(filtered))
|
|
}
|
|
})
|
|
}
|