// Copyright (c) 2019, 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_generated.dart'; import 'package:analysis_server/lsp_protocol/protocol_special.dart'; import 'package:analysis_server/src/protocol/protocol_internal.dart'; import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Position; import 'package:test/test.dart'; import 'package:test_reflective_loader/test_reflective_loader.dart'; import 'server_abstract.dart'; void main() { defineReflectiveSuite(() { defineReflectiveTests(DocumentChangesTest); }); } @reflectiveTest class DocumentChangesTest extends AbstractLspAnalysisServerTest { String get content => ''' class Foo { String get bar => 'baz'; } '''; String get contentAfterUpdate => ''' class Bar { String get bar => 'updated'; } '''; Future test_documentChange_notifiesPlugins() async { await _initializeAndOpen(); await changeFile(2, mainFileUri, [ Either2.t1(TextDocumentContentChangeEvent1( range: Range( start: Position(line: 0, character: 6), end: Position(line: 0, character: 9)), text: 'Bar', )), Either2.t1(TextDocumentContentChangeEvent1( range: Range( start: Position(line: 1, character: 21), end: Position(line: 1, character: 24)), text: 'updated', )), ]); final notifiedChanges = pluginManager.analysisUpdateContentParams! .files[mainFilePath] as ChangeContentOverlay; expect( applySequenceOfEdits(content, notifiedChanges.edits), contentAfterUpdate, ); } Future test_documentChange_updatesOverlay() async { await _initializeAndOpen(); await changeFile(2, mainFileUri, [ Either2.t1(TextDocumentContentChangeEvent1( range: Range( start: Position(line: 0, character: 6), end: Position(line: 0, character: 9)), text: 'Bar', )), Either2.t1(TextDocumentContentChangeEvent1( range: Range( start: Position(line: 1, character: 21), end: Position(line: 1, character: 24)), text: 'updated', )), ]); expect(server.resourceProvider.hasOverlay(mainFilePath), isTrue); expect(server.resourceProvider.getFile(mainFilePath).readAsStringSync(), equals(contentAfterUpdate)); } Future test_documentClose_deletesOverlay() async { await _initializeAndOpen(); await closeFile(mainFileUri); expect(server.resourceProvider.hasOverlay(mainFilePath), isFalse); } Future test_documentClose_notifiesPlugins() async { await _initializeAndOpen(); await closeFile(mainFileUri); expect(pluginManager.analysisUpdateContentParams!.files, equals({mainFilePath: RemoveContentOverlay()})); } Future test_documentOpen_addsOverlayOnlyToDriver_onlyIfInsideRoots() async { // Ensures that opening a file doesn't add it to the driver if it's outside // of the drivers root. final fileInsideRootPath = mainFilePath; final fileOutsideRootPath = convertPath('/home/unrelated/main.dart'); await initialize(); await openFile(Uri.file(fileInsideRootPath), content); await openFile(Uri.file(fileOutsideRootPath), content); // Expect both files return the same driver final driverForInside = server.getAnalysisDriver(fileInsideRootPath)!; final driverForOutside = server.getAnalysisDriver(fileOutsideRootPath)!; expect(driverForInside, equals(driverForOutside)); // But that only the file inside the root was added. expect(driverForInside.addedFiles, contains(fileInsideRootPath)); expect(driverForInside.addedFiles, isNot(contains(fileOutsideRootPath))); } Future test_documentOpen_createsOverlay() async { await _initializeAndOpen(); expect(server.resourceProvider.hasOverlay(mainFilePath), isTrue); expect(server.resourceProvider.getFile(mainFilePath).readAsStringSync(), equals(content)); } Future test_documentOpen_notifiesPlugins() async { await _initializeAndOpen(); expect(pluginManager.analysisUpdateContentParams!.files, equals({mainFilePath: AddContentOverlay(content)})); } Future test_documentOpen_setsPriorityFileIfEarly() async { // When initializing with config support, the server will call back to the client // which can delay analysis roots being configured. This can result in files // being opened before analysis roots are set which has previously caused the // files not to be marked as priority on the created drivers. // https://github.com/Dart-Code/Dart-Code/issues/2438 // https://github.com/dart-lang/sdk/issues/42994 // Initialize the server, but delay providing the configuration until after // we've opened the file. final completer = Completer(); // Send the initialize request but do not await it. final initResponse = initialize( workspaceCapabilities: withDidChangeConfigurationDynamicRegistration( withConfigurationSupport(emptyWorkspaceClientCapabilities))); // When asked for config, delay the response until we have sent the openFile notification. final config = provideConfig( () => initResponse, completer.future.then((_) => {'dart.foo': false}), ); // Wait for initialization to finish, open the file, then allow config to complete. await initResponse; await openFile(mainFileUri, content); completer.complete(); await config; await pumpEventQueue(times: 5000); // Ensure the opened file is in the priority list. expect(server.getAnalysisDriver(mainFilePath)!.priorityFiles, equals([mainFilePath])); } Future _initializeAndOpen() async { await initialize(); await openFile(mainFileUri, content); } }