github.com/getgauge/gauge@v1.6.9/api/lang/completion_test.go (about) 1 /*---------------------------------------------------------------- 2 * Copyright (c) ThoughtWorks, Inc. 3 * Licensed under the Apache License, Version 2.0 4 * See LICENSE in the project root for license information. 5 *----------------------------------------------------------------*/ 6 7 package lang 8 9 import ( 10 "encoding/json" 11 "reflect" 12 "testing" 13 "time" 14 15 gm "github.com/getgauge/gauge-proto/go/gauge_messages" 16 "github.com/getgauge/gauge/api/infoGatherer" 17 "github.com/getgauge/gauge/gauge" 18 "github.com/getgauge/gauge/runner" 19 "github.com/sourcegraph/go-langserver/pkg/lsp" 20 "github.com/sourcegraph/jsonrpc2" 21 ) 22 23 var placeHolderTests = []struct { 24 input string 25 args []string 26 want string 27 }{ 28 { 29 input: "say {} to {}", 30 args: []string{"hello", "gauge"}, 31 want: `say "${1:hello}" to "${0:gauge}"`, 32 }, 33 { 34 input: "say {}", 35 args: []string{"hello"}, 36 want: `say "${0:hello}"`, 37 }, 38 { 39 input: "say", 40 args: []string{}, 41 want: `say`, 42 }, 43 } 44 45 func TestAddPlaceHolders(t *testing.T) { 46 for _, test := range placeHolderTests { 47 got := addPlaceHolders(test.input, test.args) 48 if got != test.want { 49 t.Errorf("Adding Autocomplete placeholder failed, got: `%s`, want: `%s`", got, test.want) 50 } 51 } 52 } 53 54 type dummyInfoProvider struct { 55 specsFunc func(specs []string) []*infoGatherer.SpecDetail 56 } 57 58 func (p dummyInfoProvider) GetAvailableSpecDetails(specs []string) []*infoGatherer.SpecDetail { 59 return p.specsFunc(specs) 60 } 61 func (p dummyInfoProvider) Init() {} 62 func (p dummyInfoProvider) Steps(filterConcepts bool) []*gauge.Step { 63 return []*gauge.Step{{ 64 FileName: "foo.spec", 65 Args: []*gauge.StepArg{{Name: "hello", Value: "hello", ArgType: gauge.Dynamic}, {Name: "gauge", Value: "gauge", ArgType: gauge.Dynamic}}, 66 Value: "Say {} to {}", 67 LineText: "Say <hello> to <gauge>", 68 }} 69 } 70 71 func (p dummyInfoProvider) AllSteps(filterConcepts bool) []*gauge.Step { 72 return []*gauge.Step{{ 73 FileName: "foo.spec", 74 LineNo: 7, 75 Args: []*gauge.StepArg{{Name: "hello", Value: "hello", ArgType: gauge.Dynamic}, {Name: "gauge", Value: "gauge", ArgType: gauge.Dynamic}}, 76 Value: "Say {} to {}", 77 LineText: "Say <hello> to <gauge>", 78 }} 79 } 80 81 func (p dummyInfoProvider) Concepts() []*gm.ConceptInfo { 82 return []*gm.ConceptInfo{ 83 { 84 StepValue: &gm.ProtoStepValue{ 85 StepValue: "concept1", 86 ParameterizedStepValue: "concept1", 87 Parameters: []string{}, 88 }, 89 }, 90 } 91 } 92 93 func (p dummyInfoProvider) Params(file string, argType gauge.ArgType) []gauge.StepArg { 94 return []gauge.StepArg{{Value: "hello", ArgType: gauge.Static}, {Value: "gauge", ArgType: gauge.Static}} 95 } 96 97 func (p dummyInfoProvider) Tags() []string { 98 return []string{"hello"} 99 } 100 101 func (p dummyInfoProvider) GetSpecDirs() []string { 102 return []string{"specs"} 103 } 104 105 func (p dummyInfoProvider) SearchConceptDictionary(stepValue string) *gauge.Concept { 106 return &(gauge.Concept{FileName: "concept_uri.cpt", ConceptStep: &gauge.Step{ 107 Value: "concept1", 108 LineNo: 1, 109 LineText: "concept1", 110 }}) 111 } 112 113 func TestCompletion(t *testing.T) { 114 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 115 openFilesCache.add("uri", " * ") 116 position := lsp.Position{Line: 0, Character: len(" * ")} 117 want := completionList{IsIncomplete: false, Items: []completionItem{ 118 { 119 CompletionItem: lsp.CompletionItem{ 120 Label: "concept1", 121 Detail: "Concept", 122 Kind: lsp.CIKFunction, 123 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: position, End: position}, NewText: `concept1`}, 124 FilterText: `concept1`, 125 Documentation: "concept1", 126 }, 127 InsertTextFormat: snippet, 128 }, 129 { 130 CompletionItem: lsp.CompletionItem{ 131 Label: "Say <hello> to <gauge>", 132 Detail: "Step", 133 Kind: lsp.CIKFunction, 134 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: position, End: position}, NewText: `Say "${1:hello}" to "${0:gauge}"`}, 135 FilterText: "Say <hello> to <gauge>", 136 Documentation: "Say <hello> to <gauge>", 137 }, 138 InsertTextFormat: snippet, 139 }, 140 }, 141 } 142 provider = &dummyInfoProvider{} 143 144 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 145 p := json.RawMessage(b) 146 responses := map[gm.Message_MessageType]interface{}{} 147 responses[gm.Message_StepNamesResponse] = &gm.StepNamesResponse{Steps: []string{}} 148 lRunner.runner = &runner.GrpcRunner{LegacyClient: &mockClient{responses: responses}, Timeout: time.Second * 30} 149 150 got, err := completion(&jsonrpc2.Request{Params: &p}) 151 152 if err != nil { 153 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 154 } 155 if !reflect.DeepEqual(got, want) { 156 t.Errorf("Autocomplete request failed, got: `%v`, want: `%v`", got, want) 157 } 158 } 159 160 func TestCompletionForLineWithText(t *testing.T) { 161 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 162 openFilesCache.add("uri", " * step") 163 position := lsp.Position{Line: 0, Character: len(` *`)} 164 wantStartPos := lsp.Position{Line: position.Line, Character: len(` *`)} 165 wantEndPos := lsp.Position{Line: position.Line, Character: len(` * step`)} 166 want := completionList{IsIncomplete: false, Items: []completionItem{ 167 { 168 CompletionItem: lsp.CompletionItem{ 169 Label: "concept1", 170 Detail: "Concept", 171 Kind: lsp.CIKFunction, 172 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` concept1`}, 173 FilterText: ` concept1`, 174 Documentation: "concept1", 175 }, 176 InsertTextFormat: snippet, 177 }, 178 { 179 CompletionItem: lsp.CompletionItem{ 180 Label: "Say <hello> to <gauge>", 181 Detail: "Step", 182 Kind: lsp.CIKFunction, 183 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` Say "${1:hello}" to "${0:gauge}"`}, 184 FilterText: " Say <hello> to <gauge>", 185 Documentation: "Say <hello> to <gauge>", 186 }, 187 InsertTextFormat: snippet, 188 }, 189 }, 190 } 191 provider = &dummyInfoProvider{} 192 193 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 194 p := json.RawMessage(b) 195 196 got, err := completion(&jsonrpc2.Request{Params: &p}) 197 198 if err != nil { 199 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 200 } 201 if !reflect.DeepEqual(got, want) { 202 t.Errorf("Autocomplete request failed, got: `%+v`, want: `%+v`", got, want) 203 } 204 } 205 206 func TestCompletionInBetweenLine(t *testing.T) { 207 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 208 openFilesCache.add("uri", "* step") 209 position := lsp.Position{Line: 0, Character: len(`* s`)} 210 wantStartPos := lsp.Position{Line: position.Line, Character: len(`* `)} 211 wantEndPos := lsp.Position{Line: position.Line, Character: len(`* step`)} 212 want := completionList{IsIncomplete: false, Items: []completionItem{ 213 { 214 CompletionItem: lsp.CompletionItem{ 215 Label: "concept1", 216 Detail: "Concept", 217 Kind: lsp.CIKFunction, 218 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: `concept1`}, 219 FilterText: `concept1`, 220 Documentation: "concept1", 221 }, 222 InsertTextFormat: snippet, 223 }, 224 { 225 CompletionItem: lsp.CompletionItem{ 226 Label: "Say <hello> to <gauge>", 227 Detail: "Step", 228 Kind: lsp.CIKFunction, 229 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: `Say "${1:hello}" to "${0:gauge}"`}, 230 FilterText: "Say <hello> to <gauge>", 231 Documentation: "Say <hello> to <gauge>", 232 }, 233 InsertTextFormat: snippet, 234 }, 235 }, 236 } 237 provider = &dummyInfoProvider{} 238 239 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 240 p := json.RawMessage(b) 241 242 got, err := completion(&jsonrpc2.Request{Params: &p}) 243 244 if err != nil { 245 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 246 } 247 if !reflect.DeepEqual(got, want) { 248 t.Errorf("Autocomplete request failed, got: `%v`, want: `%v`", got, want) 249 } 250 } 251 252 func TestCompletionInBetweenLineHavingParams(t *testing.T) { 253 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 254 line := "*step with a <param> and more" 255 openFilesCache.add("uri", line) 256 position := lsp.Position{Line: 0, Character: len(`*step with a <param> and`)} 257 wantStartPos := lsp.Position{Line: position.Line, Character: len(`*`)} 258 wantEndPos := lsp.Position{Line: position.Line, Character: len(line)} 259 want := completionList{IsIncomplete: false, Items: []completionItem{ 260 { 261 CompletionItem: lsp.CompletionItem{ 262 Label: "concept1", 263 Detail: "Concept", 264 Kind: lsp.CIKFunction, 265 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` concept1`}, 266 FilterText: ` concept1`, 267 Documentation: "concept1", 268 }, 269 InsertTextFormat: snippet, 270 }, 271 { 272 CompletionItem: lsp.CompletionItem{ 273 Label: "Say <hello> to <gauge>", 274 Detail: "Step", 275 Kind: lsp.CIKFunction, 276 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` Say "${1:hello}" to "${0:gauge}"`}, 277 FilterText: " Say <param> to <gauge>", 278 Documentation: "Say <hello> to <gauge>", 279 }, 280 InsertTextFormat: snippet, 281 }, 282 }, 283 } 284 provider = &dummyInfoProvider{} 285 286 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 287 p := json.RawMessage(b) 288 289 got, err := completion(&jsonrpc2.Request{Params: &p}) 290 291 if err != nil { 292 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 293 } 294 if !reflect.DeepEqual(got, want) { 295 t.Errorf("Autocomplete request failed, got: `%+v`, want: `%+v`", got, want) 296 } 297 } 298 299 func TestCompletionInBetweenLineHavingSpecialParams(t *testing.T) { 300 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 301 line := "*step with a <file:test.txt> and more" 302 openFilesCache.add("uri", line) 303 position := lsp.Position{Line: 0, Character: len(`*step with a <file:test.txt>`)} 304 wantStartPos := lsp.Position{Line: position.Line, Character: len(`*`)} 305 wantEndPos := lsp.Position{Line: position.Line, Character: len(line)} 306 want := completionList{IsIncomplete: false, Items: []completionItem{ 307 { 308 CompletionItem: lsp.CompletionItem{ 309 Label: "concept1", 310 Detail: "Concept", 311 Kind: lsp.CIKFunction, 312 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` concept1`}, 313 FilterText: ` concept1`, 314 Documentation: "concept1", 315 }, 316 InsertTextFormat: snippet, 317 }, 318 { 319 CompletionItem: lsp.CompletionItem{ 320 Label: "Say <hello> to <gauge>", 321 Detail: "Step", 322 Kind: lsp.CIKFunction, 323 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: ` Say "${1:hello}" to "${0:gauge}"`}, 324 FilterText: " Say <file:test.txt> to <gauge>", 325 Documentation: "Say <hello> to <gauge>", 326 }, 327 InsertTextFormat: snippet, 328 }, 329 }, 330 } 331 provider = &dummyInfoProvider{} 332 333 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 334 p := json.RawMessage(b) 335 336 got, err := completion(&jsonrpc2.Request{Params: &p}) 337 338 if err != nil { 339 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 340 } 341 if !reflect.DeepEqual(got, want) { 342 t.Errorf("Autocomplete request failed, got: `%+v`, want: `%+v`", got, want) 343 } 344 } 345 346 func TestParamCompletion(t *testing.T) { 347 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 348 line := ` * step with a "param` 349 openFilesCache.add("uri", line) 350 position := lsp.Position{Line: 0, Character: len(` * step with a "pa`)} 351 wantStartPos := lsp.Position{Line: position.Line, Character: len(` * step with a "`)} 352 wantEndPos := lsp.Position{Line: position.Line, Character: len(` * step with a "param`)} 353 want := completionList{IsIncomplete: false, Items: []completionItem{ 354 { 355 CompletionItem: lsp.CompletionItem{ 356 Label: "hello", 357 FilterText: "hello\"", 358 Detail: "static", 359 Kind: lsp.CIKVariable, 360 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: "hello\""}, 361 }, 362 InsertTextFormat: text, 363 }, 364 { 365 CompletionItem: lsp.CompletionItem{ 366 Label: "gauge", 367 FilterText: "gauge\"", 368 Detail: "static", 369 Kind: lsp.CIKVariable, 370 TextEdit: &lsp.TextEdit{Range: lsp.Range{Start: wantStartPos, End: wantEndPos}, NewText: "gauge\""}, 371 }, 372 InsertTextFormat: text, 373 }, 374 }, 375 } 376 provider = &dummyInfoProvider{} 377 378 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 379 p := json.RawMessage(b) 380 381 got, err := completion(&jsonrpc2.Request{Params: &p}) 382 383 if err != nil { 384 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 385 } 386 if !reflect.DeepEqual(got, want) { 387 t.Errorf("Autocomplete request failed, got: `%+v`, want: `%+v`", got, want) 388 } 389 } 390 391 func TestCompletionWithError(t *testing.T) { 392 p := json.RawMessage("sfdf") 393 _, err := completion(&jsonrpc2.Request{Params: &p}) 394 395 if err == nil { 396 t.Error("Expected error != nil in Completion, got nil") 397 } 398 } 399 400 func TestCompletionForInvalidPosition(t *testing.T) { 401 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 402 openFilesCache.add("uri", " * step") 403 position := lsp.Position{Line: 1, Character: 2} 404 want := completionList{IsIncomplete: false, Items: []completionItem{}} 405 provider = &dummyInfoProvider{} 406 407 b, _ := json.Marshal(lsp.TextDocumentPositionParams{TextDocument: lsp.TextDocumentIdentifier{URI: "uri"}, Position: position}) 408 p := json.RawMessage(b) 409 410 got, err := completion(&jsonrpc2.Request{Params: &p}) 411 412 if err != nil { 413 t.Fatalf("Expected error == nil in Completion, got %s", err.Error()) 414 } 415 if !reflect.DeepEqual(got, want) { 416 t.Errorf("Autocomplete request failed, got: `%+v`, want: `%+v`", got, want) 417 } 418 } 419 420 func TestCompletionResolve(t *testing.T) { 421 want := completionItem{CompletionItem: lsp.CompletionItem{Label: "step"}} 422 b, _ := json.Marshal(want) 423 p := json.RawMessage(b) 424 got, err := resolveCompletion(&jsonrpc2.Request{Params: &p}) 425 426 if err != nil { 427 t.Errorf("Expected error == nil in Completion resolve, got %s", err.Error()) 428 } 429 430 if !reflect.DeepEqual(got, want) { 431 t.Errorf("Autocomplete resolve request failed, got: `%v`, want: `%v`", got, want) 432 } 433 } 434 435 func TestCompletionResolveWithError(t *testing.T) { 436 p := json.RawMessage("sfdf") 437 _, err := resolveCompletion(&jsonrpc2.Request{Params: &p}) 438 439 if err == nil { 440 t.Error("Expected error != nil in Completion, got nil") 441 } 442 } 443 444 func TestIsInStepCompletionAtStartOfLine(t *testing.T) { 445 if !isStepCompletion("* ", 1) { 446 t.Errorf("isStepCompletion not recognizing step context") 447 } 448 } 449 450 func TestIsInStepCompletionAtEndOfLine(t *testing.T) { 451 if !isStepCompletion("* Step without params", 21) { 452 t.Errorf("isStepCompletion not recognizing step context") 453 } 454 } 455 456 var paramContextTest = []struct { 457 input string 458 charPos int 459 want bool 460 }{ 461 { 462 input: `* Step with "static" and <dynamic> params`, 463 charPos: len(`* Step with "`), 464 want: true, 465 }, 466 { 467 input: `* Step with "static" and <dynamic> params`, 468 charPos: len(`* Step with "static" an`), 469 want: false, 470 }, 471 { 472 input: `* Step with "static" and <dynamic> params`, 473 charPos: len(`* Step with "static" and <d`), 474 want: true, 475 }, 476 } 477 478 func TestIsInParamContext(t *testing.T) { 479 for _, test := range paramContextTest { 480 got := inParameterContext(test.input, test.charPos) 481 if test.want != got { 482 t.Errorf("got : %v, want : %v", got, test.want) 483 } 484 } 485 } 486 487 func TestIsInTagsContext(t *testing.T) { 488 specText := `Specification Heading 489 ===================== 490 tags:foo, bar 491 492 Scenario Heading 493 ---------------- 494 tags: blah,abc 495 * step 496 ` 497 uri := lsp.DocumentURI("foo.spec") 498 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 499 openFilesCache.add(uri, specText) 500 got := isInTagsContext(2, uri) 501 if !got { 502 t.Errorf("want : %v\n Got : %v", true, got) 503 } 504 } 505 506 func TestIsInTagsContextMultiline(t *testing.T) { 507 specText := `Specification Heading 508 ===================== 509 tags:foo, bar, 510 abc 511 512 Scenario Heading 513 ---------------- 514 tags: blah,abc 515 * step 516 ` 517 uri := lsp.DocumentURI("foo.spec") 518 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 519 openFilesCache.add(uri, specText) 520 got := isInTagsContext(3, uri) 521 if !got { 522 t.Errorf("want : %v\n Got : %v", true, got) 523 } 524 } 525 526 func TestNotInTagsContext(t *testing.T) { 527 specText := `Specification Heading 528 ===================== 529 tags:foo, bar 530 531 Scenario Heading 532 ---------------- 533 tags: blah,abc 534 * step 535 ` 536 uri := lsp.DocumentURI("foo.spec") 537 openFilesCache = &files{cache: make(map[lsp.DocumentURI][]string)} 538 openFilesCache.add(uri, specText) 539 got := isInTagsContext(3, uri) 540 if got { 541 t.Errorf("want : %v\n Got : %v", false, got) 542 } 543 }