mirror of
https://github.com/gogrlx/grlx-lsp.git
synced 2026-04-02 03:18:47 -07:00
test(lsp): improve handler test coverage from 22% to 68%
- Add completion_test.go: tests for top-level, ingredient.method, property, requisite type, and step ID completions - Add hover_test.go: tests for markdown generation, word extraction, and edge cases - Add diagnostics_test.go: tests for nil recipe, parse errors, empty ingredient, requisite validation, range helpers - Update all dependencies to latest versions - Add target/ to .gitignore (stale Rust build artifacts)
This commit is contained in:
304
internal/lsp/completion_test.go
Normal file
304
internal/lsp/completion_test.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.lsp.dev/protocol"
|
||||
|
||||
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||
)
|
||||
|
||||
func TestCompleteTopLevel(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := ``
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
|
||||
items := h.completeTopLevel("")
|
||||
if len(items) != len(schema.TopLevelKeys) {
|
||||
t.Errorf("expected %d top-level items, got %d", len(schema.TopLevelKeys), len(items))
|
||||
}
|
||||
labelSet := make(map[string]bool)
|
||||
for _, item := range items {
|
||||
labelSet[item.Label] = true
|
||||
}
|
||||
for _, key := range schema.TopLevelKeys {
|
||||
if !labelSet[key] {
|
||||
t.Errorf("missing top-level completion: %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteIngredientMethod(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
|
||||
// Without dot prefix — should return all ingredient.method combos
|
||||
items := h.completeIngredientMethod("")
|
||||
if len(items) == 0 {
|
||||
t.Fatal("expected completion items for all ingredient.method combos")
|
||||
}
|
||||
labelSet := make(map[string]bool)
|
||||
for _, item := range items {
|
||||
labelSet[item.Label] = true
|
||||
}
|
||||
for _, name := range []string{"file.managed", "cmd.run", "pkg.installed", "service.running"} {
|
||||
if !labelSet[name] {
|
||||
t.Errorf("missing completion: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// With dot prefix — should complete methods for that ingredient
|
||||
items = h.completeIngredientMethod("file.")
|
||||
if len(items) == 0 {
|
||||
t.Fatal("expected method completions for file ingredient")
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.Kind != protocol.CompletionItemKindFunction {
|
||||
t.Errorf("expected Function kind, got %v", item.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown ingredient with dot
|
||||
items = h.completeIngredientMethod("bogus.")
|
||||
if len(items) != 0 {
|
||||
t.Errorf("expected no completions for unknown ingredient, got %d", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteProperties(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
install nginx:
|
||||
pkg.installed:
|
||||
- name: nginx
|
||||
- `
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
doc := h.getDocument("file:///test.grlx")
|
||||
|
||||
// Line 4 is inside pkg.installed properties, "name" is already used
|
||||
items := h.completeProperties(doc, 4)
|
||||
|
||||
// Should offer "version" but NOT "name" (already used)
|
||||
for _, item := range items {
|
||||
if item.Label == "- name: " {
|
||||
t.Error("should not offer already-used property 'name'")
|
||||
}
|
||||
}
|
||||
|
||||
// Should offer requisites
|
||||
foundRequisites := false
|
||||
for _, item := range items {
|
||||
if item.Label == "- requisites:" {
|
||||
foundRequisites = true
|
||||
}
|
||||
}
|
||||
if !foundRequisites {
|
||||
t.Error("expected requisites in property completions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletePropertiesNoStep(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:`
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
doc := h.getDocument("file:///test.grlx")
|
||||
|
||||
items := h.completeProperties(doc, 0)
|
||||
if len(items) != 0 {
|
||||
t.Errorf("expected no completions when no step found, got %d", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteRequisiteTypes(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
|
||||
items := h.completeRequisiteTypes("")
|
||||
if len(items) != len(schema.AllRequisiteTypes) {
|
||||
t.Errorf("expected %d requisite types, got %d", len(schema.AllRequisiteTypes), len(items))
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.Kind != protocol.CompletionItemKindEnum {
|
||||
t.Errorf("expected Enum kind for requisite, got %v", item.Kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteStepIDs(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
install nginx:
|
||||
pkg.installed:
|
||||
- name: nginx
|
||||
start nginx:
|
||||
service.running:
|
||||
- name: nginx`
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
doc := h.getDocument("file:///test.grlx")
|
||||
|
||||
items := h.completeStepIDs(doc)
|
||||
if len(items) != 2 {
|
||||
t.Errorf("expected 2 step IDs, got %d", len(items))
|
||||
}
|
||||
labelSet := make(map[string]bool)
|
||||
for _, item := range items {
|
||||
labelSet[item.Label] = true
|
||||
}
|
||||
if !labelSet["install nginx"] || !labelSet["start nginx"] {
|
||||
t.Errorf("missing expected step IDs, got: %v", labelSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteStepIDsNilRecipe(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
doc := &document{content: "", recipe: nil}
|
||||
|
||||
items := h.completeStepIDs(doc)
|
||||
if len(items) != 0 {
|
||||
t.Errorf("expected no step IDs for nil recipe, got %d", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsTopLevel(t *testing.T) {
|
||||
content := "steps:\n install:\n pkg.installed:"
|
||||
if !isTopLevel(content, 0) {
|
||||
t.Error("line 0 should be top-level")
|
||||
}
|
||||
if isTopLevel(content, 1) {
|
||||
t.Error("line 1 should not be top-level (indented)")
|
||||
}
|
||||
if isTopLevel(content, 99) {
|
||||
t.Error("out-of-bounds line should not be top-level")
|
||||
}
|
||||
if isTopLevel(content, -1) {
|
||||
t.Error("negative line should not be top-level")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInRequisites(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{" - require: step1", true},
|
||||
{" - onchanges: step1", true},
|
||||
{" - onfail: step1", true},
|
||||
{" - name: foo", false},
|
||||
{"steps:", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := isInRequisites(tt.line)
|
||||
if got != tt.want {
|
||||
t.Errorf("isInRequisites(%q) = %v, want %v", tt.line, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInRequisiteValue(t *testing.T) {
|
||||
content := `steps:
|
||||
first:
|
||||
file.exists:
|
||||
- name: /tmp/a
|
||||
- requisites:
|
||||
- require:
|
||||
- first`
|
||||
|
||||
if !isInRequisiteValue(content, 6) {
|
||||
t.Error("line 6 should be in requisite value context")
|
||||
}
|
||||
if isInRequisiteValue(content, 0) {
|
||||
t.Error("line 0 should not be in requisite value context")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPropertyPosition(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{" - name: foo", true},
|
||||
{" - ", true},
|
||||
{" ", true},
|
||||
{"steps:", false},
|
||||
{" - name: foo", false}, // only 2 spaces indent
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := isPropertyPosition(tt.line)
|
||||
if got != tt.want {
|
||||
t.Errorf("isPropertyPosition(%q) = %v, want %v", tt.line, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMethodDoc(t *testing.T) {
|
||||
ing := &schema.Ingredient{Name: "file", Description: "Manage files"}
|
||||
m := &schema.Method{
|
||||
Name: "managed",
|
||||
Description: "Download and manage a file",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true, Description: "The file path"},
|
||||
{Key: "source", Type: "string", Required: true, Description: "Source URL"},
|
||||
{Key: "mode", Type: "string", Required: false},
|
||||
},
|
||||
}
|
||||
|
||||
doc := buildMethodDoc(ing, m)
|
||||
if doc == "" {
|
||||
t.Fatal("expected non-empty doc string")
|
||||
}
|
||||
// Required properties should be marked with *
|
||||
if !contains(doc, "* name") {
|
||||
t.Error("expected required marker for 'name'")
|
||||
}
|
||||
if !contains(doc, "* source") {
|
||||
t.Error("expected required marker for 'source'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindStepForLine(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
install nginx:
|
||||
pkg.installed:
|
||||
- name: nginx
|
||||
start nginx:
|
||||
service.running:
|
||||
- name: nginx`
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
doc := h.getDocument("file:///test.grlx")
|
||||
|
||||
// Line 3 is inside "install nginx" step
|
||||
step := h.findStepForLine(doc, 3)
|
||||
if step == nil {
|
||||
t.Fatal("expected to find step for line 3")
|
||||
}
|
||||
if step.Ingredient != "pkg" || step.Method != "installed" {
|
||||
t.Errorf("expected pkg.installed, got %s.%s", step.Ingredient, step.Method)
|
||||
}
|
||||
|
||||
// Line 6 is inside "start nginx" step
|
||||
step = h.findStepForLine(doc, 6)
|
||||
if step == nil {
|
||||
t.Fatal("expected to find step for line 6")
|
||||
}
|
||||
if step.Ingredient != "service" || step.Method != "running" {
|
||||
t.Errorf("expected service.running, got %s.%s", step.Ingredient, step.Method)
|
||||
}
|
||||
|
||||
// Nil recipe
|
||||
step = h.findStepForLine(&document{content: "", recipe: nil}, 0)
|
||||
if step != nil {
|
||||
t.Error("expected nil for nil recipe")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
|
||||
}
|
||||
|
||||
func containsHelper(s, substr string) bool {
|
||||
for i := 0; i+len(substr) <= len(s); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
205
internal/lsp/diagnostics_test.go
Normal file
205
internal/lsp/diagnostics_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.lsp.dev/protocol"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/gogrlx/grlx-lsp/internal/recipe"
|
||||
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||
)
|
||||
|
||||
func TestDiagnoseNilRecipe(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
doc := &document{content: "", recipe: nil}
|
||||
diags := h.diagnose(doc)
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("expected no diagnostics for nil recipe, got %d", len(diags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiagnoseParseErrors(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
bad step:
|
||||
not.a.valid.format:
|
||||
- name: foo`
|
||||
doc := &document{
|
||||
content: src,
|
||||
recipe: recipe.Parse([]byte(src)),
|
||||
}
|
||||
diags := h.diagnose(doc)
|
||||
// Should at least report the unknown ingredient
|
||||
if len(diags) == 0 {
|
||||
t.Error("expected diagnostics for invalid recipe")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiagnoseEmptyIngredient(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
// Step with empty ingredient should be skipped
|
||||
r := &recipe.Recipe{
|
||||
Steps: []recipe.Step{
|
||||
{ID: "test", Ingredient: "", Method: "run"},
|
||||
},
|
||||
}
|
||||
doc := &document{content: "", recipe: r}
|
||||
diags := h.diagnose(doc)
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("expected no diagnostics for empty ingredient, got %d", len(diags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiagnoseRequisiteUnknownRef(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
first:
|
||||
file.exists:
|
||||
- name: /tmp/a
|
||||
second:
|
||||
file.exists:
|
||||
- name: /tmp/b
|
||||
- requisites:
|
||||
- require: nonexistent_step`
|
||||
doc := &document{
|
||||
content: src,
|
||||
recipe: recipe.Parse([]byte(src)),
|
||||
}
|
||||
diags := h.diagnose(doc)
|
||||
found := false
|
||||
for _, d := range diags {
|
||||
if d.Severity == protocol.DiagnosticSeverityWarning && contains(d.Message, "reference to unknown step") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("expected warning for reference to unknown step")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidRequisiteType(t *testing.T) {
|
||||
validTypes := []string{"require", "require_any", "onchanges", "onchanges_any", "onfail", "onfail_any"}
|
||||
for _, rt := range validTypes {
|
||||
if !isValidRequisiteType(rt) {
|
||||
t.Errorf("expected %q to be valid requisite type", rt)
|
||||
}
|
||||
}
|
||||
if isValidRequisiteType("bogus") {
|
||||
t.Error("expected 'bogus' to be invalid requisite type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPointRange(t *testing.T) {
|
||||
r := pointRange(5, 10)
|
||||
if r.Start.Line != 5 || r.Start.Character != 10 {
|
||||
t.Errorf("unexpected start: %v", r.Start)
|
||||
}
|
||||
if r.End.Line != 5 || r.End.Character != 11 {
|
||||
t.Errorf("unexpected end: %v", r.End)
|
||||
}
|
||||
|
||||
// Negative values should clamp to 0
|
||||
r = pointRange(-1, -5)
|
||||
if r.Start.Line != 0 || r.Start.Character != 0 {
|
||||
t.Errorf("expected clamped to 0, got: %v", r.Start)
|
||||
}
|
||||
}
|
||||
|
||||
func TestYamlNodeRange(t *testing.T) {
|
||||
// Nil node
|
||||
r := yamlNodeRange(nil)
|
||||
if r.Start.Line != 0 || r.Start.Character != 0 {
|
||||
t.Errorf("expected (0,0) for nil node, got: %v", r.Start)
|
||||
}
|
||||
|
||||
// Normal node
|
||||
node := &yaml.Node{Line: 3, Column: 5, Value: "hello"}
|
||||
r = yamlNodeRange(node)
|
||||
if r.Start.Line != 2 || r.Start.Character != 4 {
|
||||
t.Errorf("expected (2,4), got: (%d,%d)", r.Start.Line, r.Start.Character)
|
||||
}
|
||||
if r.End.Character != 9 { // 4 + len("hello")
|
||||
t.Errorf("expected end char 9, got: %d", r.End.Character)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPublishDiagnosticsNilConn(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
src := `steps:
|
||||
test:
|
||||
pkg.installed:
|
||||
- name: nginx`
|
||||
h.updateDocument("file:///test.grlx", src)
|
||||
|
||||
// Should not panic with nil conn
|
||||
h.publishDiagnostics(t.Context(), "file:///test.grlx")
|
||||
}
|
||||
|
||||
func TestPublishDiagnosticsNoDoc(t *testing.T) {
|
||||
h := NewHandler(schema.DefaultRegistry())
|
||||
// Should not panic when document doesn't exist
|
||||
h.publishDiagnostics(t.Context(), "file:///nonexistent.grlx")
|
||||
}
|
||||
|
||||
func TestCheckRequiredAllPresent(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "managed",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true},
|
||||
{Key: "source", Type: "string", Required: true},
|
||||
},
|
||||
}
|
||||
s := recipe.Step{
|
||||
Properties: []recipe.PropertyEntry{
|
||||
{Key: "name"},
|
||||
{Key: "source"},
|
||||
},
|
||||
}
|
||||
diags := checkRequired(s, m)
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("expected no diagnostics when all required present, got %d", len(diags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUnknownNoUnknowns(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "installed",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true},
|
||||
{Key: "version", Type: "string"},
|
||||
},
|
||||
}
|
||||
s := recipe.Step{
|
||||
Ingredient: "pkg",
|
||||
Method: "installed",
|
||||
Properties: []recipe.PropertyEntry{
|
||||
{Key: "name", KeyNode: &yaml.Node{Line: 1, Column: 1, Value: "name"}},
|
||||
},
|
||||
}
|
||||
diags := checkUnknown(s, m)
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("expected no unknown property diagnostics, got %d", len(diags))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckUnknownRequisitesAllowed(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "installed",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true},
|
||||
},
|
||||
}
|
||||
s := recipe.Step{
|
||||
Ingredient: "pkg",
|
||||
Method: "installed",
|
||||
Properties: []recipe.PropertyEntry{
|
||||
{Key: "name", KeyNode: &yaml.Node{Line: 1, Column: 1, Value: "name"}},
|
||||
{Key: "requisites", KeyNode: &yaml.Node{Line: 2, Column: 1, Value: "requisites"}},
|
||||
},
|
||||
}
|
||||
diags := checkUnknown(s, m)
|
||||
if len(diags) != 0 {
|
||||
t.Errorf("'requisites' should be allowed, got diagnostics: %v", diags)
|
||||
}
|
||||
}
|
||||
128
internal/lsp/hover_test.go
Normal file
128
internal/lsp/hover_test.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogrlx/grlx-lsp/internal/schema"
|
||||
)
|
||||
|
||||
func TestBuildMethodMarkdown(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "managed",
|
||||
Description: "Download and manage a file from a source",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true, Description: "The file path"},
|
||||
{Key: "source", Type: "string", Required: true, Description: "Source URL"},
|
||||
{Key: "mode", Type: "string", Required: false, Description: "File permissions"},
|
||||
},
|
||||
}
|
||||
|
||||
md := buildMethodMarkdown("file", m)
|
||||
|
||||
// Should contain header
|
||||
if !contains(md, "### file.managed") {
|
||||
t.Error("expected markdown header with ingredient.method")
|
||||
}
|
||||
|
||||
// Should contain description
|
||||
if !contains(md, "Download and manage a file") {
|
||||
t.Error("expected description in markdown")
|
||||
}
|
||||
|
||||
// Should contain properties table
|
||||
if !contains(md, "| Property |") {
|
||||
t.Error("expected properties table")
|
||||
}
|
||||
if !contains(md, "| `name` |") {
|
||||
t.Error("expected name property in table")
|
||||
}
|
||||
if !contains(md, "| `source` |") {
|
||||
t.Error("expected source property in table")
|
||||
}
|
||||
|
||||
// Required properties should show "yes"
|
||||
if !contains(md, "| yes |") {
|
||||
t.Error("expected 'yes' for required properties")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMethodMarkdownNoProperties(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "cleaned",
|
||||
Description: "Clean package cache",
|
||||
}
|
||||
|
||||
md := buildMethodMarkdown("pkg", m)
|
||||
if !contains(md, "### pkg.cleaned") {
|
||||
t.Error("expected header")
|
||||
}
|
||||
// Should not contain a table
|
||||
if contains(md, "| Property |") {
|
||||
t.Error("should not have property table for method with no properties")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMethodMarkdownNoDescription(t *testing.T) {
|
||||
m := &schema.Method{
|
||||
Name: "test",
|
||||
Properties: []schema.Property{
|
||||
{Key: "name", Type: "string", Required: true},
|
||||
},
|
||||
}
|
||||
|
||||
md := buildMethodMarkdown("foo", m)
|
||||
if !contains(md, "### foo.test") {
|
||||
t.Error("expected header")
|
||||
}
|
||||
if !contains(md, "| `name` |") {
|
||||
t.Error("expected name property")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWordChar(t *testing.T) {
|
||||
tests := []struct {
|
||||
b byte
|
||||
want bool
|
||||
}{
|
||||
{'a', true},
|
||||
{'z', true},
|
||||
{'A', true},
|
||||
{'Z', true},
|
||||
{'0', true},
|
||||
{'9', true},
|
||||
{'_', true},
|
||||
{'.', true},
|
||||
{'-', true},
|
||||
{' ', false},
|
||||
{':', false},
|
||||
{'\t', false},
|
||||
{'(', false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := isWordChar(tt.b)
|
||||
if got != tt.want {
|
||||
t.Errorf("isWordChar(%q) = %v, want %v", tt.b, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWordAtPositionEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
line string
|
||||
col int
|
||||
want string
|
||||
}{
|
||||
{"", 0, ""},
|
||||
{"hello", 100, "hello"}, // col beyond line length
|
||||
{" file.managed:", 2, "file.managed"}, // col 2 is start of word
|
||||
{"file.managed:", 12, "file.managed"}, // just before colon
|
||||
{"a", 0, "a"},
|
||||
{"a", 1, "a"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := wordAtPosition(tt.line, tt.col)
|
||||
if got != tt.want {
|
||||
t.Errorf("wordAtPosition(%q, %d) = %q, want %q", tt.line, tt.col, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user