diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-14 23:40:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-14 23:40:26 +0300 |
| commit | f4470bbcfbe3b14c99baeef475fe872825a13a39 (patch) | |
| tree | e12fc6168d21119dfff3a0fef5b6c5b54149f3ab /internal/lsp/codeaction_custom_test.go | |
| parent | 68438c98d23545ff791768e3e219cd21d3814e0c (diff) | |
release: v0.10.0v0.10.0
Diffstat (limited to 'internal/lsp/codeaction_custom_test.go')
| -rw-r--r-- | internal/lsp/codeaction_custom_test.go | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/internal/lsp/codeaction_custom_test.go b/internal/lsp/codeaction_custom_test.go new file mode 100644 index 0000000..7baf993 --- /dev/null +++ b/internal/lsp/codeaction_custom_test.go @@ -0,0 +1,110 @@ +package lsp + +import ( + "bytes" + "encoding/json" + "io" + "log" + "strings" + "testing" +) + +// local copy of captureResponse for this test file +func capResp(t *testing.T, buf *bytes.Buffer) Response { + t.Helper() + raw := buf.String() + idx := strings.Index(raw, "\r\n\r\n") + if idx < 0 { + t.Fatalf("no header/body separator in %q", raw) + } + body := raw[idx+4:] + var resp Response + if err := json.Unmarshal([]byte(body), &resp); err != nil { + t.Fatalf("unmarshal response: %v", err) + } + return resp +} + +func TestHandleCodeAction_ListsCustomActions(t *testing.T) { + var out bytes.Buffer + s := &Server{logger: log.New(io.Discard, "", 0), docs: make(map[string]*document), out: &out} + s.llmClient = fakeLLM{resp: "IGN"} + // Inject two custom actions + s.customActions = []CustomAction{ + {ID: "extract", Title: "Extract function", Scope: "selection", Kind: "refactor.extract", Instruction: "Extract into function"}, + {ID: "fix", Title: "Fix diagnostics", Scope: "diagnostics", Kind: "quickfix", User: "Fix:\n{{diagnostics}}\n\n{{selection}}"}, + } + // Prepare document and params + uri := "file:///t.go" + s.setDocument(uri, "package x\n\nfunc f(){}\n") + p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line: 2}, End: Position{Line: 2, Character: 5}}} + // Include diagnostics context so diagnostics-scoped action appears + ctx := CodeActionContext{Diagnostics: []Diagnostic{{Range: Range{Start: Position{Line: 2}}, Message: "warn"}}} + raw, _ := json.Marshal(ctx) + p.Context = json.RawMessage(raw) + + // Call handler + req := Request{JSONRPC: "2.0", ID: json.RawMessage("1"), Method: "textDocument/codeAction"} + req.Params, _ = json.Marshal(p) + out.Reset() + s.handleCodeAction(req) + resp := capResp(t, &out) + var actions []CodeAction + rb, _ := json.Marshal(resp.Result) + if err := json.Unmarshal(rb, &actions); err != nil { + t.Fatalf("decode: %v", err) + } + var seenSel, seenDiag bool + for _, a := range actions { + if a.Title == "Hexai: Extract function" { + seenSel = true + } + if a.Title == "Hexai: Fix diagnostics" { + seenDiag = true + } + } + if !seenSel || !seenDiag { + t.Fatalf("expected both custom actions, got %+v", actions) + } +} + +func TestResolveCodeAction_CustomInstructionAndUser(t *testing.T) { + s := newTestServer() + s.llmClient = fakeLLM{resp: "REPLACED"} + // one instruction-based and one user-based + s.customActions = []CustomAction{ + {ID: "extract", Title: "Extract function", Scope: "selection", Kind: "refactor.extract", Instruction: "Extract into function"}, + {ID: "fix", Title: "Fix diagnostics", Scope: "diagnostics", Kind: "quickfix", User: "Fix: {{diagnostics}}\n{{selection}}"}, + } + uri := "file:///t.go" + p := CodeActionParams{TextDocument: TextDocumentIdentifier{URI: uri}, Range: Range{Start: Position{Line: 1}, End: Position{Line: 1, Character: 3}}} + + // Build selection-scoped custom action payload + selPayload := struct { + Type string `json:"type"` + ID string `json:"id"` + URI string `json:"uri"` + Range Range `json:"range"` + Selection string `json:"selection"` + }{Type: "custom", ID: "extract", URI: uri, Range: p.Range, Selection: "abc"} + raw1, _ := json.Marshal(selPayload) + ca1 := CodeAction{Title: "Hexai: Extract function", Data: raw1} + if resolved, ok := s.resolveCodeAction(ca1); !ok || resolved.Edit == nil { + t.Fatalf("expected resolve for instruction-based custom action") + } + + // Build diagnostics-scoped custom action payload + diagPayload := struct { + Type string `json:"type"` + ID string `json:"id"` + URI string `json:"uri"` + Range Range `json:"range"` + Selection string `json:"selection"` + Diagnostics []Diagnostic `json:"diagnostics"` + }{Type: "custom", ID: "fix", URI: uri, Range: p.Range, Selection: "abc", Diagnostics: []Diagnostic{{Message: "lint"}}} + raw2, _ := json.Marshal(diagPayload) + ca2 := CodeAction{Title: "Hexai: Fix diagnostics", Data: raw2} + if resolved, ok := s.resolveCodeAction(ca2); !ok || resolved.Edit == nil { + t.Fatalf("expected resolve for user-based custom action") + } +} |
