| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analysis_server/src/lsp/snippets.dart' as lsp; |
| import 'package:analysis_server/src/lsp/snippets.dart'; |
| import 'package:analysis_server/src/protocol_server.dart' as server; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(SnippetsTest); |
| defineReflectiveTests(SnippetBuilderTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class SnippetBuilderTest { |
| Future<void> test_appendChoice() async { |
| var builder = SnippetBuilder() |
| ..appendChoice({r'a'}) |
| ..appendChoice({r'a', r'b'}) |
| ..appendChoice({}, placeholderNumber: 6) |
| ..appendChoice({r'aaa', r'bbb'}, placeholderNumber: 12) |
| ..appendChoice({r'aaa', r'bbb \ bbb $ bbb | bbb , bbb } bbb'}); |
| |
| expect( |
| builder.value, |
| r'${1:a}' |
| r'${2|a,b|}' |
| r'$6' |
| r'${12|aaa,bbb|}' |
| // Only pipes (delimiter), comma (separator) and backslashes (the escape |
| // character) are escaped in choices. Not dollars or braces. |
| // https://github.com/microsoft/vscode/issues/201059 |
| r'${13|aaa,bbb \\ bbb $ bbb \| bbb \, bbb } bbb|}', |
| ); |
| } |
| |
| Future<void> test_appendPlaceholder() async { |
| var builder = SnippetBuilder() |
| ..appendPlaceholder(r'placeholder $ 1') |
| ..appendPlaceholder(r'') |
| ..appendPlaceholder(r'placeholder } 3', placeholderNumber: 6); |
| |
| expect( |
| builder.value, |
| r'${1:placeholder \$ 1}' |
| r'$2' |
| r'${6:placeholder \} 3}', |
| ); |
| } |
| |
| Future<void> test_appendTabStop() async { |
| var builder = SnippetBuilder() |
| ..appendTabStop() |
| ..appendTabStop(placeholderNumber: 10) |
| ..appendTabStop(); |
| |
| expect( |
| builder.value, |
| r'$1' |
| r'$10' |
| r'$11', |
| ); |
| } |
| |
| Future<void> test_appendText() async { |
| var builder = SnippetBuilder() |
| ..appendText(r'text 1') |
| ..appendText(r'text ${that needs} escaping $0') |
| ..appendText(r'text 2'); |
| |
| expect( |
| builder.value, |
| r'text 1' |
| r'text \${that needs} escaping \$0' |
| r'text 2', |
| ); |
| } |
| |
| Future<void> test_extension_appendPlaceholders() async { |
| var code = r''' |
| 012345678 |
| 012345678 |
| 012345678 |
| 012345678 |
| 012345678 |
| '''; |
| |
| var placeholders = [ |
| lsp.SnippetPlaceholder(2, 2), |
| lsp.SnippetPlaceholder(32, 2, linkedGroupId: 123), |
| lsp.SnippetPlaceholder(12, 2, isFinal: true), |
| lsp.SnippetPlaceholder(42, 2, linkedGroupId: 123), |
| lsp.SnippetPlaceholder(22, 2, suggestions: ['aaa', 'bbb']), |
| ]; |
| |
| var builder = SnippetBuilder() |
| ..appendPlaceholders(code, placeholders, isPreSorted: false); |
| |
| expect(builder.value, r''' |
| 01${1:23}45678 |
| 01${0:23}45678 |
| 01${2|23,aaa,bbb|}45678 |
| 01${3:23}45678 |
| 01${3:23}45678 |
| '''); |
| } |
| |
| Future<void> test_mixed() async { |
| var builder = SnippetBuilder() |
| ..appendText('text1') |
| ..appendPlaceholder('placeholder') |
| ..appendText('text2') |
| ..appendChoice({'aaa', 'bbb'}) |
| ..appendText('text3') |
| ..appendTabStop() |
| ..appendText('text4'); |
| |
| expect( |
| builder.value, |
| r'text1' |
| r'${1:placeholder}' |
| r'text2' |
| r'${2|aaa,bbb|}' |
| r'text3' |
| r'$3' |
| r'text4', |
| ); |
| } |
| } |
| |
| @reflectiveTest |
| class SnippetsTest { |
| /// Paths aren't used for anything except filtering edit groups positions |
| /// so the specific values are not important. |
| final mainPath = '/home/test.dart'; |
| final otherPath = '/home/other.dart'; |
| |
| Future<void> test_editGroups_choices() async { |
| var result = lsp.buildSnippetStringForEditGroups( |
| r''' |
| var a = 1; |
| ''', |
| filePath: mainPath, |
| editOffset: 0, |
| editGroups: [ |
| server.LinkedEditGroup( |
| [_pos(4)], |
| 1, |
| [ |
| _suggestion('aaa'), |
| _suggestion(r'bbb${},|'), // test for escaping |
| _suggestion('ccc'), |
| ], |
| ), |
| ], |
| ); |
| // Choices are never 0th placeholders, so this is `$1`. |
| expect(result, equals(r''' |
| var ${1|a,aaa,bbb${}\,\|,ccc|} = 1; |
| ''')); |
| } |
| |
| Future<void> test_editGroups_emptyGroup() async { |
| var result = lsp.buildSnippetStringForEditGroups( |
| r''' |
| class { |
| (); |
| } |
| ''', |
| filePath: mainPath, |
| editOffset: 0, |
| editGroups: [ |
| server.LinkedEditGroup( |
| [ |
| _pos(6), |
| _pos(11), |
| ], |
| 0, |
| [], |
| ), |
| ], |
| ); |
| expect(result, equals(r''' |
| class $0 { |
| $0(); |
| } |
| ''')); |
| } |
| |
| Future<void> test_editGroups_positionsInOtherFiles() async { |
| var result = lsp.buildSnippetStringForEditGroups( |
| r''' |
| class A { |
| A(); |
| } |
| ''', |
| filePath: mainPath, |
| editOffset: 0, |
| editGroups: [ |
| server.LinkedEditGroup( |
| [ |
| _pos(6), |
| _pos(10, otherPath), // Should not be included. |
| _pos(12), |
| ], |
| 1, |
| [], |
| ), |
| ], |
| ); |
| expect(result, equals(r''' |
| class ${0:A} { |
| ${0:A}(); |
| } |
| ''')); |
| } |
| |
| Future<void> test_editGroups_simpleGroup() async { |
| var result = lsp.buildSnippetStringForEditGroups( |
| r''' |
| class A { |
| A(); |
| } |
| ''', |
| filePath: mainPath, |
| editOffset: 0, |
| editGroups: [ |
| server.LinkedEditGroup( |
| [ |
| _pos(6), |
| _pos(12), |
| ], |
| 1, |
| [], |
| ), |
| ], |
| ); |
| expect(result, equals(r''' |
| class ${0:A} { |
| ${0:A}(); |
| } |
| ''')); |
| } |
| |
| Future<void> test_editGroups_withOffset() async { |
| var result = lsp.buildSnippetStringForEditGroups( |
| r''' |
| class A { |
| A(); |
| } |
| ''', |
| filePath: mainPath, |
| // This means the edit will be inserted at offset 100, so all linked edit |
| // offsets will be 100 more than in the supplied text. |
| editOffset: 100, |
| editGroups: [ |
| server.LinkedEditGroup( |
| [ |
| _pos(100 + 6), |
| _pos(100 + 12), |
| ], |
| 1, |
| [], |
| ), |
| ], |
| ); |
| expect(result, equals(r''' |
| class ${0:A} { |
| ${0:A}(); |
| } |
| ''')); |
| } |
| |
| Future<void> test_tabStops_contains() async { |
| var result = lsp.buildSnippetStringWithTabStops('a, b, c', [3, 1]); |
| expect(result, equals(r'a, ${0:b}, c')); |
| } |
| |
| Future<void> test_tabStops_empty() async { |
| var result = lsp.buildSnippetStringWithTabStops('a, b', []); |
| expect(result, equals(r'a, b')); |
| } |
| |
| Future<void> test_tabStops_endsWith() async { |
| var result = lsp.buildSnippetStringWithTabStops('a, b', [3, 1]); |
| expect(result, equals(r'a, ${0:b}')); |
| } |
| |
| Future<void> test_tabStops_escape() async { |
| var result = lsp.buildSnippetStringWithTabStops( |
| r'te$tstri}ng, te$tstri}ng, te$tstri}ng', [13, 11]); |
| expect(result, equals(r'te\$tstri}ng, ${0:te\$tstri\}ng}, te\$tstri}ng')); |
| } |
| |
| Future<void> test_tabStops_multiple() async { |
| var result = |
| lsp.buildSnippetStringWithTabStops('a, b, c', [0, 1, 3, 1, 6, 1]); |
| expect(result, equals(r'${1:a}, ${2:b}, ${3:c}')); |
| } |
| |
| Future<void> test_tabStops_null() async { |
| var result = lsp.buildSnippetStringWithTabStops('a, b', null); |
| expect(result, equals(r'a, b')); |
| } |
| |
| Future<void> test_tabStops_startsWith() async { |
| var result = lsp.buildSnippetStringWithTabStops('a, b', [0, 1]); |
| expect(result, equals(r'${0:a}, b')); |
| } |
| |
| server.Position _pos(int offset, [String? path]) => |
| server.Position(path ?? mainPath, offset); |
| |
| server.LinkedEditSuggestion _suggestion(String text) => |
| server.LinkedEditSuggestion( |
| text, |
| server.LinkedEditSuggestionKind.TYPE, // We don't use type. |
| ); |
| } |