blob: ebdb22bbd54256364526b531df8e4fcc5b4defad [file] [log] [blame]
// 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<void> test_documentChange_notifiesPlugins() async {
await _initializeAndOpen();
await changeFile(2, mainFileUri, [
Either2<TextDocumentContentChangeEvent1,
TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
range: Range(
start: Position(line: 0, character: 6),
end: Position(line: 0, character: 9)),
text: 'Bar',
)),
Either2<TextDocumentContentChangeEvent1,
TextDocumentContentChangeEvent2>.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<void> test_documentChange_updatesOverlay() async {
await _initializeAndOpen();
await changeFile(2, mainFileUri, [
Either2<TextDocumentContentChangeEvent1,
TextDocumentContentChangeEvent2>.t1(TextDocumentContentChangeEvent1(
range: Range(
start: Position(line: 0, character: 6),
end: Position(line: 0, character: 9)),
text: 'Bar',
)),
Either2<TextDocumentContentChangeEvent1,
TextDocumentContentChangeEvent2>.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<void> test_documentClose_deletesOverlay() async {
await _initializeAndOpen();
await closeFile(mainFileUri);
expect(server.resourceProvider.hasOverlay(mainFilePath), isFalse);
}
Future<void> test_documentClose_notifiesPlugins() async {
await _initializeAndOpen();
await closeFile(mainFileUri);
expect(pluginManager.analysisUpdateContentParams!.files,
equals({mainFilePath: RemoveContentOverlay()}));
}
Future<void>
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<void> test_documentOpen_createsOverlay() async {
await _initializeAndOpen();
expect(server.resourceProvider.hasOverlay(mainFilePath), isTrue);
expect(server.resourceProvider.getFile(mainFilePath).readAsStringSync(),
equals(content));
}
Future<void> test_documentOpen_notifiesPlugins() async {
await _initializeAndOpen();
expect(pluginManager.analysisUpdateContentParams!.files,
equals({mainFilePath: AddContentOverlay(content)}));
}
Future<void> 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<void>();
// 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<void> _initializeAndOpen() async {
await initialize();
await openFile(mainFileUri, content);
}
}