| // 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.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import 'server_abstract.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(OutlineTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class OutlineTest extends AbstractLspAnalysisServerTest { |
| Future<void> test_afterChange() async { |
| var initialContent = 'class A {}'; |
| var updatedContent = 'class B {}'; |
| await initialize(initializationOptions: {'outline': true}); |
| |
| var outlineUpdateBeforeChange = waitForOutline(mainFileUri); |
| await openFile(mainFileUri, initialContent); |
| var outlineBeforeChange = await outlineUpdateBeforeChange; |
| |
| var outlineUpdateAfterChange = waitForOutline(mainFileUri); |
| await replaceFile(1, mainFileUri, updatedContent); |
| var outlineAfterChange = await outlineUpdateAfterChange; |
| |
| expect(outlineBeforeChange, isNotNull); |
| expect(outlineBeforeChange.children, hasLength(1)); |
| expect(outlineBeforeChange.children![0].element.name, equals('A')); |
| |
| expect(outlineAfterChange, isNotNull); |
| expect(outlineAfterChange.children, hasLength(1)); |
| expect(outlineAfterChange.children![0].element.name, equals('B')); |
| } |
| |
| Future<void> test_extensions() async { |
| var initialContent = ''' |
| extension StringExtensions on String {} |
| extension on String {} |
| '''; |
| await initialize(initializationOptions: {'outline': true}); |
| |
| var outlineUpdate = waitForOutline(mainFileUri); |
| await openFile(mainFileUri, initialContent); |
| var outline = await outlineUpdate; |
| |
| expect(outline, isNotNull); |
| expect(outline.children, hasLength(2)); |
| expect(outline.children![0].element.name, equals('StringExtensions')); |
| expect(outline.children![1].element.name, equals('<unnamed extension>')); |
| } |
| |
| Future<void> test_initial() async { |
| var content = ''' |
| /// a |
| class A { |
| /// b |
| b() { |
| /// c |
| c() {} |
| } |
| |
| /// d |
| num get d => 1; |
| } |
| '''; |
| await initialize(initializationOptions: {'outline': true}); |
| |
| var outlineNotification = waitForOutline(mainFileUri); |
| await openFile(mainFileUri, content); |
| var outline = await outlineNotification; |
| |
| expect(outline, isNotNull); |
| |
| // Root node is entire document |
| expect( |
| outline.range, |
| equals(Range( |
| start: Position(line: 0, character: 0), |
| end: Position(line: 11, character: 0)))); |
| expect(outline.children, hasLength(1)); |
| |
| // class A |
| var classA = outline.children![0]; |
| expect(classA.element.name, equals('A')); |
| expect(classA.element.kind, equals('CLASS')); |
| expect( |
| classA.element.range, |
| equals(Range( |
| start: Position(line: 1, character: 6), |
| end: Position(line: 1, character: 7)))); |
| expect( |
| classA.range, |
| equals(Range( |
| start: Position(line: 0, character: 0), |
| end: Position(line: 10, character: 1)))); |
| expect( |
| classA.codeRange, |
| equals(Range( |
| start: Position(line: 1, character: 0), |
| end: Position(line: 10, character: 1)))); |
| expect(classA.children, hasLength(2)); |
| |
| // b() |
| var methodB = classA.children![0]; |
| expect(methodB.element.name, equals('b')); |
| expect(methodB.element.kind, equals('METHOD')); |
| expect( |
| methodB.element.range, |
| equals(Range( |
| start: Position(line: 3, character: 2), |
| end: Position(line: 3, character: 3)))); |
| expect( |
| methodB.range, |
| equals(Range( |
| start: Position(line: 2, character: 2), |
| end: Position(line: 6, character: 3)))); |
| expect( |
| methodB.codeRange, |
| equals(Range( |
| start: Position(line: 3, character: 2), |
| end: Position(line: 6, character: 3)))); |
| expect(methodB.children, hasLength(1)); |
| |
| // c() |
| var methodC = methodB.children![0]; |
| expect(methodC.element.name, equals('c')); |
| expect(methodC.element.kind, equals('FUNCTION')); |
| expect( |
| methodC.element.range, |
| equals(Range( |
| start: Position(line: 5, character: 4), |
| end: Position(line: 5, character: 5)))); |
| // TODO(dantup): This one seems to be excluding its dartdoc? |
| // should be line 4 for the starting range. |
| // https://github.com/dart-lang/sdk/issues/39746 |
| expect( |
| methodC.range, |
| equals(Range( |
| start: Position(line: 5, character: 4), |
| end: Position(line: 5, character: 10)))); |
| expect( |
| methodC.codeRange, |
| equals(Range( |
| start: Position(line: 5, character: 4), |
| end: Position(line: 5, character: 10)))); |
| expect(methodC.children, isNull); |
| |
| // num get d |
| var fieldD = classA.children![1]; |
| expect(fieldD.element.name, equals('d')); |
| expect(fieldD.element.kind, equals('GETTER')); |
| expect( |
| fieldD.element.range, |
| equals(Range( |
| start: Position(line: 9, character: 10), |
| end: Position(line: 9, character: 11)))); |
| expect( |
| fieldD.range, |
| equals(Range( |
| start: Position(line: 8, character: 2), |
| end: Position(line: 9, character: 17)))); |
| expect( |
| fieldD.codeRange, |
| equals(Range( |
| start: Position(line: 9, character: 2), |
| end: Position(line: 9, character: 17)))); |
| expect(fieldD.children, isNull); |
| } |
| |
| /// As an optimization, when a file is opened but does not contain any changes |
| /// from what was on disk, we skip analysis (in onOverlayCreated). |
| /// We still need to ensure that notifications like Outline (which are |
| /// triggered by analysis results and only sent for open files) are sent to |
| /// the client. |
| Future<void> test_openedWithoutChanges() async { |
| var content = r''' |
| class A {} |
| '''; |
| |
| // Create the file on disk so that opening the file won't re-trigger |
| // analysis. |
| newFile(mainFilePath, content); |
| |
| // Track when outlines arrive. |
| Outline? mainOutline; |
| unawaited( |
| waitForOutline(mainFileUri).then((outline) => mainOutline = outline), |
| ); |
| |
| await Future.wait([ |
| initialize(initializationOptions: {'outline': true}), |
| waitForAnalysisComplete(), |
| ]); |
| await pumpEventQueue(times: 5000); |
| expect(mainOutline, isNull); // Shouldn't be sent yet, file is not open. |
| |
| await openFile(mainFileUri, content); |
| await pumpEventQueue(times: 5000); |
| expect(mainOutline, isNotNull); // Should have been sent now. |
| } |
| } |