| // Copyright (c) 2018, 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/src/flutter/flutter_outline_computer.dart'; |
| import 'package:analysis_server/src/protocol_server.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| import '../../abstract_context.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(FlutterOutlineComputerTest); |
| }); |
| } |
| |
| @reflectiveTest |
| class FlutterOutlineComputerTest extends AbstractContextTest { |
| String testPath; |
| String testCode; |
| ResolvedUnitResult resolveResult; |
| FlutterOutlineComputer computer; |
| |
| @override |
| void setUp() { |
| super.setUp(); |
| testPath = convertPath('/home/test/lib/test.dart'); |
| addFlutterPackage(); |
| } |
| |
| Future<void> test_attribute_namedExpression() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| main() { |
| return new WidgetA( |
| value: 42, |
| ); // WidgetA |
| } |
| |
| class WidgetA extends StatelessWidget { |
| WidgetA({int value}); |
| } |
| '''); |
| var main = unitOutline.children[0]; |
| var widget = main.children[0]; |
| expect(widget.attributes, hasLength(1)); |
| |
| var attribute = widget.attributes[0]; |
| expect(attribute.name, 'value'); |
| expect(attribute.label, '42'); |
| _assertLocation(attribute.nameLocation, 75, 5); |
| _assertLocation(attribute.valueLocation, 82, 2); |
| } |
| |
| Future<void> test_attributes_bool() async { |
| var attribute = await _getAttribute('test', 'true'); |
| expect(attribute.label, 'true'); |
| expect(attribute.literalValueBoolean, true); |
| } |
| |
| Future<void> |
| test_attributes_functionExpression_hasParameters_blockExpression() async { |
| var attribute = await _getAttribute('test', '(a) {}'); |
| expect(attribute.label, '(…) { … }'); |
| } |
| |
| Future<void> |
| test_attributes_functionExpression_hasParameters_bodyExpression() async { |
| var attribute = await _getAttribute('test', '(a) => 1'); |
| expect(attribute.label, '(…) => …'); |
| } |
| |
| Future<void> |
| test_attributes_functionExpression_noParameters_blockExpression() async { |
| var attribute = await _getAttribute('test', '() {}'); |
| expect(attribute.label, '() { … }'); |
| } |
| |
| Future<void> |
| test_attributes_functionExpression_noParameters_bodyExpression() async { |
| var attribute = await _getAttribute('test', '() => 1'); |
| expect(attribute.label, '() => …'); |
| } |
| |
| Future<void> test_attributes_int() async { |
| var attribute = await _getAttribute('test', '42'); |
| expect(attribute.label, '42'); |
| expect(attribute.literalValueInteger, 42); |
| } |
| |
| Future<void> test_attributes_listLiteral() async { |
| var attribute = await _getAttribute('test', '[1, 2, 3]'); |
| expect(attribute.label, '[…]'); |
| } |
| |
| Future<void> test_attributes_mapLiteral() async { |
| var attribute = await _getAttribute('test', '{1: 10, 2: 20}'); |
| expect(attribute.label, '{…}'); |
| } |
| |
| Future<void> test_attributes_multiLine() async { |
| var attribute = await _getAttribute('test', '1 +\n 2'); |
| expect(attribute.label, '…'); |
| } |
| |
| Future<void> test_attributes_setLiteral() async { |
| var attribute = await _getAttribute('test', '{1, 2}'); |
| expect(attribute.label, '{…}'); |
| } |
| |
| Future<void> test_attributes_string_interpolation() async { |
| var unitOutline = await _computeOutline(r''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| var name = 'Foo'; |
| return const Text('Hello, $name!') |
| } |
| } |
| '''); |
| var myWidget = unitOutline.children[0]; |
| var build = myWidget.children[0]; |
| var textOutline = build.children[0]; |
| |
| expect(textOutline.attributes, hasLength(1)); |
| |
| var attribute = textOutline.attributes[0]; |
| expect(attribute.name, 'data'); |
| expect(attribute.label, r"'Hello, $name!'"); |
| expect(attribute.literalValueString, isNull); |
| } |
| |
| Future<void> test_attributes_string_literal() async { |
| var attribute = await _getAttribute('test', "'my text'"); |
| expect(attribute.label, "'my text'"); |
| expect(attribute.literalValueString, 'my text'); |
| } |
| |
| Future<void> test_attributes_unresolved() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return const Row(1, foo: 2) |
| } |
| } |
| '''); |
| var myWidget = unitOutline.children[0]; |
| var build = myWidget.children[0]; |
| |
| var rowOutline = build.children[0]; |
| expect(rowOutline.attributes, isEmpty); |
| } |
| |
| Future<void> test_children() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new Column(children: [ |
| const Text('aaa'), |
| const Text('bbb'), |
| ]); // Column |
| } |
| } |
| '''); |
| |
| expect(_toText(unitOutline), r''' |
| (D) MyWidget |
| (D) build |
| Column |
| Text |
| Text |
| '''); |
| var myWidget = unitOutline.children[0]; |
| var build = myWidget.children[0]; |
| |
| var columnOutline = build.children[0]; |
| { |
| var offset = testCode.indexOf('new Column'); |
| var length = testCode.indexOf('; // Column') - offset; |
| expect(columnOutline.offset, offset); |
| expect(columnOutline.length, length); |
| } |
| |
| { |
| var textOutline = columnOutline.children[0]; |
| var text = "const Text('aaa')"; |
| var offset = testCode.indexOf(text); |
| expect(textOutline.offset, offset); |
| expect(textOutline.length, text.length); |
| } |
| |
| { |
| var textOutline = columnOutline.children[1]; |
| var text = "const Text('bbb')"; |
| var offset = testCode.indexOf(text); |
| expect(textOutline.offset, offset); |
| expect(textOutline.length, text.length); |
| } |
| } |
| |
| Future<void> test_children_closure_blockBody() async { |
| newFile('/home/test/lib/a.dart', content: r''' |
| import 'package:flutter/widgets.dart'; |
| |
| class WidgetA extends StatelessWidget { |
| final Widget Function(bool) factory; |
| |
| WidgetA(this.factory); |
| } |
| '''); |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| import 'a.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new WidgetA((b) { |
| if (b) { |
| return const Text('aaa'), |
| } else { |
| return const Container(), |
| } |
| }); // WidgetA |
| } |
| } |
| '''); |
| expect(_toText(unitOutline), r''' |
| (D) MyWidget |
| (D) build |
| WidgetA |
| Text |
| Container |
| '''); |
| } |
| |
| Future<void> test_children_closure_expressionBody() async { |
| newFile('/home/test/lib/a.dart', content: r''' |
| import 'package:flutter/widgets.dart'; |
| |
| class WidgetA extends StatelessWidget { |
| final Widget Function() factory; |
| |
| WidgetA(this.factory); |
| } |
| '''); |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| import 'a.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new WidgetA( |
| () => const Text('aaa'), |
| ); // WidgetA |
| } |
| } |
| '''); |
| expect(_toText(unitOutline), r''' |
| (D) MyWidget |
| (D) build |
| WidgetA |
| Text |
| '''); |
| } |
| |
| Future<void> test_children_withCollectionElements() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| bool includeB = true; |
| return new Column(children: [ |
| const Text('aaa'), |
| if (includeB) const Text('bbb'), |
| for (int s in ['ccc', 'ddd'] const Text(s), |
| ]); |
| } |
| } |
| '''); |
| |
| expect(_toText(unitOutline), r''' |
| (D) MyWidget |
| (D) build |
| Column |
| Text |
| Text |
| Text |
| '''); |
| } |
| |
| Future<void> test_codeOffsetLength() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| /// Comment |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new Container(); |
| } |
| } |
| '''); |
| var myWidget = unitOutline.children[0]; |
| expect(myWidget.offset, 40); |
| expect(myWidget.length, 137); |
| expect(myWidget.codeOffset, 52); |
| expect(myWidget.codeLength, 125); |
| |
| var build = myWidget.children[0]; |
| expect(build.offset, 95); |
| expect(build.length, 80); |
| expect(build.codeOffset, 107); |
| expect(build.codeLength, 68); |
| |
| var container = build.children[0]; |
| expect(container.offset, 155); |
| expect(container.length, 15); |
| expect(container.codeOffset, 155); |
| expect(container.codeLength, 15); |
| } |
| |
| Future<void> test_genericLabel_invocation() async { |
| var unitOutline = await _computeOutline(r''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new Row(children: [ |
| createText(0), |
| createEmptyText(), |
| WidgetFactory.createMyText(), |
| ]); |
| } |
| Text createEmptyText() { |
| return new Text(''); |
| } |
| Text createText(int index) { |
| return new Text('index: $index'); |
| } |
| } |
| |
| class WidgetFactory { |
| static Text createMyText() => new Text(''); |
| } |
| '''); |
| var myWidget = unitOutline.children[0]; |
| var build = myWidget.children[0]; |
| expect(build.children, hasLength(1)); |
| |
| var row = build.children[0]; |
| expect(row.kind, FlutterOutlineKind.NEW_INSTANCE); |
| expect(row.className, 'Row'); |
| expect(row.children, hasLength(3)); |
| |
| { |
| var text = row.children[0]; |
| expect(text.kind, FlutterOutlineKind.GENERIC); |
| expect(text.className, 'Text'); |
| expect(text.label, 'createText(…)'); |
| } |
| |
| { |
| var text = row.children[1]; |
| expect(text.kind, FlutterOutlineKind.GENERIC); |
| expect(text.className, 'Text'); |
| expect(text.label, 'createEmptyText()'); |
| } |
| |
| { |
| var text = row.children[2]; |
| expect(text.kind, FlutterOutlineKind.GENERIC); |
| expect(text.className, 'Text'); |
| expect(text.label, 'WidgetFactory.createMyText()'); |
| } |
| } |
| |
| Future<void> test_parentAssociationLabel() async { |
| newFile('/home/test/lib/a.dart', content: r''' |
| import 'package:flutter/widgets.dart'; |
| |
| class WidgetA extends StatelessWidget { |
| final Widget top; |
| final Widget bottom; |
| |
| WidgetA({this.top, this.bottom}); |
| } |
| '''); |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| import 'a.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| return new WidgetA( |
| top: const Text('aaa'), |
| bottom: const Text('bbb'), |
| ); // WidgetA |
| } |
| } |
| '''); |
| expect(_toText(unitOutline), r''' |
| (D) MyWidget |
| (D) build |
| WidgetA |
| top: Text |
| bottom: Text |
| '''); |
| } |
| |
| Future<void> test_variableName() async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| class MyWidget extends StatelessWidget { |
| @override |
| Widget build(BuildContext context) { |
| var text = new Text('aaa'); |
| return new Center(child: text); // Center |
| } |
| } |
| '''); |
| var myWidget = unitOutline.children[0]; |
| var build = myWidget.children[0]; |
| expect(build.children, hasLength(2)); |
| |
| var textNew = build.children[0]; |
| expect(textNew.kind, FlutterOutlineKind.NEW_INSTANCE); |
| expect(textNew.className, 'Text'); |
| |
| var center = build.children[1]; |
| expect(center.kind, FlutterOutlineKind.NEW_INSTANCE); |
| expect(center.className, 'Center'); |
| expect(center.children, hasLength(1)); |
| |
| var textRef = center.children[0]; |
| expect(textRef.kind, FlutterOutlineKind.VARIABLE); |
| expect(textRef.className, 'Text'); |
| expect(textRef.variableName, 'text'); |
| } |
| |
| void _assertLocation( |
| Location actual, int expectedOffset, int expectedLength) { |
| expect(actual.offset, expectedOffset); |
| expect(actual.length, expectedLength); |
| } |
| |
| Future<FlutterOutline> _computeOutline(String code) async { |
| testCode = code; |
| newFile(testPath, content: code); |
| resolveResult = await session.getResolvedUnit(testPath); |
| computer = FlutterOutlineComputer(resolveResult); |
| return computer.compute(); |
| } |
| |
| Future<FlutterOutlineAttribute> _getAttribute( |
| String name, String value) async { |
| var unitOutline = await _computeOutline(''' |
| import 'package:flutter/widgets.dart'; |
| |
| main() { |
| new MyWidget($value); |
| } |
| |
| class MyWidget extends StatelessWidget { |
| MyWidget($name); |
| |
| @override |
| Widget build(BuildContext context) { |
| return const Text('') |
| } |
| } |
| '''); |
| var main = unitOutline.children[0]; |
| var newMyWidget = main.children[0]; |
| |
| expect(newMyWidget.attributes, hasLength(1)); |
| |
| var attribute = newMyWidget.attributes[0]; |
| expect(attribute.name, name); |
| expect(attribute.nameLocation, isNull); |
| _assertLocation(attribute.valueLocation, 64, value.length); |
| |
| return attribute; |
| } |
| |
| static String _toText(FlutterOutline outline) { |
| var buffer = StringBuffer(); |
| |
| void writeOutline(FlutterOutline outline, String indent) { |
| buffer.write(indent); |
| |
| if (outline.kind == FlutterOutlineKind.DART_ELEMENT) { |
| buffer.write('(D) '); |
| buffer.writeln(outline.dartElement.name); |
| } else { |
| if (outline.kind == FlutterOutlineKind.NEW_INSTANCE) { |
| if (outline.parentAssociationLabel != null) { |
| buffer.write(outline.parentAssociationLabel); |
| buffer.write(': '); |
| } |
| buffer.writeln(outline.className); |
| } else { |
| fail('Unknown kind: ${outline.kind}'); |
| } |
| } |
| |
| if (outline.children != null) { |
| for (var child in outline.children) { |
| writeOutline(child, '$indent '); |
| } |
| } |
| } |
| |
| for (var child in outline.children) { |
| writeOutline(child, ''); |
| } |
| return buffer.toString(); |
| } |
| } |