package lsp import ( "strings" "testing" ) func TestInParamList(t *testing.T) { line := "func foo(a int, b string) int {" if !inParamList(line, 15) { // inside params t.Fatalf("expected inParamList true for cursor inside params") } if inParamList(line, 2) { // before 'func' t.Fatalf("expected inParamList false for cursor before params") } if inParamList(line, len(line)) { // after ')' t.Fatalf("expected inParamList false for cursor after params") } } func TestComputeWordStart(t *testing.T) { current := "fmt.Prin" // Cursor after the word (index 8) got := computeWordStart(current, 8) // should stop after the dot at index 4 if want := 4; got != want { t.Fatalf("computeWordStart got %d want %d", got, want) } } func TestComputeTextEditAndFilter_InParams(t *testing.T) { current := "func foo(a int, b string) {" // ')' at index 26 p := CompletionParams{Position: Position{Line: 10, Character: 20}} te, filter := computeTextEditAndFilter("x int, y string", true, current, p) if te == nil { t.Fatalf("expected TextEdit") } // left should be after '(' which is at index 8 if te.Range.Start.Line != 10 || te.Range.Start.Character != 9 { t.Fatalf("start got line=%d char=%d want line=10 char=9", te.Range.Start.Line, te.Range.Start.Character) } // right should clamp to cursor (20) if te.Range.End.Line != 10 || te.Range.End.Character != 20 { t.Fatalf("end got line=%d char=%d want line=10 char=20", te.Range.End.Line, te.Range.End.Character) } if filter == "" { t.Fatalf("expected non-empty filter inside params") } } func TestComputeTextEditAndFilter_Word(t *testing.T) { current := "fmt.Prin" p := CompletionParams{Position: Position{Line: 2, Character: len(current)}} te, filter := computeTextEditAndFilter("Println", false, current, p) if te == nil { t.Fatalf("expected TextEdit") } if te.Range.Start.Character != 4 || te.Range.End.Character != len(current) { t.Fatalf("range chars got %d..%d want 4..%d", te.Range.Start.Character, te.Range.End.Character, len(current)) } if filter != "Prin" { t.Fatalf("filter got %q want %q", filter, "Prin") } } func TestLabelForCompletion(t *testing.T) { if got := labelForCompletion("Println", "Pri"); got != "Println" { t.Fatalf("label mismatch got %q want %q", got, "Println") } if got := labelForCompletion("Println", "X"); got != "X" { t.Fatalf("label mismatch with filter got %q want %q", got, "X") } if got := labelForCompletion("Println\nmore", ""); got != "Println" { t.Fatalf("label firstLine got %q want %q", got, "Println") } } func TestBuildPrompts_InParams(t *testing.T) { p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Position: Position{Line: 1, Character: 12}} sys, user := buildPrompts(true, p, "above", "func foo(", "below", "func foo(") if sys == "" || user == "" { t.Fatalf("expected non-empty prompts") } if want := "function signatures"; !contains(sys, want) { t.Fatalf("system prompt missing %q: %q", want, sys) } if want := "parameter list"; !contains(user, want) { t.Fatalf("user prompt missing %q: %q", want, user) } } func TestBuildPrompts_Outside(t *testing.T) { p := CompletionParams{TextDocument: TextDocumentIdentifier{URI: "file:///t.go"}, Position: Position{Line: 1, Character: 5}} sys, user := buildPrompts(false, p, "ab", "cur", "be", "fnctx") if sys == "" || user == "" { t.Fatalf("expected non-empty prompts") } if want := "completion engine"; !contains(sys, want) { t.Fatalf("system prompt missing %q: %q", want, sys) } if want := "Provide the next likely code"; !contains(user, want) { t.Fatalf("user prompt missing %q: %q", want, user) } } func TestComputeTextEditAndFilter_NoParensFallback(t *testing.T) { current := "func foo bar" // no parentheses cursor := len(current) p := CompletionParams{Position: Position{Line: 0, Character: cursor}} te, filter := computeTextEditAndFilter("baz", true, current, p) if te == nil { t.Fatalf("expected TextEdit from fallback path") } // fallback should behave like word edit; start at last space + 1 lastSpace := strings.LastIndex(current, " ") if te.Range.Start.Character != lastSpace+1 || te.Range.End.Character != cursor { t.Fatalf("range got %d..%d want %d..%d", te.Range.Start.Character, te.Range.End.Character, lastSpace+1, cursor) } if filter != "bar" { t.Fatalf("filter got %q want %q", filter, "bar") } } // small helper to avoid importing strings func contains(s, sub string) bool { return len(s) >= len(sub) && (func() bool { i := 0; for i+len(sub) <= len(s) { if s[i:i+len(sub)] == sub { return true }; i++ }; return false })() }