| // Copyright (c) 2025, 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 'dart:async'; | |
| import 'package:analysis_server/lsp_protocol/protocol.dart'; | |
| import 'package:analysis_server/src/lsp/constants.dart'; | |
| import 'package:analyzer/src/test_utilities/test_code_format.dart'; | |
| import 'package:test/test.dart'; | |
| import '../lsp/request_helpers_mixin.dart'; | |
| import '../lsp/server_abstract.dart'; | |
| import '../tool/lsp_spec/matchers.dart'; | |
| import '../utils/test_code_extensions.dart'; | |
| import 'shared_test_interface.dart'; | |
| /// Shared tests used by both LSP + Legacy server tests and/or integration. | |
| mixin SharedApplyCodeActionTests | |
| on | |
| SharedTestInterface, | |
| LspRequestHelpersMixin, | |
| LspEditHelpersMixin, | |
| LspVerifyEditHelpersMixin, | |
| ClientCapabilitiesHelperMixin { | |
| /// Verifies a command execution was logged to analytics. | |
| /// | |
| /// Implementations are provided by the in-process test base classes. This | |
| /// method will be a no-op for out-of-process tests because the analytics | |
| /// manager will not be accessible. | |
| void expectCommandLogged(String command); | |
| Future<void> test_bad_actionNotFoundAtLocation() async { | |
| var code = TestCode.parse(''' | |
| var a = [!!]1; | |
| '''); | |
| await initializeServer(); | |
| await openFile(testFileUri, code.code); | |
| var command = Command( | |
| title: 'unused', | |
| command: Commands.applyCodeAction, | |
| arguments: [ | |
| { | |
| 'textDocument': {'uri': testFileUri.toString(), 'version': 1}, | |
| 'range': code.range.range.toJson(), | |
| 'kind': 'refactor.convert.toDoubleQuotedString', | |
| 'loggedAction': 'dart.assist.convert.toDoubleQuotedString', | |
| }, | |
| ], | |
| ); | |
| await expectLater( | |
| executeCommand(command), | |
| throwsA( | |
| isResponseError( | |
| ServerErrorCodes.invalidCommandArguments, | |
| message: | |
| 'The code action refactor.convert.toDoubleQuotedString ' | |
| 'is not valid at this location', | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<void> test_bad_editorDoesNotSupportApplyEdit() async { | |
| setApplyEditSupport(false); | |
| await initializeServer(); | |
| var command = Command( | |
| title: 'unused', | |
| command: Commands.applyCodeAction, | |
| arguments: [ | |
| {'textDocument': true}, | |
| ], | |
| ); | |
| await expectLater( | |
| executeCommand(command), | |
| throwsA( | |
| isResponseError( | |
| ServerErrorCodes.stateError, | |
| message: 'The editor does not support workspace/applyEdit', | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<void> test_bad_invalidParams() async { | |
| await initializeServer(); | |
| var command = Command( | |
| title: 'unused', | |
| command: Commands.applyCodeAction, | |
| arguments: [ | |
| {'textDocument': true}, | |
| ], | |
| ); | |
| await expectLater( | |
| executeCommand(command), | |
| throwsA( | |
| isResponseError( | |
| ServerErrorCodes.invalidCommandArguments, | |
| message: | |
| 'dart.edit.codeAction.apply requires 3 parameters: ' | |
| 'textDocument: Map<String, Object?> (OptionalVersionedTextDocumentIdentifier), ' | |
| 'range: Map<String, Object?> (Range), ' | |
| 'kind: String (CodeActionKind) ' | |
| 'but ' | |
| 'textDocument was not a valid OptionalVersionedTextDocumentIdentifier, ' | |
| 'range was not a valid Range, ' | |
| 'kind was not a valid String', | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<void> test_bad_wrongDocumentVersion() async { | |
| var code = TestCode.parse(''' | |
| var a = [!!]1; | |
| '''); | |
| await initializeServer(); | |
| await openFile(testFileUri, code.code, version: 111); | |
| var command = Command( | |
| title: 'unused', | |
| command: Commands.applyCodeAction, | |
| arguments: [ | |
| { | |
| 'textDocument': { | |
| 'uri': testFileUri.toString(), | |
| // Older document version (we set 111 above) | |
| 'version': 2, | |
| }, | |
| 'range': code.range.range.toJson(), | |
| 'kind': 'refactor.convert.toDoubleQuotedString', | |
| 'loggedAction': 'dart.assist.convert.toDoubleQuotedString', | |
| }, | |
| ], | |
| ); | |
| await expectLater( | |
| executeCommand(command), | |
| throwsA( | |
| isResponseError( | |
| ErrorCodes.ContentModified, | |
| message: 'Document was modified before operation completed', | |
| ), | |
| ), | |
| ); | |
| } | |
| Future<void> test_good() async { | |
| var code = TestCode.parseNormalized(''' | |
| var a = [!!]''; | |
| '''); | |
| await initializeServer(); | |
| await openFile(testFileUri, code.code); | |
| var command = Command( | |
| title: 'unused', | |
| command: Commands.applyCodeAction, | |
| arguments: [ | |
| { | |
| 'textDocument': {'uri': testFileUri.toString(), 'version': 1}, | |
| 'range': code.range.range.toJson(), | |
| 'kind': 'refactor.convert.toDoubleQuotedString', | |
| 'loggedAction': 'dart.assist.convert.toDoubleQuotedString', | |
| }, | |
| ], | |
| ); | |
| // Verify that executing the command produces the correct edits (which will | |
| // come back via `workspace/applyEdit`). | |
| await verifyCommandEdits(command, r''' | |
| >>>>>>>>>> lib/test.dart | |
| var a = ""; | |
| '''); | |
| expectCommandLogged(Commands.applyCodeAction); | |
| expectCommandLogged('dart.assist.convert.toDoubleQuotedString'); | |
| } | |
| } |