Change setup of capabilities in LSP tests to allow easier chaining

Change-Id: I6bc2743d64e7eb263f34f9c12140588d35b1482e
Reviewed-on: https://dart-review.googlesource.com/c/87065
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/test/lsp/code_actions_abstract.dart b/pkg/analysis_server/test/lsp/code_actions_abstract.dart
deleted file mode 100644
index 9517b53..0000000
--- a/pkg/analysis_server/test/lsp/code_actions_abstract.dart
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2018, 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/lsp_protocol/protocol_generated.dart';
-
-import 'server_abstract.dart';
-
-abstract class AbstractCodeActionsTest extends AbstractLspAnalysisServerTest {
-  initializeWithSupportForKinds(List<CodeActionKind> kinds) async {
-    await initialize(textDocumentCapabilities: {
-      'codeAction': {
-        'codeActionLiteralSupport': {
-          'codeActionKind': {
-            'valueSet': kinds.map((k) => k.toJson()).toList(),
-          },
-        },
-      },
-    });
-  }
-}
diff --git a/pkg/analysis_server/test/lsp/code_actions_source_test.dart b/pkg/analysis_server/test/lsp/code_actions_source_test.dart
index a1f9cfb..92e6671 100644
--- a/pkg/analysis_server/test/lsp/code_actions_source_test.dart
+++ b/pkg/analysis_server/test/lsp/code_actions_source_test.dart
@@ -9,7 +9,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import '../tool/lsp_spec/matchers.dart';
-import 'code_actions_abstract.dart';
+import 'server_abstract.dart';
 
 main() {
   defineReflectiveSuite(() {
@@ -18,8 +18,69 @@
   });
 }
 
+abstract class AbstractCodeActionsTest extends AbstractLspAnalysisServerTest {
+  Future<void> checkCodeActionAvailable(
+    Uri uri,
+    String command,
+    String title, {
+    bool asCodeActionLiteral = false,
+    bool asCommand = false,
+  }) async {
+    final codeActions = await getCodeActions(uri.toString());
+    final codeAction = findCommand(codeActions, command);
+    expect(codeAction, isNotNull);
+
+    codeAction.map(
+      (command) {
+        if (!asCommand) {
+          throw 'Got Command but expected CodeAction literal';
+        }
+        expect(command.title, equals(title));
+        expect(command.arguments, equals([uri.toFilePath()]));
+      },
+      (codeAction) {
+        if (!asCodeActionLiteral) {
+          throw 'Got CodeAction literal but expected Command';
+        }
+        expect(codeAction, isNotNull);
+        expect(codeAction.title, equals(title));
+        expect(codeAction.command.title, equals(title));
+        expect(codeAction.command.arguments, equals([uri.toFilePath()]));
+      },
+    );
+  }
+
+  Either2<Command, CodeAction> findCommand(
+      List<Either2<Command, CodeAction>> actions, String commandID) {
+    for (var codeAction in actions) {
+      final id = codeAction.map(
+          (cmd) => cmd.command, (action) => action.command.command);
+      if (id == commandID) {
+        return codeAction;
+      }
+    }
+    return null;
+  }
+
+  CodeAction findEditAction(List<Either2<Command, CodeAction>> actions,
+      CodeActionKind actionKind, String title) {
+    for (var codeAction in actions) {
+      final codeActionLiteral =
+          codeAction.map((cmd) => null, (action) => action);
+      if (codeActionLiteral?.kind == actionKind &&
+          codeActionLiteral?.title == title) {
+        // We're specifically looking for an action that contains an edit.
+        assert(codeActionLiteral.command == null);
+        assert(codeActionLiteral.edit != null);
+        return codeActionLiteral;
+      }
+    }
+    return null;
+  }
+}
+
 @reflectiveTest
-class OrganizeImportsSourceCodeActionsTest extends SourceCodeActionsTest {
+class OrganizeImportsSourceCodeActionsTest extends AbstractCodeActionsTest {
   test_appliesCorrectEdits_withDocumentChangesSupport() async {
     const content = '''
 import 'dart:math';
@@ -37,10 +98,12 @@
 int minified(int x, int y) => min(x, y);
     ''';
     await newFile(mainFilePath, content: content);
-    await initializeWithDocumentChangesSupport();
+    await initialize(
+        workspaceCapabilities:
+            withDocumentChangesSupport(emptyWorkspaceClientCapabilities));
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.organizeImports);
+    final codeAction = findCommand(codeActions, Commands.organizeImports);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -97,7 +160,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.organizeImports);
+    final codeAction = findCommand(codeActions, Commands.organizeImports);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -136,7 +199,10 @@
 
   test_availableAsCodeActionLiteral() async {
     await newFile(mainFilePath);
-    await initializeWithSupportForKinds([CodeActionKind.Source]);
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Source]),
+    );
 
     await checkCodeActionAvailable(
       mainFileUri,
@@ -164,7 +230,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.organizeImports);
+    final codeAction = findCommand(codeActions, Commands.organizeImports);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -190,7 +256,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.organizeImports);
+    final codeAction = findCommand(codeActions, Commands.organizeImports);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -207,16 +273,19 @@
 
   test_unavailableWhenNotRequested() async {
     await newFile(mainFilePath);
-    await initializeWithSupportForKinds([CodeActionKind.Refactor]);
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
+    );
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.organizeImports);
+    final codeAction = findCommand(codeActions, Commands.organizeImports);
     expect(codeAction, isNull);
   }
 }
 
 @reflectiveTest
-class SortMembersSourceCodeActionsTest extends SourceCodeActionsTest {
+class SortMembersSourceCodeActionsTest extends AbstractCodeActionsTest {
   test_appliesCorrectEdits_withDocumentChangesSupport() async {
     const content = '''
     String b;
@@ -227,10 +296,12 @@
     String b;
     ''';
     await newFile(mainFilePath, content: content);
-    await initializeWithDocumentChangesSupport();
+    await initialize(
+        workspaceCapabilities:
+            withDocumentChangesSupport(emptyWorkspaceClientCapabilities));
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.sortMembers);
+    final codeAction = findCommand(codeActions, Commands.sortMembers);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -280,7 +351,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.sortMembers);
+    final codeAction = findCommand(codeActions, Commands.sortMembers);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -319,7 +390,10 @@
 
   test_availableAsCodeActionLiteral() async {
     await newFile(mainFilePath);
-    await initializeWithSupportForKinds([CodeActionKind.Source]);
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Source]),
+    );
 
     await checkCodeActionAvailable(
       mainFileUri,
@@ -350,7 +424,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.sortMembers);
+    final codeAction = findCommand(codeActions, Commands.sortMembers);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -380,7 +454,7 @@
     await initialize();
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.sortMembers);
+    final codeAction = findCommand(codeActions, Commands.sortMembers);
     expect(codeAction, isNotNull);
 
     final command = codeAction.map(
@@ -396,63 +470,13 @@
 
   test_unavailableWhenNotRequested() async {
     await newFile(mainFilePath);
-    await initializeWithSupportForKinds([CodeActionKind.Refactor]);
+    await initialize(
+      textDocumentCapabilities: withCodeActionKinds(
+          emptyTextDocumentClientCapabilities, [CodeActionKind.Refactor]),
+    );
 
     final codeActions = await getCodeActions(mainFileUri.toString());
-    final codeAction = _findCommand(codeActions, Commands.sortMembers);
+    final codeAction = findCommand(codeActions, Commands.sortMembers);
     expect(codeAction, isNull);
   }
 }
-
-abstract class SourceCodeActionsTest extends AbstractCodeActionsTest {
-  Future<void> checkCodeActionAvailable(
-    Uri uri,
-    String command,
-    String title, {
-    bool asCodeActionLiteral = false,
-    bool asCommand = false,
-  }) async {
-    final codeActions = await getCodeActions(uri.toString());
-    final codeAction = _findCommand(codeActions, command);
-    expect(codeAction, isNotNull);
-
-    codeAction.map(
-      (command) {
-        if (!asCommand) {
-          throw 'Got Command but expected CodeAction literal';
-        }
-        expect(command.title, equals(title));
-        expect(command.arguments, equals([uri.toFilePath()]));
-      },
-      (codeAction) {
-        if (!asCodeActionLiteral) {
-          throw 'Got CodeAction literal but expected Command';
-        }
-        expect(codeAction, isNotNull);
-        expect(codeAction.title, equals(title));
-        expect(codeAction.command.title, equals(title));
-        expect(codeAction.command.arguments, equals([uri.toFilePath()]));
-      },
-    );
-  }
-
-  Future<void> initializeWithDocumentChangesSupport() async {
-    await initialize(workspaceCapabilities: {
-      'workspaceEdit': {
-        'documentChanges': true,
-      },
-    });
-  }
-
-  Either2<Command, CodeAction> _findCommand(
-      List<Either2<Command, CodeAction>> actions, String commandID) {
-    for (var codeAction in actions) {
-      final id = codeAction.map(
-          (cmd) => cmd.command, (action) => action.command.command);
-      if (id == commandID) {
-        return codeAction;
-      }
-    }
-    return null;
-  }
-}
diff --git a/pkg/analysis_server/test/lsp/completion_test.dart b/pkg/analysis_server/test/lsp/completion_test.dart
index 2d29edb..a58127e 100644
--- a/pkg/analysis_server/test/lsp/completion_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_test.dart
@@ -40,17 +40,16 @@
     final content = "import '^';";
 
     // Tell the server we support some specific CompletionItemKinds.
-    await initialize(textDocumentCapabilities: {
-      'completion': {
-        'completionItemKind': {
-          'valueSet': [
-            CompletionItemKind.File.toJson(),
-            CompletionItemKind.Folder.toJson(),
-            CompletionItemKind.Module.toJson(),
-          ],
-        }
-      },
-    });
+    await initialize(
+      textDocumentCapabilities: withCompletionItemKinds(
+        emptyTextDocumentClientCapabilities,
+        [
+          CompletionItemKind.File,
+          CompletionItemKind.Folder,
+          CompletionItemKind.Module,
+        ],
+      ),
+    );
     await openFile(mainFileUri, withoutMarkers(content));
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
 
@@ -75,15 +74,10 @@
     ''';
 
     // Tell the server we only support the Field CompletionItemKind.
-    await initialize(textDocumentCapabilities: {
-      'completion': {
-        'completionItemKind': {
-          'valueSet': [
-            CompletionItemKind.Field.toJson(),
-          ],
-        }
-      },
-    });
+    await initialize(
+      textDocumentCapabilities: withCompletionItemKinds(
+          emptyTextDocumentClientCapabilities, [CompletionItemKind.Field]),
+    );
     await openFile(mainFileUri, withoutMarkers(content));
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
     final kinds = res.map((item) => item.kind).toList();
@@ -171,13 +165,9 @@
     }
     ''';
 
-    await initialize(textDocumentCapabilities: {
-      'completion': {
-        'completionItem': {
-          'deprecatedSupport': true,
-        }
-      },
-    });
+    await initialize(
+        textDocumentCapabilities: withCompletionItemDeprecatedSupport(
+            emptyTextDocumentClientCapabilities));
     await openFile(mainFileUri, withoutMarkers(content));
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
     final item = res.singleWhere((c) => c.label == 'abcdefghij');
@@ -223,13 +213,9 @@
     }
     ''';
 
-    await initialize(textDocumentCapabilities: {
-      'completion': {
-        'completionItem': {
-          'snippetSupport': true,
-        }
-      }
-    });
+    await initialize(
+        textDocumentCapabilities: withCompletionItemSnippetSupport(
+            emptyTextDocumentClientCapabilities));
     await openFile(mainFileUri, withoutMarkers(content));
     final res = await getCompletion(mainFileUri, positionFromMarker(content));
     expect(res.any((c) => c.label == 'abcdefghij'), isTrue);
diff --git a/pkg/analysis_server/test/lsp/document_symbols_test.dart b/pkg/analysis_server/test/lsp/document_symbols_test.dart
index 752a160..b29ec7e 100644
--- a/pkg/analysis_server/test/lsp/document_symbols_test.dart
+++ b/pkg/analysis_server/test/lsp/document_symbols_test.dart
@@ -26,11 +26,9 @@
     }
     ''';
     newFile(mainFilePath, content: content);
-    await initialize(textDocumentCapabilities: {
-      'documentSymbol': {
-        'hierarchicalDocumentSymbolSupport': true,
-      },
-    });
+    await initialize(
+        textDocumentCapabilities: withHierarchicalDocumentSymbolSupport(
+            emptyTextDocumentClientCapabilities));
 
     final result = await getDocumentSymbols(mainFileUri.toString());
     final symbols = result.map(
diff --git a/pkg/analysis_server/test/lsp/hover_test.dart b/pkg/analysis_server/test/lsp/hover_test.dart
index 080a499..1625425 100644
--- a/pkg/analysis_server/test/lsp/hover_test.dart
+++ b/pkg/analysis_server/test/lsp/hover_test.dart
@@ -18,14 +18,6 @@
 
 @reflectiveTest
 class HoverTest extends AbstractLspAnalysisServerTest {
-  Future<void> initializeSupportingMarkupContent([
-    List<MarkupKind> formats = const [MarkupKind.Markdown],
-  ]) async {
-    await initialize(textDocumentCapabilities: {
-      'hover': {'contentFormat': formats.map((f) => f.toJson())}
-    });
-  }
-
   test_hover_bad_position() async {
     await initialize();
     await openFile(mainFileUri, '');
@@ -66,7 +58,9 @@
     '''
         .trim();
 
-    await initializeSupportingMarkupContent([MarkupKind.Markdown]);
+    await initialize(
+        textDocumentCapabilities: withHoverContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     final hover = await getHover(mainFileUri, positionFromMarker(content));
     expect(hover, isNotNull);
@@ -83,7 +77,9 @@
     String [[a^bc]];
     ''';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withHoverContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     final hover = await getHover(mainFileUri, positionFromMarker(content));
     expect(hover, isNotNull);
@@ -103,7 +99,9 @@
     int a;
     ''';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withHoverContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     var hover = await getHover(mainFileUri, positionFromMarker(content));
     expect(hover, isNull);
@@ -115,7 +113,9 @@
     String [[a^bc]];
     ''';
 
-    await initializeSupportingMarkupContent([MarkupKind.PlainText]);
+    await initialize(
+        textDocumentCapabilities: withHoverContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.PlainText]));
     await openFile(mainFileUri, withoutMarkers(content));
     final hover = await getHover(mainFileUri, positionFromMarker(content));
     expect(hover, isNotNull);
diff --git a/pkg/analysis_server/test/lsp/server_abstract.dart b/pkg/analysis_server/test/lsp/server_abstract.dart
index 9d14506..5c7c905 100644
--- a/pkg/analysis_server/test/lsp/server_abstract.dart
+++ b/pkg/analysis_server/test/lsp/server_abstract.dart
@@ -27,7 +27,8 @@
 
 final beginningOfDocument = new Range(new Position(0, 0), new Position(0, 0));
 
-abstract class AbstractLspAnalysisServerTest with ResourceProviderMixin {
+abstract class AbstractLspAnalysisServerTest
+    with ResourceProviderMixin, ClientCapabilitiesHelperMixin {
   static const positionMarker = '^';
   static const rangeMarkerStart = '[[';
   static const rangeMarkerEnd = ']]';
@@ -42,31 +43,15 @@
   String projectFolderPath, mainFilePath;
   Uri mainFileUri;
 
-  final emptyTextDocumentClientCapabilities =
-      new TextDocumentClientCapabilities(
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null,
-          null);
-
-  final emptyWorkspaceClientCapabilities = new WorkspaceClientCapabilities(
-      null, null, null, null, null, null, null, null);
+  void applyChanges(
+    Map<String, String> fileContents,
+    Map<String, List<TextEdit>> changes,
+  ) {
+    changes.forEach((fileUri, edits) {
+      final path = Uri.parse(fileUri).toFilePath();
+      fileContents[path] = applyTextEdits(fileContents[path], edits);
+    });
+  }
 
   void applyDocumentChanges(
       Map<String, String> fileContents,
@@ -82,16 +67,6 @@
     );
   }
 
-  void applyChanges(
-    Map<String, String> fileContents,
-    Map<String, List<TextEdit>> changes,
-  ) {
-    changes.forEach((fileUri, edits) {
-      final path = Uri.parse(fileUri).toFilePath();
-      fileContents[path] = applyTextEdits(fileContents[path], edits);
-    });
-  }
-
   void applyResourceChanges(
     Map<String, String> oldFileContent,
     List<Either4<TextDocumentEdit, CreateFile, RenameFile, DeleteFile>> changes,
@@ -387,14 +362,10 @@
   /// match the spec exactly and are not verified.
   Future<ResponseMessage> initialize({
     String rootPath,
-    Map<String, dynamic> textDocumentCapabilities,
-    Map<String, dynamic> workspaceCapabilities,
+    TextDocumentClientCapabilities textDocumentCapabilities,
+    WorkspaceClientCapabilities workspaceCapabilities,
   }) async {
     final rootUri = Uri.file(rootPath ?? projectFolderPath).toString();
-    final newTextDocumentCapabilities =
-        overrideTextDocumentCapabilities(textDocumentCapabilities);
-    final newWorkspaceCapabilities =
-        overrideWorkspaceCapabilities(workspaceCapabilities);
     final request = makeRequest(
         Method.initialize,
         new InitializeParams(
@@ -403,8 +374,8 @@
             rootUri,
             null,
             new ClientCapabilities(
-              newWorkspaceCapabilities,
-              newTextDocumentCapabilities,
+              workspaceCapabilities,
+              textDocumentCapabilities,
               null,
             ),
             null,
@@ -439,28 +410,6 @@
     await pumpEventQueue();
   }
 
-  TextDocumentClientCapabilities overrideTextDocumentCapabilities(
-      Map<String, dynamic> textDocumentCapabilities) {
-    final json = emptyTextDocumentClientCapabilities.toJson();
-    if (textDocumentCapabilities != null) {
-      textDocumentCapabilities.keys.forEach((key) {
-        json[key] = textDocumentCapabilities[key];
-      });
-    }
-    return TextDocumentClientCapabilities.fromJson(json);
-  }
-
-  WorkspaceClientCapabilities overrideWorkspaceCapabilities(
-      Map<String, dynamic> workspaceCapabilities) {
-    final json = emptyWorkspaceClientCapabilities.toJson();
-    if (workspaceCapabilities != null) {
-      workspaceCapabilities.keys.forEach((key) {
-        json[key] = workspaceCapabilities[key];
-      });
-    }
-    return WorkspaceClientCapabilities.fromJson(json);
-  }
-
   Position positionFromMarker(String contents) =>
       positionFromOffset(contents.indexOf('^'), contents);
 
@@ -548,3 +497,141 @@
   String withoutMarkers(String contents) =>
       contents.replaceAll(allMarkersPattern, '');
 }
+
+mixin ClientCapabilitiesHelperMixin {
+  final emptyTextDocumentClientCapabilities =
+      new TextDocumentClientCapabilities(
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null,
+          null);
+
+  final emptyWorkspaceClientCapabilities = new WorkspaceClientCapabilities(
+      null, null, null, null, null, null, null, null);
+
+  TextDocumentClientCapabilities extendTextDocumentCapabilities(
+    TextDocumentClientCapabilities source,
+    Map<String, dynamic> textDocumentCapabilities,
+  ) {
+    final json = source.toJson();
+    if (textDocumentCapabilities != null) {
+      textDocumentCapabilities.keys.forEach((key) {
+        json[key] = textDocumentCapabilities[key];
+      });
+    }
+    return TextDocumentClientCapabilities.fromJson(json);
+  }
+
+  WorkspaceClientCapabilities extendWorkspaceCapabilities(
+    WorkspaceClientCapabilities source,
+    Map<String, dynamic> workspaceCapabilities,
+  ) {
+    final json = source.toJson();
+    if (workspaceCapabilities != null) {
+      workspaceCapabilities.keys.forEach((key) {
+        json[key] = workspaceCapabilities[key];
+      });
+    }
+    return WorkspaceClientCapabilities.fromJson(json);
+  }
+
+  TextDocumentClientCapabilities withCodeActionKinds(
+    TextDocumentClientCapabilities source,
+    List<CodeActionKind> kinds,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'codeAction': {
+        'codeActionLiteralSupport': {
+          'codeActionKind': {'valueSet': kinds.map((k) => k.toJson()).toList()}
+        }
+      }
+    });
+  }
+
+  TextDocumentClientCapabilities withCompletionItemDeprecatedSupport(
+    TextDocumentClientCapabilities source,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'completion': {
+        'completionItem': {'deprecatedSupport': true}
+      }
+    });
+  }
+
+  TextDocumentClientCapabilities withCompletionItemSnippetSupport(
+    TextDocumentClientCapabilities source,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'completion': {
+        'completionItem': {'snippetSupport': true}
+      }
+    });
+  }
+
+  TextDocumentClientCapabilities withCompletionItemKinds(
+    TextDocumentClientCapabilities source,
+    List<CompletionItemKind> kinds,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'completion': {
+        'completionItemKind': {
+          'valueSet': kinds.map((k) => k.toJson()).toList()
+        }
+      }
+    });
+  }
+
+  TextDocumentClientCapabilities withHoverContentFormat(
+    TextDocumentClientCapabilities source,
+    List<MarkupKind> formats,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'hover': {'contentFormat': formats.map((k) => k.toJson()).toList()}
+    });
+  }
+
+  TextDocumentClientCapabilities withSignatureHelpContentFormat(
+    TextDocumentClientCapabilities source,
+    List<MarkupKind> formats,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'signatureHelp': {
+        'signatureInformation': {
+          'documentationFormat': formats.map((k) => k.toJson()).toList()
+        }
+      }
+    });
+  }
+
+  TextDocumentClientCapabilities withHierarchicalDocumentSymbolSupport(
+    TextDocumentClientCapabilities source,
+  ) {
+    return extendTextDocumentCapabilities(source, {
+      'documentSymbol': {'hierarchicalDocumentSymbolSupport': true}
+    });
+  }
+
+  WorkspaceClientCapabilities withDocumentChangesSupport(
+    WorkspaceClientCapabilities source,
+  ) {
+    return extendWorkspaceCapabilities(source, {
+      'workspaceEdit': {'documentChanges': true}
+    });
+  }
+}
diff --git a/pkg/analysis_server/test/lsp/signature_help_test.dart b/pkg/analysis_server/test/lsp/signature_help_test.dart
index b72233d..ae051ff 100644
--- a/pkg/analysis_server/test/lsp/signature_help_test.dart
+++ b/pkg/analysis_server/test/lsp/signature_help_test.dart
@@ -16,18 +16,6 @@
 
 @reflectiveTest
 class SignatureHelpTest extends AbstractLspAnalysisServerTest {
-  Future<void> initializeSupportingMarkupContent([
-    List<MarkupKind> formats = const [MarkupKind.Markdown],
-  ]) async {
-    await initialize(textDocumentCapabilities: {
-      'signatureHelp': {
-        'signatureInformation': {
-          'documentationFormat': formats.map((f) => f.toJson())
-        }
-      }
-    });
-  }
-
   test_formats_markdown() async {
     final content = '''
     /// Does foo.
@@ -38,7 +26,9 @@
     final expectedLabel = 'foo(String s, int i)';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent([MarkupKind.Markdown]);
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -86,7 +76,9 @@
     final expectedLabel = 'foo(String s, int i)';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent([MarkupKind.PlainText]);
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.PlainText]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -113,8 +105,10 @@
     // We say we prefer PlainText as a client, but since we only really
     // support Markdown and the client supports it, we expect the server
     // to provide Markdown.
-    await initializeSupportingMarkupContent(
-        [MarkupKind.PlainText, MarkupKind.Markdown]);
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities,
+            [MarkupKind.PlainText, MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -139,7 +133,9 @@
     final expectedLabel = 'foo(String s, {bool b = true, bool a})';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -164,7 +160,9 @@
     final expectedLabel = 'foo(String s, [bool b = true, bool a])';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -189,7 +187,9 @@
     final expectedLabel = 'foo(String s, {bool b = true})';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -213,7 +213,9 @@
     final expectedLabel = 'foo(String s, [bool b = true])';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -236,7 +238,9 @@
     final expectedLabel = 'foo(String s, int i)';
     final expectedDoc = 'Does foo.';
 
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await openFile(mainFileUri, withoutMarkers(content));
     await testSignature(
       content,
@@ -260,7 +264,9 @@
     final expectedDoc = 'Does foo.';
 
     newFile(mainFilePath, content: withoutMarkers(content));
-    await initializeSupportingMarkupContent();
+    await initialize(
+        textDocumentCapabilities: withSignatureHelpContentFormat(
+            emptyTextDocumentClientCapabilities, [MarkupKind.Markdown]));
     await testSignature(
       content,
       expectedLabel,