| // Copyright (c) 2014, 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. |
| |
| library test.domain.completion; |
| |
| import 'dart:async'; |
| |
| import 'package:analysis_server/plugin/protocol/protocol.dart'; |
| import 'package:analysis_server/src/analysis_server.dart'; |
| import 'package:analysis_server/src/channel/channel.dart'; |
| import 'package:analysis_server/src/constants.dart'; |
| import 'package:analysis_server/src/context_manager.dart'; |
| import 'package:analysis_server/src/domain_analysis.dart'; |
| import 'package:analysis_server/src/domain_completion.dart'; |
| import 'package:analysis_server/src/plugin/server_plugin.dart'; |
| import 'package:analysis_server/src/provisional/completion/completion_core.dart' |
| show CompletionRequest, CompletionResult; |
| import 'package:analysis_server/src/provisional/completion/completion_dart.dart' |
| as newApi; |
| import 'package:analysis_server/src/services/completion/completion_manager.dart'; |
| import 'package:analysis_server/src/services/completion/contribution_sorter.dart'; |
| import 'package:analysis_server/src/services/completion/dart_completion_manager.dart'; |
| import 'package:analysis_server/src/services/index/index.dart' show Index; |
| import 'package:analysis_server/src/services/index/local_memory_index.dart'; |
| import 'package:analysis_server/src/services/search/search_engine.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/instrumentation/instrumentation.dart'; |
| import 'package:analyzer/source/pub_package_map_provider.dart'; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/sdk.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:plugin/manager.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| import 'package:unittest/unittest.dart'; |
| |
| import 'analysis_abstract.dart'; |
| import 'mock_sdk.dart'; |
| import 'mocks.dart'; |
| import 'utils.dart'; |
| |
| main() { |
| initializeTestEnvironment(); |
| defineReflectiveTests(CompletionManagerTest); |
| defineReflectiveTests(CompletionTest); |
| defineReflectiveTests(_NoSearchEngine); |
| } |
| |
| @reflectiveTest |
| class CompletionManagerTest extends AbstractAnalysisTest { |
| AnalysisDomainHandler analysisDomain; |
| Test_CompletionDomainHandler completionDomain; |
| Request request; |
| int requestCount = 0; |
| String testFile2 = '/project/bin/test2.dart'; |
| |
| /// Cached model state in case tests need to set task model to on/off. |
| bool wasTaskModelEnabled; |
| |
| AnalysisServer createAnalysisServer(Index index) { |
| ExtensionManager manager = new ExtensionManager(); |
| ServerPlugin serverPlugin = new ServerPlugin(); |
| manager.processPlugins([serverPlugin]); |
| return new Test_AnalysisServer( |
| super.serverChannel, |
| super.resourceProvider, |
| super.packageMapProvider, |
| index, |
| serverPlugin, |
| new AnalysisServerOptions(), |
| new MockSdk(), |
| InstrumentationService.NULL_SERVICE); |
| } |
| |
| @override |
| Index createIndex() { |
| return createLocalMemoryIndex(); |
| } |
| |
| void sendRequest(String path) { |
| String id = (++requestCount).toString(); |
| request = new CompletionGetSuggestionsParams(path, 0).toRequest(id); |
| Response response = handler.handleRequest(request); |
| expect(response, isResponseSuccess(id)); |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| wasTaskModelEnabled = AnalysisEngine.instance.useTaskModel; |
| AnalysisEngine.instance.useTaskModel = true; |
| createProject(); |
| analysisDomain = handler; |
| completionDomain = new Test_CompletionDomainHandler(server); |
| handler = completionDomain; |
| addTestFile('^library A; cl'); |
| addFile(testFile2, 'library B; cl'); |
| } |
| |
| void tearDown() { |
| super.tearDown(); |
| analysisDomain = null; |
| completionDomain = null; |
| AnalysisEngine.instance.useTaskModel = wasTaskModelEnabled; |
| } |
| |
| /** |
| * Assert different managers are used for different sources |
| */ |
| test_2_requests_different_sources() { |
| expect(completionDomain.manager, isNull); |
| sendRequest(testFile); |
| expect(completionDomain.manager, isNotNull); |
| MockCompletionManager expectedManager = completionDomain.manager; |
| expect(expectedManager.disposeCallCount, 0); |
| expect(completionDomain.mockContext.mockStream.listenCount, 1); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 0); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, expectedManager); |
| expect(completionDomain.mockManager.computeCallCount, 1); |
| sendRequest(testFile2); |
| expect(completionDomain.manager, isNotNull); |
| expect(completionDomain.manager, isNot(expectedManager)); |
| expect(expectedManager.disposeCallCount, 1); |
| expectedManager = completionDomain.manager; |
| expect(completionDomain.mockContext.mockStream.listenCount, 2); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 1); |
| return pumpEventQueue(); |
| }).then((_) { |
| expect(completionDomain.manager, expectedManager); |
| expect(completionDomain.mockContext.mockStream.listenCount, 2); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 1); |
| expect(completionDomain.mockManager.computeCallCount, 1); |
| }); |
| } |
| |
| /** |
| * Assert same manager is used for multiple requests on same source |
| */ |
| test_2_requests_same_source() { |
| expect(completionDomain.manager, isNull); |
| sendRequest(testFile); |
| expect(completionDomain.manager, isNotNull); |
| expect(completionDomain.manager.source, isNotNull); |
| CompletionManager expectedManager = completionDomain.manager; |
| expect(completionDomain.mockContext.mockStream.listenCount, 1); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 0); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, expectedManager); |
| expect(completionDomain.mockManager.computeCallCount, 1); |
| sendRequest(testFile); |
| expect(completionDomain.manager, expectedManager); |
| expect(completionDomain.mockContext.mockStream.listenCount, 1); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 0); |
| return pumpEventQueue(); |
| }).then((_) { |
| expect(completionDomain.manager, expectedManager); |
| expect(completionDomain.mockContext.mockStream.listenCount, 1); |
| expect(completionDomain.mockContext.mockStream.cancelCount, 0); |
| expect(completionDomain.mockManager.computeCallCount, 2); |
| }); |
| } |
| |
| /** |
| * Assert manager is NOT cleared when context NOT associated with manager changes. |
| */ |
| test_contextsChanged_different() { |
| sendRequest(testFile); |
| CompletionManager expectedManager; |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| expectedManager = completionDomain.manager; |
| completionDomain.contextsChangedRaw( |
| new ContextsChangedEvent(changed: [new MockContext()])); |
| return pumpEventQueue(); |
| }).then((_) { |
| expect(completionDomain.manager, expectedManager); |
| }); |
| } |
| |
| /** |
| * Assert manager is cleared when context associated with manager changes. |
| */ |
| test_contextsChanged_same() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| completionDomain.contextsChangedRaw( |
| new ContextsChangedEvent(changed: [completionDomain.mockContext])); |
| return pumpEventQueue(); |
| }).then((_) { |
| expect(completionDomain.manager, isNull); |
| }); |
| } |
| |
| /** |
| * Assert manager is cleared when analysis roots are set |
| */ |
| test_setAnalysisRoots() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| request = new AnalysisSetAnalysisRootsParams([], []).toRequest('7'); |
| Response response = analysisDomain.handleRequest(request); |
| expect(response, isResponseSuccess('7')); |
| return pumpEventQueue(); |
| }).then((_) { |
| expect(completionDomain.manager, isNull); |
| }); |
| } |
| |
| /** |
| * Assert manager is cleared when source NOT associated with manager is changed. |
| */ |
| test_sourcesChanged_different_source_changed() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| ContextSourcePair contextSource = server.getContextSourcePair(testFile2); |
| ChangeSet changeSet = new ChangeSet(); |
| changeSet.changedSource(contextSource.source); |
| completionDomain.sourcesChanged(new SourcesChangedEvent(changeSet)); |
| expect(completionDomain.manager, isNull); |
| }); |
| } |
| |
| /** |
| * Assert manager is NOT cleared when source associated with manager is changed. |
| */ |
| test_sourcesChanged_same_source_changed() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| CompletionManager expectedManager = completionDomain.manager; |
| ChangeSet changeSet = new ChangeSet(); |
| changeSet.changedSource(completionDomain.manager.source); |
| completionDomain.sourcesChanged(new SourcesChangedEvent(changeSet)); |
| expect(completionDomain.manager, expectedManager); |
| }); |
| } |
| |
| /** |
| * Assert manager is cleared when source is deleted |
| */ |
| test_sourcesChanged_source_deleted() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| ChangeSet changeSet = new ChangeSet(); |
| changeSet.deletedSource(completionDomain.manager.source); |
| completionDomain.sourcesChanged(new SourcesChangedEvent(changeSet)); |
| expect(completionDomain.manager, isNull); |
| }); |
| } |
| |
| /** |
| * Assert manager is cleared when source is removed |
| */ |
| test_sourcesChanged_source_removed() { |
| sendRequest(testFile); |
| return pumpEventQueue().then((_) { |
| expect(completionDomain.manager, isNotNull); |
| ChangeSet changeSet = new ChangeSet(); |
| changeSet.removedSource(completionDomain.manager.source); |
| completionDomain.sourcesChanged(new SourcesChangedEvent(changeSet)); |
| expect(completionDomain.manager, isNull); |
| }); |
| } |
| } |
| |
| @reflectiveTest |
| class CompletionTest extends AbstractAnalysisTest { |
| String completionId; |
| int completionOffset; |
| int replacementOffset; |
| int replacementLength; |
| List<CompletionSuggestion> suggestions = []; |
| bool suggestionsDone = false; |
| |
| String addTestFile(String content, {int offset}) { |
| completionOffset = content.indexOf('^'); |
| if (offset != null) { |
| expect(completionOffset, -1, reason: 'cannot supply offset and ^'); |
| completionOffset = offset; |
| return super.addTestFile(content); |
| } |
| expect(completionOffset, isNot(equals(-1)), reason: 'missing ^'); |
| int nextOffset = content.indexOf('^', completionOffset + 1); |
| expect(nextOffset, equals(-1), reason: 'too many ^'); |
| return super.addTestFile(content.substring(0, completionOffset) + |
| content.substring(completionOffset + 1)); |
| } |
| |
| void assertHasResult(CompletionSuggestionKind kind, String completion, |
| {int relevance: DART_RELEVANCE_DEFAULT, |
| bool isDeprecated: false, |
| bool isPotential: false, |
| int selectionOffset}) { |
| var cs; |
| suggestions.forEach((s) { |
| if (s.completion == completion) { |
| if (cs == null) { |
| cs = s; |
| } else { |
| fail('expected exactly one $completion but found > 1'); |
| } |
| } |
| }); |
| if (cs == null) { |
| var completions = suggestions.map((s) => s.completion).toList(); |
| fail('expected "$completion" but found\n $completions'); |
| } |
| expect(cs.kind, equals(kind)); |
| expect(cs.relevance, equals(relevance)); |
| expect(cs.selectionOffset, selectionOffset ?? completion.length); |
| expect(cs.selectionLength, equals(0)); |
| expect(cs.isDeprecated, equals(isDeprecated)); |
| expect(cs.isPotential, equals(isPotential)); |
| } |
| |
| void assertNoResult(String completion) { |
| if (suggestions.any((cs) => cs.completion == completion)) { |
| fail('did not expect completion: $completion'); |
| } |
| } |
| |
| void assertValidId(String id) { |
| expect(id, isNotNull); |
| expect(id.isNotEmpty, isTrue); |
| } |
| |
| @override |
| Index createIndex() { |
| return createLocalMemoryIndex(); |
| } |
| |
| Future getSuggestions() { |
| return waitForTasksFinished().then((_) { |
| Request request = new CompletionGetSuggestionsParams( |
| testFile, completionOffset).toRequest('0'); |
| Response response = handleSuccessfulRequest(request); |
| completionId = response.id; |
| assertValidId(completionId); |
| return pumpEventQueue().then((_) { |
| expect(suggestionsDone, isTrue); |
| }); |
| }); |
| } |
| |
| void processNotification(Notification notification) { |
| if (notification.event == COMPLETION_RESULTS) { |
| var params = new CompletionResultsParams.fromNotification(notification); |
| String id = params.id; |
| assertValidId(id); |
| if (id == completionId) { |
| expect(suggestionsDone, isFalse); |
| replacementOffset = params.replacementOffset; |
| replacementLength = params.replacementLength; |
| suggestionsDone = params.isLast; |
| expect(suggestionsDone, isNotNull); |
| suggestions = params.results; |
| } |
| } |
| } |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| createProject(); |
| handler = new CompletionDomainHandler(server); |
| } |
| |
| test_html() { |
| testFile = '/project/web/test.html'; |
| addTestFile(''' |
| <html>^</html> |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| expect(suggestions, hasLength(0)); |
| }); |
| } |
| |
| test_imports() { |
| addTestFile(''' |
| import 'dart:html'; |
| main() {^} |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'HtmlElement'); |
| assertNoResult('test'); |
| }); |
| } |
| |
| test_imports_incremental() async { |
| addTestFile('''library foo; |
| e^ |
| import "dart:async"; |
| import "package:foo/foo.dart"; |
| class foo { }'''); |
| await waitForTasksFinished(); |
| server.updateContent('uc1', {testFile: new AddContentOverlay(testCode)}); |
| server.updateContent('uc2', { |
| testFile: |
| new ChangeContentOverlay([new SourceEdit(completionOffset, 0, 'xp')]) |
| }); |
| completionOffset += 2; |
| await getSuggestions(); |
| expect(replacementOffset, completionOffset - 3); |
| expect(replacementLength, 3); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'export', |
| relevance: DART_RELEVANCE_HIGH); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'import', |
| relevance: DART_RELEVANCE_HIGH); |
| assertNoResult('extends'); |
| assertNoResult('library'); |
| } |
| |
| test_imports_partial() async { |
| addTestFile('''^ |
| import "package:foo/foo.dart"; |
| import "package:bar/bar.dart"; |
| class Baz { }'''); |
| |
| // Wait for analysis then edit the content |
| await waitForTasksFinished(); |
| String revisedContent = testCode.substring(0, completionOffset) + |
| 'i' + |
| testCode.substring(completionOffset); |
| ++completionOffset; |
| server.handleRequest(new AnalysisUpdateContentParams( |
| {testFile: new AddContentOverlay(revisedContent)}).toRequest('add1')); |
| |
| // Request code completion immediately after edit |
| Response response = handleSuccessfulRequest( |
| new CompletionGetSuggestionsParams(testFile, completionOffset) |
| .toRequest('0')); |
| completionId = response.id; |
| assertValidId(completionId); |
| await waitForTasksFinished(); |
| expect(replacementOffset, completionOffset - 1); |
| expect(replacementLength, 1); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'library', |
| relevance: DART_RELEVANCE_HIGH); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'import', |
| relevance: DART_RELEVANCE_HIGH); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'export', |
| relevance: DART_RELEVANCE_HIGH); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'part', |
| relevance: DART_RELEVANCE_HIGH); |
| assertNoResult('extends'); |
| } |
| |
| test_imports_prefixed() { |
| addTestFile(''' |
| import 'dart:html' as foo; |
| main() {^} |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'foo'); |
| assertNoResult('HtmlElement'); |
| assertNoResult('test'); |
| }); |
| } |
| |
| test_invocation() { |
| addTestFile('class A {b() {}} main() {A a; a.^}'); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'b'); |
| }); |
| } |
| |
| test_invocation_sdk_relevancy_off() { |
| var originalSorter = DartCompletionManager.defaultContributionSorter; |
| var mockSorter = new MockRelevancySorter(); |
| DartCompletionManager.defaultContributionSorter = mockSorter; |
| addTestFile('main() {Map m; m.^}'); |
| return getSuggestions().then((_) { |
| // Assert that the CommonUsageComputer has been replaced |
| expect(suggestions.any((s) => s.relevance == DART_RELEVANCE_COMMON_USAGE), |
| isFalse); |
| DartCompletionManager.defaultContributionSorter = originalSorter; |
| mockSorter.enabled = false; |
| }); |
| } |
| |
| test_invocation_sdk_relevancy_on() { |
| addTestFile('main() {Map m; m.^}'); |
| return getSuggestions().then((_) { |
| // Assert that the CommonUsageComputer is working |
| expect(suggestions.any((s) => s.relevance == DART_RELEVANCE_COMMON_USAGE), |
| isTrue); |
| }); |
| } |
| |
| test_invocation_withTrailingStmt() { |
| addTestFile('class A {b() {}} main() {A a; a.^ int x = 7;}'); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'b'); |
| }); |
| } |
| |
| test_keyword() { |
| addTestFile('library A; cl^'); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset - 2)); |
| expect(replacementLength, equals(2)); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'export', |
| relevance: DART_RELEVANCE_HIGH); |
| assertHasResult(CompletionSuggestionKind.KEYWORD, 'class', |
| relevance: DART_RELEVANCE_HIGH); |
| }); |
| } |
| |
| test_local_named_constructor() { |
| addTestFile('class A {A.c(); x() {new A.^}}'); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'c'); |
| assertNoResult('A'); |
| }); |
| } |
| |
| test_locals() { |
| addTestFile('class A {var a; x() {var b;^}} class DateTime { }'); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'A'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'a', |
| relevance: DART_RELEVANCE_LOCAL_FIELD); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'b', |
| relevance: DART_RELEVANCE_LOCAL_VARIABLE); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'x', |
| relevance: DART_RELEVANCE_LOCAL_METHOD); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'DateTime'); |
| }); |
| } |
| |
| test_offset_past_eof() { |
| addTestFile('main() { }', offset: 300); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(300)); |
| expect(replacementLength, equals(0)); |
| expect(suggestionsDone, true); |
| expect(suggestions.length, 0); |
| }); |
| } |
| |
| test_overrides() { |
| addFile('/libA.dart', 'class A {m() {}}'); |
| addTestFile(''' |
| import '/libA.dart'; |
| class B extends A {m() {^}} |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'm', |
| relevance: DART_RELEVANCE_LOCAL_METHOD); |
| }); |
| } |
| |
| test_partFile() { |
| addFile( |
| '/project/bin/testA.dart', |
| ''' |
| library libA; |
| part "$testFile"; |
| import 'dart:html'; |
| class A { } |
| '''); |
| addTestFile(''' |
| part of libA; |
| main() {^}'''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'HtmlElement'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'A'); |
| assertNoResult('test'); |
| }); |
| } |
| |
| test_partFile2() { |
| addFile( |
| '/testA.dart', |
| ''' |
| part of libA; |
| class A { }'''); |
| addTestFile(''' |
| library libA; |
| part "/testA.dart"; |
| import 'dart:html'; |
| main() {^} |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'HtmlElement'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'A'); |
| assertNoResult('test'); |
| }); |
| } |
| |
| test_simple() { |
| addTestFile(''' |
| void main() { |
| ^ |
| } |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset)); |
| expect(replacementLength, equals(0)); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertNoResult('HtmlElement'); |
| assertNoResult('test'); |
| }); |
| } |
| |
| test_topLevel() { |
| addTestFile(''' |
| typedef foo(); |
| var test = ''; |
| main() {tes^t} |
| '''); |
| return getSuggestions().then((_) { |
| expect(replacementOffset, equals(completionOffset - 3)); |
| expect(replacementLength, equals(4)); |
| // Suggestions based upon imported elements are partially filtered |
| //assertHasResult(CompletionSuggestionKind.INVOCATION, 'Object'); |
| assertHasResult(CompletionSuggestionKind.INVOCATION, 'test', |
| relevance: DART_RELEVANCE_LOCAL_TOP_LEVEL_VARIABLE); |
| assertNoResult('HtmlElement'); |
| }); |
| } |
| } |
| |
| class MockCache extends CompletionCache { |
| MockCache(AnalysisContext context, Source source) : super(context, source); |
| } |
| |
| class MockCompletionManager implements CompletionManager { |
| final AnalysisContext context; |
| final Source source; |
| final SearchEngine searchEngine; |
| StreamController<CompletionResult> controller; |
| int computeCallCount = 0; |
| int disposeCallCount = 0; |
| |
| MockCompletionManager(this.context, this.source, this.searchEngine); |
| |
| @override |
| Future<bool> computeCache() { |
| return new Future.value(true); |
| } |
| |
| @override |
| void computeSuggestions(CompletionRequest request) { |
| ++computeCallCount; |
| CompletionResult result = new CompletionResultImpl(0, 0, [], true); |
| controller.add(result); |
| } |
| |
| @override |
| void dispose() { |
| ++disposeCallCount; |
| } |
| |
| @override |
| Stream<CompletionResult> results(CompletionRequest request) { |
| controller = new StreamController<CompletionResult>(onListen: () { |
| scheduleMicrotask(() { |
| computeSuggestions(request); |
| }); |
| }); |
| return controller.stream; |
| } |
| } |
| |
| /** |
| * Mock [AnaysisContext] for tracking usage of onSourcesChanged. |
| */ |
| class MockContext implements AnalysisContext { |
| static final SourceFactory DEFAULT_SOURCE_FACTORY = new SourceFactory([]); |
| |
| MockStream<SourcesChangedEvent> mockStream; |
| |
| SourceFactory sourceFactory = DEFAULT_SOURCE_FACTORY; |
| |
| MockContext() { |
| mockStream = new MockStream<SourcesChangedEvent>(); |
| } |
| |
| @override |
| Stream<SourcesChangedEvent> get onSourcesChanged => mockStream; |
| |
| @override |
| bool exists(Source source) { |
| return source != null && source.exists(); |
| } |
| |
| @override |
| TimestampedData<String> getContents(Source source) { |
| return source.contents; |
| } |
| |
| noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
| } |
| |
| class MockRelevancySorter implements ContributionSorter { |
| bool enabled = true; |
| |
| @override |
| void sort(newApi.DartCompletionRequest request, |
| List<CompletionSuggestion> suggestions) { |
| if (!enabled) { |
| throw 'unexpected sort'; |
| } |
| } |
| } |
| |
| /** |
| * Mock stream for tracking calls to listen and subscription.cancel. |
| */ |
| class MockStream<E> implements Stream<E> { |
| MockSubscription<E> mockSubscription = new MockSubscription<E>(); |
| int listenCount = 0; |
| |
| int get cancelCount => mockSubscription.cancelCount; |
| |
| @override |
| StreamSubscription<E> listen(void onData(E event), |
| {Function onError, void onDone(), bool cancelOnError}) { |
| ++listenCount; |
| return mockSubscription; |
| } |
| |
| noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
| } |
| |
| /** |
| * Mock subscription for tracking calls to subscription.cancel. |
| */ |
| class MockSubscription<E> implements StreamSubscription<E> { |
| int cancelCount = 0; |
| |
| Future cancel() { |
| ++cancelCount; |
| return new Future.value(true); |
| } |
| |
| noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); |
| } |
| |
| class Test_AnalysisServer extends AnalysisServer { |
| final MockContext mockContext = new MockContext(); |
| |
| Test_AnalysisServer( |
| ServerCommunicationChannel channel, |
| ResourceProvider resourceProvider, |
| PubPackageMapProvider packageMapProvider, |
| Index index, |
| ServerPlugin serverPlugin, |
| AnalysisServerOptions analysisServerOptions, |
| DartSdk defaultSdk, |
| InstrumentationService instrumentationService) |
| : super( |
| channel, |
| resourceProvider, |
| packageMapProvider, |
| index, |
| serverPlugin, |
| analysisServerOptions, |
| defaultSdk, |
| instrumentationService); |
| |
| @override |
| AnalysisContext getAnalysisContext(String path) { |
| return mockContext; |
| } |
| |
| @override |
| ContextSourcePair getContextSourcePair(String path) { |
| ContextSourcePair pair = super.getContextSourcePair(path); |
| return new ContextSourcePair(mockContext, pair.source); |
| } |
| } |
| |
| /** |
| * A [CompletionDomainHandler] subclass that returns a mock completion manager |
| * so that the domain handler cache management can be tested. |
| */ |
| class Test_CompletionDomainHandler extends CompletionDomainHandler { |
| Test_CompletionDomainHandler(Test_AnalysisServer server) : super(server); |
| |
| MockContext get mockContext => (server as Test_AnalysisServer).mockContext; |
| |
| MockCompletionManager get mockManager => manager; |
| |
| void contextsChanged(ContextsChangedEvent event) { |
| contextsChangedRaw(new ContextsChangedEvent( |
| added: event.added.length > 0 ? [mockContext] : [], |
| changed: event.changed.length > 0 ? [mockContext] : [], |
| removed: event.removed.length > 0 ? [mockContext] : [])); |
| } |
| |
| void contextsChangedRaw(ContextsChangedEvent newEvent) { |
| super.contextsChanged(newEvent); |
| } |
| |
| CompletionManager createCompletionManager( |
| AnalysisContext context, Source source, SearchEngine searchEngine) { |
| return new MockCompletionManager(mockContext, source, searchEngine); |
| } |
| } |
| |
| @reflectiveTest |
| class _NoSearchEngine extends AbstractAnalysisTest { |
| @override |
| void setUp() { |
| super.setUp(); |
| createProject(); |
| handler = new CompletionDomainHandler(server); |
| } |
| |
| test_noSearchEngine() async { |
| addTestFile(''' |
| main() { |
| ^ |
| } |
| '''); |
| await waitForTasksFinished(); |
| Request request = |
| new CompletionGetSuggestionsParams(testFile, 0).toRequest('0'); |
| Response response = handler.handleRequest(request); |
| expect(response.error, isNotNull); |
| expect(response.error.code, RequestErrorCode.NO_INDEX_GENERATED); |
| } |
| } |