2024-12-29 06:17:40 +00:00
|
|
|
package d2lsp
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestGetCompletionItems(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
text string
|
|
|
|
|
line int
|
|
|
|
|
column int
|
|
|
|
|
want []CompletionItem
|
|
|
|
|
wantErr bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "style dot suggestions",
|
|
|
|
|
text: "a.style.",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getStyleCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "style map suggestions",
|
|
|
|
|
text: `a: {
|
|
|
|
|
style.
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getStyleCompletions(),
|
|
|
|
|
},
|
2025-01-02 04:00:45 +00:00
|
|
|
{
|
|
|
|
|
name: "classes shapes",
|
|
|
|
|
text: `classes: {
|
|
|
|
|
goal: {
|
|
|
|
|
shape:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 2,
|
|
|
|
|
column: 10,
|
|
|
|
|
want: getShapeCompletions(),
|
|
|
|
|
},
|
2025-01-02 03:35:06 +00:00
|
|
|
{
|
|
|
|
|
name: "nested style map suggestions",
|
|
|
|
|
text: `a: {
|
|
|
|
|
style: {
|
|
|
|
|
3d:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 2,
|
|
|
|
|
column: 7,
|
|
|
|
|
want: getBooleanCompletions(),
|
|
|
|
|
},
|
2024-12-29 06:17:40 +00:00
|
|
|
{
|
|
|
|
|
name: "3d style map suggestions",
|
|
|
|
|
text: `a.style: {
|
|
|
|
|
3d:
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 5,
|
|
|
|
|
want: getBooleanCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "fill pattern style map suggestions",
|
|
|
|
|
text: `a.style: {
|
|
|
|
|
fill-pattern:
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 15,
|
|
|
|
|
want: getFillPatternCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "opacity style map suggestions",
|
|
|
|
|
text: `a.style: {
|
|
|
|
|
opacity:
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 10,
|
|
|
|
|
want: getValueCompletions("opacity"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "width dot",
|
|
|
|
|
text: `a.width:`,
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getValueCompletions("width"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "layer shape",
|
|
|
|
|
text: `a
|
|
|
|
|
|
|
|
|
|
layers: {
|
|
|
|
|
hey: {
|
|
|
|
|
go: {
|
|
|
|
|
shape:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
line: 5,
|
|
|
|
|
column: 12,
|
|
|
|
|
want: getShapeCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "stroke width value",
|
|
|
|
|
text: `a.style.stroke-width: 1`,
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 23,
|
|
|
|
|
want: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "no style suggestions",
|
|
|
|
|
text: `a.style:
|
|
|
|
|
`,
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "style property suggestions",
|
|
|
|
|
text: "a -> b: { style. }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 16,
|
|
|
|
|
want: getStyleCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "style.opacity value hint",
|
|
|
|
|
text: "a -> b: { style.opacity: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 24,
|
|
|
|
|
want: getValueCompletions("opacity"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "fill pattern completions",
|
|
|
|
|
text: "a -> b: { style.fill-pattern: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 29,
|
|
|
|
|
want: getFillPatternCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "text transform completions",
|
|
|
|
|
text: "a -> b: { style.text-transform: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 31,
|
|
|
|
|
want: getTextTransformCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "boolean property completions",
|
|
|
|
|
text: "a -> b: { style.shadow: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 23,
|
|
|
|
|
want: getBooleanCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "near position completions",
|
|
|
|
|
text: "a -> b: { label.near: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 21,
|
|
|
|
|
want: getNearCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "direction completions",
|
|
|
|
|
text: "a -> b: { direction: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 20,
|
|
|
|
|
want: getDirectionCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "icon url completions",
|
|
|
|
|
text: "a -> b: { icon: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 15,
|
|
|
|
|
want: getIconCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "icon dot url completions",
|
|
|
|
|
text: "a.icon:",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 7,
|
|
|
|
|
want: getIconCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "icon near completions",
|
|
|
|
|
text: "a -> b: { icon.near: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 20,
|
|
|
|
|
want: getNearCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "icon map",
|
|
|
|
|
text: `a.icon: {
|
|
|
|
|
# here
|
|
|
|
|
}`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 2,
|
|
|
|
|
want: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "icon flat dot",
|
|
|
|
|
text: `a.icon.`,
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 7,
|
|
|
|
|
want: getLabelCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "label flat dot",
|
|
|
|
|
text: `a.label.`,
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getLabelCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead completions - dot syntax",
|
|
|
|
|
text: "a -> b: { source-arrowhead. }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 27,
|
|
|
|
|
want: getArrowheadCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead completions - colon syntax",
|
|
|
|
|
text: "a -> b: { source-arrowhead: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 27,
|
|
|
|
|
want: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead completions - map syntax",
|
|
|
|
|
text: `a -> b: {
|
|
|
|
|
source-arrowhead: {
|
|
|
|
|
# here
|
|
|
|
|
}
|
|
|
|
|
}`,
|
|
|
|
|
line: 2,
|
|
|
|
|
column: 4,
|
|
|
|
|
want: getArrowheadCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead shape completions - flat dot syntax",
|
|
|
|
|
text: "(a -> b)[0].source-arrowhead.shape:",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 35,
|
|
|
|
|
want: getArrowheadShapeCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead shape completions - dot syntax",
|
|
|
|
|
text: "a -> b: { source-arrowhead.shape: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 33,
|
|
|
|
|
want: getArrowheadShapeCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "arrowhead shape completions - map syntax",
|
|
|
|
|
text: "a -> b: { source-arrowhead: { shape: } }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 36,
|
|
|
|
|
want: getArrowheadShapeCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "width value hint",
|
|
|
|
|
text: "a -> b: { width: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 16,
|
|
|
|
|
want: getValueCompletions("width"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "height value hint",
|
|
|
|
|
text: "a -> b: { height: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 17,
|
|
|
|
|
want: getValueCompletions("height"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "tooltip markdown template",
|
|
|
|
|
text: "a -> b: { tooltip: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 18,
|
|
|
|
|
want: getTooltipCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "tooltip dot markdown template",
|
|
|
|
|
text: "a.tooltip:",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 10,
|
|
|
|
|
want: getTooltipCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "shape dot suggestions",
|
|
|
|
|
text: "a.shape:",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getShapeCompletions(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "shape suggestions",
|
|
|
|
|
text: "a -> b: { shape: }",
|
|
|
|
|
line: 0,
|
|
|
|
|
column: 16,
|
|
|
|
|
want: getShapeCompletions(),
|
|
|
|
|
},
|
2025-01-02 04:00:45 +00:00
|
|
|
{
|
|
|
|
|
name: "shape 2 suggestions",
|
|
|
|
|
text: `a: {
|
|
|
|
|
shape:
|
|
|
|
|
}`,
|
|
|
|
|
line: 1,
|
|
|
|
|
column: 8,
|
|
|
|
|
want: getShapeCompletions(),
|
|
|
|
|
},
|
2024-12-29 06:17:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got, err := GetCompletionItems(tt.text, tt.line, tt.column)
|
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
|
t.Errorf("GetCompletionItems() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(got) != len(tt.want) {
|
|
|
|
|
t.Errorf("GetCompletionItems() got %d completions, want %d", len(got), len(tt.want))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create maps for easy comparison
|
|
|
|
|
gotMap := make(map[string]CompletionItem)
|
|
|
|
|
wantMap := make(map[string]CompletionItem)
|
|
|
|
|
for _, item := range got {
|
|
|
|
|
gotMap[item.Label] = item
|
|
|
|
|
}
|
|
|
|
|
for _, item := range tt.want {
|
|
|
|
|
wantMap[item.Label] = item
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that each completion exists and has correct properties
|
|
|
|
|
for label, wantItem := range wantMap {
|
|
|
|
|
gotItem, exists := gotMap[label]
|
|
|
|
|
if !exists {
|
|
|
|
|
t.Errorf("missing completion for %q", label)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if gotItem.Kind != wantItem.Kind {
|
|
|
|
|
t.Errorf("completion %q Kind = %v, want %v", label, gotItem.Kind, wantItem.Kind)
|
|
|
|
|
}
|
|
|
|
|
if gotItem.Detail != wantItem.Detail {
|
|
|
|
|
t.Errorf("completion %q Detail = %v, want %v", label, gotItem.Detail, wantItem.Detail)
|
|
|
|
|
}
|
|
|
|
|
if gotItem.InsertText != wantItem.InsertText {
|
|
|
|
|
t.Errorf("completion %q InsertText = %v, want %v", label, gotItem.InsertText, wantItem.InsertText)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Helper function to compare CompletionItem slices
|
|
|
|
|
func equalCompletions(a, b []CompletionItem) bool {
|
|
|
|
|
if len(a) != len(b) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for i := range a {
|
|
|
|
|
if a[i].Label != b[i].Label ||
|
|
|
|
|
a[i].Kind != b[i].Kind ||
|
|
|
|
|
a[i].Detail != b[i].Detail ||
|
|
|
|
|
a[i].InsertText != b[i].InsertText {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetArrowheadShapeCompletions(t *testing.T) {
|
|
|
|
|
got := getArrowheadShapeCompletions()
|
|
|
|
|
|
|
|
|
|
expectedLabels := []string{
|
|
|
|
|
"triangle", "arrow", "diamond", "circle",
|
|
|
|
|
"cf-one", "cf-one-required",
|
|
|
|
|
"cf-many", "cf-many-required",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(got) != len(expectedLabels) {
|
|
|
|
|
t.Errorf("getArrowheadShapeCompletions() returned %d items, want %d", len(got), len(expectedLabels))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, label := range expectedLabels {
|
|
|
|
|
if got[i].Label != label {
|
|
|
|
|
t.Errorf("completion[%d].Label = %v, want %v", i, got[i].Label, label)
|
|
|
|
|
}
|
|
|
|
|
if got[i].Kind != ShapeCompletion {
|
|
|
|
|
t.Errorf("completion[%d].Kind = %v, want ShapeCompletion", i, got[i].Kind)
|
|
|
|
|
}
|
|
|
|
|
if got[i].InsertText != label {
|
|
|
|
|
t.Errorf("completion[%d].InsertText = %v, want %v", i, got[i].InsertText, label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetValueCompletions(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
property string
|
|
|
|
|
wantLabel string
|
|
|
|
|
wantDetail string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
property: "opacity",
|
|
|
|
|
wantLabel: "(number between 0.0 and 1.0)",
|
|
|
|
|
wantDetail: "e.g. 0.4",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
property: "stroke-width",
|
|
|
|
|
wantLabel: "(number between 0 and 15)",
|
|
|
|
|
wantDetail: "e.g. 2",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
property: "font-size",
|
|
|
|
|
wantLabel: "(number between 8 and 100)",
|
|
|
|
|
wantDetail: "e.g. 14",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
property: "width",
|
|
|
|
|
wantLabel: "(pixels)",
|
|
|
|
|
wantDetail: "e.g. 400",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
property: "stroke",
|
|
|
|
|
wantLabel: "(color name or hex code)",
|
|
|
|
|
wantDetail: "e.g. blue, #ff0000",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.property, func(t *testing.T) {
|
|
|
|
|
got := getValueCompletions(tt.property)
|
|
|
|
|
if len(got) != 1 {
|
|
|
|
|
t.Fatalf("getValueCompletions(%s) returned %d items, want 1", tt.property, len(got))
|
|
|
|
|
}
|
|
|
|
|
if got[0].Label != tt.wantLabel {
|
|
|
|
|
t.Errorf("completion.Label = %v, want %v", got[0].Label, tt.wantLabel)
|
|
|
|
|
}
|
|
|
|
|
if got[0].Detail != tt.wantDetail {
|
|
|
|
|
t.Errorf("completion.Detail = %v, want %v", got[0].Detail, tt.wantDetail)
|
|
|
|
|
}
|
|
|
|
|
if got[0].InsertText != "" {
|
|
|
|
|
t.Errorf("completion.InsertText = %v, want empty string", got[0].InsertText)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|