| import 'dart:async'; |
| |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/error/codes.dart'; |
| import 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| import 'package:angular_analyzer_plugin/ast.dart'; |
| import 'package:angular_analyzer_plugin/src/model.dart'; |
| import 'package:angular_analyzer_plugin/src/selector.dart'; |
| // TODO(mfairhurst) use package:tuple once it support Dart 2 |
| import 'package:angular_analyzer_plugin/src/tuple.dart'; |
| import 'package:angular_analyzer_plugin/errors.dart'; |
| import 'package:angular_ast/angular_ast.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| import 'package:test/test.dart'; |
| |
| import 'abstract_angular.dart'; |
| import 'element_assert.dart'; |
| |
| void main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(TemplateResolverTest); |
| }); |
| } |
| |
| void assertPropertyElement(AngularElement element, |
| {nameMatcher, sourceMatcher}) { |
| expect(element, const isInstanceOf<InputElement>()); |
| final inputElement = element; |
| if (nameMatcher != null) { |
| expect(inputElement.name, nameMatcher); |
| } |
| if (sourceMatcher != null) { |
| expect(inputElement.source.fullName, sourceMatcher); |
| } |
| } |
| |
| @reflectiveTest |
| class TemplateResolverTest extends AbstractAngularTest { |
| String dartCode; |
| String htmlCode; |
| Source dartSource; |
| Source htmlSource; |
| |
| List<AbstractDirective> directives; |
| |
| Template template; |
| List<ResolvedRange> ranges; |
| |
| // ignore: non_constant_identifier_names |
| Future test_attribute_mixedCase() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| '''); |
| _addHtmlSource(r""" |
| <svg viewBox='0, 0, 24 24'></svg> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(0)); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_attributeInterpolation() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String aaa; // 1 |
| String bbb; // 2 |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span title='Hello {{aaa}} and {{bbb}}!'></span> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(2)); |
| _assertElement('aaa}}').dart.getter.at('aaa; // 1'); |
| _assertElement('bbb}}').dart.getter.at('bbb; // 2'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_eventBinding() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div (click)='handleClick($event)'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(3)); |
| _assertElement('click)').output.inCoreHtml; |
| _assertElement('handleClick').dart.method.at('handleClick(MouseEvent'); |
| |
| errorListener.assertNoErrors(); |
| final search = new ElementSearch((e) => e.localName == "div"); |
| template.ast.accept(search); |
| |
| expect(search.element, isNotNull); |
| expect(search.element.boundStandardOutputs, hasLength(1)); |
| expect(search.element.boundStandardOutputs.first.boundOutput.name, 'click'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_keyupdownWithKeysOk() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handle(dynamic e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div (keyup.a)='handle($event)'></div> |
| <div (keydown.enter)='handle($event)'></div> |
| <div (keydown.shift.x)='handle($event)'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_reductionsOnRegularOutputsNotAllowed() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handle(dynamic e) { |
| } |
| } |
| '''); |
| final code = r''' |
| <div (click.whatever)='handle($event)'></div> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EVENT_REDUCTION_NOT_ALLOWED, code, '.whatever'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_nativeEventBindingOnComponent() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: [SomeComponent]) |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| |
| @Component(selector: 'some-comp', template: '') |
| class SomeComponent { |
| } |
| '''); |
| _addHtmlSource(r""" |
| <some-comp (click)='handleClick($event)'></some-comp> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('click').output.inCoreHtml; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_eventBinding_on() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div on-click='handleClick()'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(2)); |
| _assertElement('click=').output.inCoreHtml; |
| _assertElement('handleClick()').dart.method.at('handleClick(MouseEvent'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_valid() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Directive(selector: '[titled]', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String title; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span titled [title]='text'></span> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| |
| errorListener.assertNoErrors(); |
| final search = new ElementSearch((e) => e.localName == "span"); |
| template.ast.accept(search); |
| |
| expect(search.element, isNotNull); |
| expect(search.element.boundDirectives, hasLength(1)); |
| final boundDirective = search.element.boundDirectives.first; |
| expect(boundDirective.inputBindings, hasLength(1)); |
| expect(boundDirective.inputBindings.first.boundInput.name, 'title'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_nativeGlobalAttrBindingOnComponent() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: [SomeComponent]) |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| |
| @Component(selector: 'some-comp', template: '') |
| class SomeComponent { |
| } |
| '''); |
| _addHtmlSource(r""" |
| <some-comp [hidden]='false'></some-comp> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('hidden').input.inCoreHtml; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_asString() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| @Input() String title; |
| } |
| '''); |
| final code = r""" |
| <title-comp title='anything can go here' id="some id"></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('title=').input.inFileName('/test_panel.dart').at('title;'); |
| _assertElement('id=').input.inCoreHtml; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_asString_fromDynamic() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| bool _title; |
| @Input() |
| set title(value) { |
| _title = value == "" ? true : false; |
| } |
| bool get title => _title; |
| } |
| '''); |
| |
| final code = r""" |
| <title-comp title='anything can go here'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('title=').input.inFileName('/test_panel.dart').at('title('); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() int title; |
| } |
| '''); |
| final code = r""" |
| <title-comp [title]='text'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "text"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_asString_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| @Input() int titleInput; |
| } |
| '''); |
| |
| final code = r""" |
| <title-comp titleInput='string binding'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID, |
| code, |
| "titleInput"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_asBoool_noError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| @Input() bool boolInput; |
| } |
| '''); |
| |
| final code = r""" |
| <title-comp boolInput></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_asBool_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| @Input() bool boolInput; |
| } |
| '''); |
| |
| final code = r""" |
| <title-comp boolInput="foo bar baz"></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID, |
| code, |
| "boolInput"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_nativeHtml_asString_notTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| <div hidden="allowed because becomes addAttribute() rather than .hidden="></div> |
| <img width="allowed because becomes addAttribute() rather than .width=" /> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_noValue() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() int title; |
| } |
| '''); |
| final code = r""" |
| <title-comp [title]></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EMPTY_BINDING, code, "[title]"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_empty() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() int title; |
| } |
| '''); |
| final code = r""" |
| <title-comp [title]=""></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EMPTY_BINDING, code, "[title]"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_boundToNothing() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [title]='text'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.NONEXIST_INPUT_BOUND, code, "title"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_duplicate_standardHtml() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [MyTagComponent]) |
| class TestPanel {} |
| @Component(selector: 'my-tag', template: '') |
| class MyTagComponent { |
| @Input() |
| String readonly; |
| } |
| '''); |
| final code = r''' |
| <my-tag [readonly]="'blah'"></my-tag> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_alt_standardHtml() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [class]='text' [innerHtml]='text'></span> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_orig_standardHtml() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [className]='text' [innerHTML]='text'></span> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_safeBindings() async { |
| _addDartSource(r''' |
| import 'package:angular/security.dart'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| SafeHtml html; |
| SafeUrl url; |
| SafeStyle style; |
| SafeResourceUrl resourceUrl; |
| } |
| '''); |
| final code = r""" |
| <a [innerHtml]='html' [innerHTML]='html' [href]='url'></a> |
| <iframe [src]='resourceUrl'></iframe><!-- TODO test [style]='style' --> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_wrongSafeBindingErrors() async { |
| _addDartSource(r''' |
| import 'package:angular/security.dart'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| SafeHtml html; |
| SafeUrl url; |
| SafeStyle style; |
| SafeResourceUrl resourceUrl; |
| } |
| '''); |
| final code = r""" |
| <a [innerHtml]='style' [innerHTML]='url' [href]='resourceUrl'></a> |
| <iframe [src]='html'></iframe> <!--TODO test [style]='html' --> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertErrorsWithCodes([ |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.UNSAFE_BINDING, // resourceUrl gets reported this way |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_unsafelyBound() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String unsafe; |
| } |
| '''); |
| final code = r""" |
| <iframe [src]='unsafe'></iframe> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.UNSAFE_BINDING, code, 'unsafe'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_hardcodedDoesntNeedSanitization() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String unsafe; |
| } |
| '''); |
| final code = r""" |
| <iframe src='this does no sanitization and succeeds'></iframe> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_unsafelyBoundViaMustache() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String unsafe; |
| } |
| '''); |
| final code = r""" |
| <iframe src='this is ok until we bind {{unsafe}}'></iframe> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(AngularWarningCode.UNSAFE_BINDING, code, |
| 'this is ok until we bind {{unsafe}}'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_doesntNeedSafeBinding() async { |
| _addDartSource(r''' |
| import 'package:angular/security.dart'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| SafeHtml html; |
| SafeUrl url; |
| SafeStyle style; |
| SafeResourceUrl resourceUrl; |
| } |
| '''); |
| final code = r""" |
| <a [class]='html'></a> |
| <a [class]='url'></a> |
| <a [class]='style'></a> |
| <a [class]='resourceUrl'></a> |
| """; |
| await angularDriver.getStandardHtml(); |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertErrorsWithCodes([ |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_valid() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Directive(selector: '[titled]', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String title; |
| @Output() EventEmitter<String> titleChange; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span titled [(title)]='text'></span> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| final search = new ElementSearch((e) => e.localName == "span"); |
| template.ast.accept(search); |
| |
| expect(search.element, isNotNull); |
| expect(search.element.boundDirectives, hasLength(1)); |
| final boundDirective = search.element.boundDirectives.first; |
| expect(boundDirective.inputBindings, hasLength(1)); |
| expect(boundDirective.inputBindings.first.boundInput.name, 'title'); |
| expect(boundDirective.outputBindings, hasLength(1)); |
| expect(boundDirective.outputBindings.first.boundOutput.name, 'titleChange'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_noAttr_emptyBinding() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Directive(selector: '[titled]', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String twoWay; |
| @Output() EventEmitter<String> twoWayChange; |
| } |
| '''); |
| final code = r""" |
| <span titled [(twoWay)]></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EMPTY_BINDING, code, "[(twoWay)]"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_inputTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() int title; |
| @Output() EventEmitter<String> titleChange; |
| } |
| '''); |
| final code = r""" |
| <title-comp [(title)]='text'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "text"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_outputTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String title; |
| @Output() EventEmitter<int> titleChange; |
| } |
| '''); |
| final code = r""" |
| <title-comp [(title)]='text'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TWO_WAY_BINDING_OUTPUT_TYPE_ERROR, code, "text"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_outputBinding_noValue() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Output() EventEmitter<int> title; |
| } |
| '''); |
| final code = r""" |
| <title-comp (title)></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EMPTY_BINDING, code, "(title)"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_notAssignableError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', |
| directives: const [TitleComponent], templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String title; |
| @Output() EventEmitter<String> titleChange; |
| } |
| '''); |
| final code = r""" |
| <title-comp [(title)]="text.toUpperCase()"></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TWO_WAY_BINDING_NOT_ASSIGNABLE, |
| code, |
| "text.toUpperCase()"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_noInputToBind() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Output() EventEmitter<String> noInputChange; |
| } |
| '''); |
| final code = r""" |
| <title-comp [(noInput)]="text"></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.NONEXIST_INPUT_BOUND, code, "noInput"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_twoWayBinding_noOutputToBind() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| @Component(selector: 'title-comp', template: '', inputs: 'title') |
| class TitleComponent { |
| @Input() String inputOnly; |
| } |
| '''); |
| final code = r""" |
| <title-comp [(inputOnly)]="text"></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.NONEXIST_TWO_WAY_OUTPUT_BOUND, code, "inputOnly"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_bind() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span bind-title='text'></span> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(1)); |
| _assertElement("text'>").dart.getter.at('text; // 1'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_outputBinding_boundToNothing() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span (title)='text'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.NONEXIST_OUTPUT_BOUND, code, "title"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_outputBinding_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [TitleComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| takeString(String arg); |
| } |
| @Component(selector: 'title-comp', template: '') |
| class TitleComponent { |
| @Output() EventEmitter<int> output; |
| } |
| '''); |
| final code = r""" |
| <title-comp (output)='takeString($event)'></title-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, code, r"$event"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputBinding_noEvent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="$event"> |
| </h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.UNDEFINED_IDENTIFIER, code, r"$event"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_mustache_noEvent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| <h1>{{$event}}</h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.UNDEFINED_IDENTIFIER, code, r"$event"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_mustache_closeOpen_githubBug198() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| }}{{''}} |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.UNOPENED_MUSTACHE, code, '}}'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_as_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1>{{str as String}}</h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "str as String"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_nested_as_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1>{{(str.isEmpty as String).isEmpty}}</h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(AngularWarningCode.DISALLOWED_EXPRESSION, code, |
| "str.isEmpty as String"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_typed_list_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="<String>[].isEmpty"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "<String>[]"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_setter_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="str = 'hey'"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "str = 'hey'"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_assignment_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 #h1 [hidden]="h1 = 4"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statements_assignment_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 #h1 (click)="h1 = 4"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_invocation_of_erroneous_assignment_no_crash() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| Function f; |
| } |
| '''); |
| final code = r""" |
| {{str = (f)()}} |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "str = (f)()"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statements_setter_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 #h1 (click)="str = 'hey'"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_is_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="str is int"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "str is int"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_typed_map_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="<String, String>{}.keys.isEmpty"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "<String, String>{}"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_func_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="(){}"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "(){}"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_func2_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="()=>x"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "()=>x"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_symbol_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="#symbol"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_symbol_invoked_noCrash() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="#symbol()"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_await_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="await str"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| //This actually gets parsed as an identifier, which is OK. Still fails! |
| errorListener.assertErrorsWithCodes([ |
| StaticWarningCode.UNDEFINED_IDENTIFIER, |
| AngularWarningCode.TRAILING_EXPRESSION |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_throw_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="throw str"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "throw str"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_cascade_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="str..x"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "str..x"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_new_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="new String().isEmpty"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "new String()"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_named_args_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| bool callMe({String arg}) => true; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="callMe(arg: 'bob')"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "arg: 'bob'"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_rethrow_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="rethrow"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "rethrow"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_super_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="super.x"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "super"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_this_not_allowed() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String str; |
| } |
| '''); |
| final code = r""" |
| <h1 [hidden]="this"></h1> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DISALLOWED_EXPRESSION, code, "this"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_attrBinding_valid() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [attr.aria-title]='text'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_attrBinding_expressionTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int pixels; |
| } |
| '''); |
| final code = r""" |
| <span [attr.aria]='pixels.length'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticTypeWarningCode.UNDEFINED_GETTER, code, "length"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_classBinding_valid() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [class.my-class]='text == null'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_classBinding_invalidClassName() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String title; |
| } |
| '''); |
| final code = r""" |
| <span [class.invalid.class]='title == null'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INVALID_HTML_CLASSNAME, code, "invalid.class"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_classBinding_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String notBoolean; |
| } |
| '''); |
| final code = r""" |
| <span [class.aria]='notBoolean'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.CLASS_BINDING_NOT_BOOLEAN, code, "notBoolean"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_noUnit_valid() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.background-color]='text'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_noUnit_invalidCssProperty() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.invalid*property]='text'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertMultipleErrorsExplicit(htmlSource, code, [ |
| new Tuple4(']', 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), |
| new Tuple4(']', 1, |
| NgParserWarningCode.EXPECTED_WHITESPACE_BEFORE_NEW_DECORATOR, []), |
| new Tuple4('[', 14, NgParserWarningCode.SUFFIX_PROPERTY, []), |
| new Tuple4('*property', 9, AngularWarningCode.TEMPLATE_ATTR_NOT_USED, []), |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_noUnit_expressionTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int noLength; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.background-color]='noLength.length'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticTypeWarningCode.UNDEFINED_GETTER, code, "length"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_invalidPropertyName() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int pixels; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.border&radius.px]='pixels'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertMultipleErrorsExplicit(htmlSource, code, [ |
| new Tuple4( |
| "]='pixels'", 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), |
| new Tuple4("]='pixels'", 1, |
| NgParserWarningCode.EXPECTED_WHITESPACE_BEFORE_NEW_DECORATOR, []), |
| new Tuple4('&radius', 1, NgParserWarningCode.UNEXPECTED_TOKEN, []), |
| new Tuple4('[style', 14, NgParserWarningCode.SUFFIX_PROPERTY, []), |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_invalidUnitName() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| double pixels; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.border-radius.p|x]='pixels'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertMultipleErrorsExplicit(htmlSource, code, [ |
| new Tuple4( |
| "]='pixels'", 0, AngularWarningCode.NONEXIST_INPUT_BOUND, ['']), |
| new Tuple4("]='pixels'", 1, |
| NgParserWarningCode.EXPECTED_WHITESPACE_BEFORE_NEW_DECORATOR, []), |
| new Tuple4('|x', 1, NgParserWarningCode.UNEXPECTED_TOKEN, []), |
| new Tuple4('[style', 23, NgParserWarningCode.SUFFIX_PROPERTY, []), |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_heightPercent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int percentage; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.height.%]='percentage'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_widthPercent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int percentage; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.width.%]='percentage'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_nonWidthOrHeightPercent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int percentage; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.something.%]='percentage'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INVALID_CSS_UNIT_NAME, code, "%"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_styleBinding_withUnit_typeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String notNumber; // 1 |
| } |
| '''); |
| final code = r""" |
| <span [style.border-radius.px]='notNumber'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.CSS_UNIT_BINDING_NOT_NUMBER, code, "notNumber"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_detect_eof_post_semicolon_in_moustache() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String name = "TestPanel"; |
| } |
| '''); |
| |
| final code = r""" |
| <p>{{name; bad portion}}</p> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TRAILING_EXPRESSION, code, "; bad portion"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_detect_eof_ellipsis_in_moustache() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String name = "TestPanel"; |
| } |
| '''); |
| final code = r""" |
| <p>{{name...}}</p> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TRAILING_EXPRESSION, code, "..."); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_detect_eof_post_semicolon_in_property_binding() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int a = 1; |
| int b = 1; |
| } |
| '''); |
| |
| final code = r""" |
| <div [class.selected]="a == b; bad portion"></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TRAILING_EXPRESSION, code, "; bad portion"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_detect_eof_ellipsis_in_property_binding() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| int a = 1; |
| int b = 1; |
| } |
| '''); |
| final code = r""" |
| <div [class.selected]="a==b..."></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TRAILING_EXPRESSION, code, "..."); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_inputAndOutputBinding_genericDirective_ok() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String string; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T> { |
| @Output() EventEmitter<T> output; |
| @Input() T input; |
| |
| @Output() EventEmitter<T> twoWayChange; |
| @Input() T twoWay; |
| } |
| '''); |
| final code = r""" |
| <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_inputAndOutputBinding_genericDirectiveChild_ok() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String string; |
| } |
| class Generic<T> { |
| EventEmitter<T> output; |
| T input; |
| |
| EventEmitter<T> twoWayChange; |
| T twoWay; |
| } |
| @Component(selector: 'generic-comp', template: '', inputs: ['input', 'twoWay'], |
| outputs: ['output', 'twoWayChange']) |
| class GenericComponent<T> extends Generic<T> { |
| } |
| '''); |
| final code = r""" |
| <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_inputAndOutputBinding_extendGenericUnbounded_ok() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String string; |
| } |
| class Generic<T> { |
| EventEmitter<T> output; |
| T input; |
| |
| EventEmitter<T> twoWayChange; |
| T twoWay; |
| } |
| @Component(selector: 'generic-comp', template: '', inputs: ['input', 'twoWay'], |
| outputs: ['output', 'twoWayChange']) |
| class GenericComponent<T> extends Generic { |
| } |
| '''); |
| final code = r""" |
| <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_inputAndOutputBinding_genericDirective_chain_ok() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| String string; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends E, E> { |
| @Output() EventEmitter<T> output; |
| @Input() T input; |
| |
| @Output() EventEmitter<T> twoWayChange; |
| @Input() T twoWay; |
| } |
| '''); |
| final code = r""" |
| <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_inputAndOutputBinding_genericDirective_nested_ok() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| List<String> stringList; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T> { |
| @Output() EventEmitter<List<T>> output; |
| @Input() List<T> input; |
| |
| @Output() EventEmitter<List<T>> twoWayChange; |
| @Input() List<T> twoWay; |
| } |
| '''); |
| final code = r""" |
| <generic-comp (output)='$event[0].length' [input]="stringList" [(twoWay)]="stringList"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_inputBinding_genericDirective_lowerBoundTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| int notString; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends String> { |
| @Input() T string; |
| } |
| '''); |
| final code = r""" |
| <generic-comp [string]="notString"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "notString"); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_input_genericDirective_lowerBoundChainTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| int notString; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends O, O extends String> { |
| @Input() T string; |
| } |
| '''); |
| final code = r""" |
| <generic-comp [string]="notString"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "notString"); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_input_genericDirective_lowerBoundNestedTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| List<int> notStringList; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends String> { |
| @Input() List<T> stringList; |
| } |
| '''); |
| final code = r""" |
| <generic-comp [stringList]="notStringList"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "notStringList"); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_outputBinding_genericDirective_lowerBoundTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| takeInt(int i) {} |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends String> { |
| @Output() EventEmitter<T> string; |
| } |
| '''); |
| final code = r""" |
| <generic-comp (string)="takeInt($event)"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, code, r"$event"); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_expression_twoWayBinding_genericDirective_lowerBoundTypeError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', directives: const [GenericComponent], |
| templateUrl: 'test_panel.html') |
| class TestPanel { |
| int anInt; |
| } |
| @Component(selector: 'generic-comp', template: '') |
| class GenericComponent<T extends String> { |
| @Output() EventEmitter<T> stringChange; |
| @Input() dynamic string; |
| } |
| '''); |
| final code = r""" |
| <generic-comp [(string)]="anInt"></generic-comp> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TWO_WAY_BINDING_OUTPUT_TYPE_ERROR, code, "anInt"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_pipe_in_moustache() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String name = "TestPanel"; |
| } |
| '''); |
| final code = r""" |
| <p>{{((1 | pipe1:(2+2):(5 | pipe2:1:2)) + (2 | pipe3:4:2))}}</p> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_pipe_in_moustache_with_error() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| String name = "TestPanel"; |
| } |
| '''); |
| final code = r""" |
| <p>{{((1 | pipe1:(2+2):(5 | pipe2:1:2)) + (error1 | pipe3:4:2))}}</p> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.UNDEFINED_IDENTIFIER, code, "error1"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_pipe_in_input_binding() async { |
| _addDartSource(r''' |
| @Component(selector: 'name-panel', template: r"<div>AAA</div>") |
| class NamePanel { |
| @Input() int value; |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NamePanel]) |
| class TestPanel { |
| int value; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <name-panel [value]='value | pipe1'></name-panel> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_expression_pipe_in_ngFor() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> operators = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li *ngFor='let operator of (operators | pipe1)'> |
| {{operator.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_statement_eventBinding_single_statement_without_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div (click)='handleClick($event)'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement('handleClick').dart.method.at('handleClick(MouseEvent'); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_single_statement_with_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div (click)='handleClick($event);'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement('handleClick').dart.method.at('handleClick(MouseEvent'); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_statement_eventBinding_return_statement_without_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r"""<h2 (click)='return 5'></h2>"""; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT, |
| code, |
| "return 5"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_return_statement_with_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r"""<h2 (click)='return 5;'></h2>"""; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT, |
| code, |
| "return 5"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_if_statement_without_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r"""<h2 (click)='if(true){}'></h2>"""; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT, |
| code, |
| "if(true){}"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_if_statement_with_semicolon() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r"""<h2 (click)='if(true){};'></h2>"""; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT, |
| code, |
| "if(true){}"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_double_statement() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div (click)='handleClick($event); 5+5;'></div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('handleClick').dart.method.at('handleClick(MouseEvent'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_error_on_second_statement() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='handleClick($event); unknownFunction()'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticTypeWarningCode.UNDEFINED_METHOD, code, "unknownFunction"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_error_on_assignment_statement() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='handleClick($event); String s;'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT, |
| code, |
| "String s"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_typeError() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='handleClick($event); 1 + "asdf";'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, code, '"asdf"'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_all_semicolons() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)=';;;;;;;;;;;;;'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_statement_eventBinding_single_variable() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| String random_string = ""; |
| } |
| '''); |
| final code = r""" |
| <div (click)='handleClick;'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_statement_eventBinding_unexpected_closing_brackets_at_end() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='handleClick($event);}}}}'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(ParserErrorCode.UNEXPECTED_TOKEN, code, '}}}}'); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_statement_eventBinding_unexpected_closing_brackets_at_start() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='}}handleClick($event)'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(ParserErrorCode.UNEXPECTED_TOKEN, code, '}}'); |
| } |
| |
| Future |
| // ignore: non_constant_identifier_names |
| test_statement_eventBinding_typechecking_after_unexpected_bracket() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(MouseEvent e) { |
| } |
| } |
| '''); |
| final code = r""" |
| <div (click)='}1.length'></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertMultipleErrorsExplicit(htmlSource, code, [ |
| new Tuple4('}1', 1, ParserErrorCode.UNEXPECTED_TOKEN, ['}']), |
| new Tuple4('length', 6, StaticTypeWarningCode.UNDEFINED_GETTER, |
| ['length', 'int']), |
| ]); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_inheritedFields() async { |
| _addDartSource(r''' |
| class BaseComponent { |
| String text; // 1 |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel extends BaseComponent { |
| main() { |
| text.length; |
| } |
| } |
| '''); |
| _addHtmlSource(r""" |
| <div> |
| Hello {{text}}! |
| </div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| expect(ranges, hasLength(1)); |
| _assertElement("text}}").dart.getter.at('text; // 1'); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_inputReference() async { |
| _addDartSource(r''' |
| @Component(selector: 'name-panel', inputs: const ['aaa', 'bbb', 'ccc'], |
| template: r"<div>AAA</div>") |
| class NamePanel { |
| int aaa; |
| int bbb; |
| int ccc; |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NamePanel]) |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <name-panel aaa='1' [bbb]='2' bind-ccc='3' id="someid"></name-panel> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement("aaa=").input.at("aaa', "); |
| _assertElement("bbb]=").input.at("bbb', "); |
| _assertElement("ccc=").input.at("ccc']"); |
| _assertElement("id=").input.inCoreHtml; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_outputReference() async { |
| _addDartSource(r''' |
| @Component(selector: 'name-panel', template: r"<div>AAA</div>") |
| class NamePanel { |
| @Output() EventEmitter aaa; |
| @Output() EventEmitter bbb; |
| @Output() EventEmitter ccc; |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NamePanel]) |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <name-panel aaa='1' (bbb)='2' on-ccc='3'></name-panel> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement("bbb)=").output.at("bbb;"); |
| _assertElement("ccc=").output.at("ccc;"); |
| final search = new ElementSearch((e) => e.localName == "name-panel"); |
| template.ast.accept(search); |
| |
| expect(search.element, isNotNull); |
| expect(search.element.boundDirectives, hasLength(1)); |
| final boundDirective = search.element.boundDirectives.first; |
| expect(boundDirective.outputBindings, hasLength(2)); |
| expect(boundDirective.outputBindings[0].boundOutput.name, 'bbb'); |
| expect(boundDirective.outputBindings[1].boundOutput.name, 'ccc'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_twoWayReference() async { |
| _addDartSource(r''' |
| @Component(selector: 'name-panel', template: r"<div>AAA</div>") |
| class NamePanel { |
| @Input() int value; |
| @Output() EventEmitter<int> valueChange; |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NamePanel]) |
| class TestPanel { |
| int value; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <name-panel [(value)]='value'></name-panel> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement("value)]").input.at("value;"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_localVariable_camelCaseName() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [MyDivComponent]) |
| class TestPanel { |
| void handleClick(String s) {} |
| } |
| @Component(selector: 'myDiv', template: '') |
| class MyDivComponent { |
| String someString = 'asdf'; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <h1 (click)='handleClick(myTargetElement.someString)'> |
| <myDiv #myTargetElement></myDiv> |
| </h1> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("myTargetElement.someString)").local.at("myTargetElement>"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_localVariable_exportAs() async { |
| _addDartSource(r''' |
| @Directive(selector: '[myDirective]', exportAs: 'exportedValue') |
| class MyDirective { |
| String aaa; // 1 |
| } |
| |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [MyDirective]) |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <div myDirective #value='exportedValue'> |
| {{value.aaa}} |
| </div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| _assertElement("myDirective #").selector.at("myDirective]"); |
| _assertElement("value=").local.declaration.type('MyDirective'); |
| _assertElement("exportedValue'>").angular.at("exportedValue')"); |
| _assertElement("value.aaa").local.at("value="); |
| _assertElement("aaa}}").dart.getter.at('aaa; // 1'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_letVariable_in_nonTemplate() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel {} |
| '''); |
| final html = r'''<div let-value></div>'''; |
| _addHtmlSource(html); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| NgParserWarningCode.INVALID_LET_BINDING_IN_NONTEMPLATE, |
| html, |
| 'let-value'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_attributeReference() async { |
| _addDartSource(r''' |
| @Component(selector: 'name-panel', template: r"<div>AAA</div>") |
| class NamePanel { |
| NamePanel(@Attribute("name-panel-attr") String namePanelAttr); |
| } |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NamePanel]) |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <name-panel name-panel-attr="foo"></name-panel> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("name-panel-attr=") |
| .angular |
| .inFileName('/test_panel.dart') |
| .at("namePanelAttr"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_erroroneousTemplate_starHash_noCrash() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel { |
| void handleClick(Element e) {} |
| } |
| '''); |
| _addHtmlSource(r""" |
| <h1 (click)='handleClick(myTargetElement)'> |
| <div *#myTargetElement></div> |
| </h1> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| // no assertion. Just don't crash. |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_localVariable_exportAs_notFound() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel {} |
| '''); |
| final code = r""" |
| <div #value='noSuchExportedValue'> |
| {{value.aaa}} |
| assertErrorInCodeAtPosition fails when it sees multiple errors. |
| this shouldn't err because 'value' should be known as uncheckable. |
| </div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.NO_DIRECTIVE_EXPORTED_BY_SPECIFIED_NAME, |
| code, |
| "noSuchExportedValue"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_localVariable_exportAs_ambiguous() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [Directive1, Directive2]) |
| class TestPanel {} |
| |
| @Directive(selector: '[dir1]', exportAs: 'ambiguous') |
| class Directive1 {} |
| |
| @Directive(selector: '[dir2]', exportAs: 'ambiguous') |
| class Directive2 {} |
| '''); |
| final code = r""" |
| <div dir1 dir2 #value="ambiguous"></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.DIRECTIVE_EXPORTED_BY_AMBIGIOUS, code, 'ambiguous'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_localVariable_scope_forwardReference() async { |
| _addDartSource(r''' |
| import 'dart:html'; |
| |
| @Component(selector: 'aaa', inputs: const ['target'], template: '') |
| class ComponentA { |
| void set target(ComponentB b) {} |
| } |
| |
| @Component(selector: 'bbb', template: '') |
| class ComponentB {} |
| |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: [ComponentA, ComponentB]) |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <div> |
| <aaa [target]='handle'></aaa> |
| <bbb #handle></bbb> |
| </div> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("handle'>").local.at("handle></bbb>").type('ComponentB'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngContent() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html') |
| class TestPanel {} |
| '''); |
| _addHtmlSource(r""" |
| <ng-content></ng-content>> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_iterableElementType() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| MyIterable<String> items = new MyIterable<String>(); |
| } |
| class BaseIterable<T> { |
| Iterator<T> get iterator => <T>[].iterator; |
| } |
| class MyIterable<T> extends BaseIterable<T> { |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li template='ngFor let item of items'> |
| {{item.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("item.").local.at('item of').type('String'); |
| _assertElement("length}}").dart.getter; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_operatorLocalVariable() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> operators = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li *ngFor='let operator of operators'> |
| {{operator.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| expect(template.ranges, hasLength(7)); |
| _assertElement("ngFor=").selector.inFileName('ng_for.dart'); |
| _assertElement("operator of").local.declaration.type('String'); |
| _assertElement("length}}").dart.getter; |
| errorListener.assertNoErrors(); |
| final search = new ElementSearch((e) => e.localName == "li"); |
| template.ast.accept(search); |
| |
| expect(search.element, isNotNull); |
| expect(search.element.templateAttribute, isNotNull); |
| expect(search.element.templateAttribute.boundDirectives, hasLength(1)); |
| final boundDirective = |
| search.element.templateAttribute.boundDirectives.first; |
| expect(boundDirective.inputBindings, hasLength(1)); |
| expect(boundDirective.inputBindings.first.boundInput.name, 'ngForOf'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_operatorLocalVariableVarKeyword() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> operators = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li *ngFor='var operator of operators'> |
| {{operator.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| expect(template.ranges, hasLength(7)); |
| _assertElement("ngFor=").selector.inFileName('ng_for.dart'); |
| _assertElement("operator of").local.declaration.type('String'); |
| _assertElement("length}}").dart.getter; |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_star() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li *ngFor='let item of items; let i = index; let e = even; let o = odd; let f = first; let l = last;'> |
| {{i}} {{item.length}} |
| {{o}} {{e}} {{f}} {{l}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| expect(template.ranges, hasLength(22)); |
| _assertElement("ngFor=").selector.inFileName('ng_for.dart'); |
| _assertElement("item of").local.declaration.type('String'); |
| _assertSelectorElement("of items") |
| .selector |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertInputElement("of items") |
| .input |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertElement("items;").dart.getter.at('items = []'); |
| _assertElement("i = index").local.declaration.type('int'); |
| _assertElement("i}}").local.at('i = index'); |
| _assertElement("item.").local.at('item of'); |
| _assertElement("length}}").dart.getter; |
| _assertElement("e = even").local.declaration.type('bool'); |
| _assertElement("e}}").local.at('e = even'); |
| _assertElement("o = odd").local.declaration.type('bool'); |
| _assertElement("o}}").local.at('o = odd'); |
| _assertElement("f = first").local.declaration.type('bool'); |
| _assertElement("f}}").local.at('f = first'); |
| _assertElement("l = last").local.declaration.type('bool'); |
| _assertElement("l}}").local.at('l = last'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_noStarError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| final code = r""" |
| <li ngFor='let item of items; let i = index'> |
| </li> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE, |
| code, |
| "ngFor"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_customDirective_noStarError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [CustomTemplateDirective]) |
| class TestPanel { |
| } |
| |
| @Directive(selector: '[customTemplateDirective]') |
| class CustomTemplateDirective { |
| CustomTemplateDirective(TemplateRef tpl); |
| } |
| '''); |
| final code = r""" |
| <div customTemplateDirective></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE, |
| code, |
| "<div customTemplateDirective>"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_customDirective_withStarOk() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [CustomTemplateDirective]) |
| class TestPanel { |
| } |
| |
| @Directive(selector: '[customTemplateDirective]') |
| class CustomTemplateDirective { |
| CustomTemplateDirective(TemplateRef tpl); |
| } |
| '''); |
| final code = r""" |
| <div *customTemplateDirective></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_customDirective_asTemplateAttrOk() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [CustomTemplateDirective]) |
| class TestPanel { |
| } |
| |
| @Directive(selector: '[customTemplateDirective]') |
| class CustomTemplateDirective { |
| CustomTemplateDirective(TemplateRef tpl); |
| } |
| '''); |
| final code = r""" |
| <div template="customTemplateDirective"></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_customDirective_starDoesntTakeTemplateError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NotTemplateDirective]) |
| class TestPanel { |
| } |
| |
| @Directive(selector: '[notTemplateDirective]') |
| class NotTemplateDirective { |
| } |
| '''); |
| final code = r""" |
| <div *notTemplateDirective></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, |
| "*notTemplateDirective"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_starNoDirectives() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const []) |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| <div *foo></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, "*foo"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_customDirective_templateDoesntTakeTemplateError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NotTemplateDirective]) |
| class TestPanel { |
| } |
| |
| @Directive(selector: '[notTemplateDirective]') |
| class NotTemplateDirective { |
| } |
| '''); |
| final code = r""" |
| <div template="notTemplateDirective"></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, 'template'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_templateNoDirectives() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const []) |
| class TestPanel { |
| } |
| '''); |
| final code = r""" |
| <div template="foo"></div> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, 'template'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_star_itemHiddenInElement() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <h1 *ngFor='let item of items' [hidden]='item == null'> |
| </h1> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("item == null").local.at('item of items'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_templateAttribute() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li template='ngFor let item of items; let i = index'> |
| {{i}} {{item.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("ngFor let").selector.inFileName('ng_for.dart'); |
| _assertElement("item of").local.declaration.type('String'); |
| _assertSelectorElement("of items") |
| .selector |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertInputElement("of items") |
| .input |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertElement("items;").dart.getter.at('items = []'); |
| _assertElement("i = index").local.declaration.type('int'); |
| _assertElement("i}}").local.at('i = index'); |
| _assertElement("item.").local.at('item of'); |
| _assertElement("length}}").dart.getter; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_templateAttribute2() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li template='ngFor: let item, of = items, let i=index'> |
| {{i}} {{item.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("ngFor:").selector.inFileName('ng_for.dart'); |
| _assertElement("item, of").local.declaration.type('String'); |
| _assertSelectorElement("of = items,") |
| .selector |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertInputElement("of = items,") |
| .input |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertElement("items,").dart.getter.at('items = []'); |
| _assertElement("i=index").local.declaration.type('int'); |
| _assertElement("i}}").local.at('i=index'); |
| _assertElement("item.").local.at('item, of'); |
| _assertElement("length}}").dart.getter; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_templateElement() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <template ngFor let-item [ngForOf]='items' let-i='index'> |
| <li>{{i}} {{item.length}}</li> |
| </template> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("ngFor let").selector.inFileName('ng_for.dart'); |
| _assertElement("item [").local.declaration.type('String'); |
| _assertSelectorElement("ngForOf]") |
| .selector |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertInputElement("ngForOf]") |
| .input |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertElement("items'").dart.getter.at('items = []'); |
| _assertElement("i='index").local.declaration.type('int'); |
| _assertElement("i}}").local.at("i='index"); |
| _assertElement("item.").local.at('item ['); |
| _assertElement("length}}").dart.getter; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_letVar_template_cascading() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor, FoobarDirective]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| @Directive(selector: '[foobar]') |
| class FoobarDirective { |
| @Input() |
| String foobar; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <template ngFor let-item [ngForOf]='items' let-i='index'> |
| <template [foobar]="item"></template> |
| </template> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement("ngFor let").selector.inFileName('ng_for.dart'); |
| _assertElement("item [").local.declaration.type('String'); |
| _assertSelectorElement("ngForOf]") |
| .selector |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertInputElement("ngForOf]") |
| .input |
| .name('ngForOf') |
| .inFileName('ng_for.dart'); |
| _assertElement("items'").dart.getter.at('items = []'); |
| _assertElement("i='index").local.declaration.type('int'); |
| _assertElement("item").local.at('item ['); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_hashRef_templateElement() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [HasTemplateInputComponent]) |
| class TestPanel { |
| } |
| @Component(selector: 'has-template-input', template: '') |
| class HasTemplateInputComponent { |
| @Input() |
| TemplateRef myTemplate; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <template #someTemplate></template> |
| <has-template-input [myTemplate]="someTemplate"></has-template-input> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertElement('someTemplate"').local.at('someTemplate>'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| // Future test_ngFor_variousKinds_useLowerIdentifier() async { |
| // _addDartSource(r''' |
| //@Component(selector: 'test-panel') |
| //@View(templateUrl: 'test_panel.html', directives: const [NgFor]) |
| //class TestPanel { |
| // List<String> items = []; |
| //} |
| //'''); |
| // _addHtmlSource(r""" |
| //<template ngFor let-item1 [ngForOf]='items' let-i='index' {{lowerEl}}> |
| // {{item1.length}} |
| //</template> |
| //<li template="ngFor let item2 of items; let i=index" {{lowerEl}}> |
| // {{item2.length}} |
| //</li> |
| //<li *ngFor="let item3 of items; let i=index" {{lowerEl}}> |
| // {{item3.length}} |
| //</li> |
| //<div #lowerEl></div> |
| //"""); |
| // await _resolveSingleTemplate(dartSource); |
| // errorListener.assertNoErrors(); |
| // } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngFor_hash_instead_of_let() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> items = []; |
| } |
| '''); |
| final code = r""" |
| <li *ngFor='#item of items; let i = index'> |
| </li> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.UNEXPECTED_HASH_IN_TEMPLATE, code, "#"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngForSugar_dartExpression() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List<String> getItems(int unused) => []; |
| int unused; |
| } |
| '''); |
| _addHtmlSource(r""" |
| <li template="ngFor let item1 of getItems(unused + 5); let i=index"> |
| {{item1.length}} |
| </li> |
| <li *ngFor="let item2 of getItems(unused + 5); let i=index"> |
| {{item2.length}} |
| </li> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngForSugar_noDartExpressionError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| } |
| '''); |
| final code = r''' |
| <li *ngFor="let item of"></li> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(AngularWarningCode.EMPTY_BINDING, code, 'of'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngForSugar_noTrackByExpressionError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgFor]) |
| class TestPanel { |
| List items; |
| } |
| '''); |
| final code = r''' |
| <li *ngFor="let item of items; trackBy:"></li> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.EMPTY_BINDING, code, 'trackBy'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngIf_star() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgIf]) |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span *ngIf='text.length != 0'></span> |
| """); |
| await _resolveSingleTemplate(dartSource); |
| errorListener.assertNoErrors(); |
| _assertSelectorElement("ngIf=").selector.inFileName('ng_if.dart'); |
| _assertInputElement("ngIf=").input.inFileName('ng_if.dart'); |
| _assertElement("text.").dart.getter.at('text; // 1'); |
| _assertElement("length != 0").dart.getter; |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngIf_noStarError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgIf]) |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r""" |
| <span ngIf='text.length != 0'></span> |
| """; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition( |
| AngularWarningCode.STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE, |
| code, |
| "ngIf"); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngIf_emptyStarError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgIf]) |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r''' |
| <span *ngIf=""></span> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| // TODO would be nice if this selected "" |
| assertErrorInCodeAtPosition(AngularWarningCode.EMPTY_BINDING, code, 'ngIf'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngIf_starNoAttrError() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgIf]) |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| final code = r''' |
| <span *ngIf></span> |
| '''; |
| _addHtmlSource(code); |
| await _resolveSingleTemplate(dartSource); |
| assertErrorInCodeAtPosition(AngularWarningCode.EMPTY_BINDING, code, 'ngIf'); |
| } |
| |
| // ignore: non_constant_identifier_names |
| Future test_ngIf_templateAttribute() async { |
| _addDartSource(r''' |
| @Component(selector: 'test-panel', templateUrl: 'test_panel.html', |
| directives: const [NgIf]) |
| class TestPanel { |
| String text; // 1 |
| } |
| '''); |
| _addHtmlSource(r""" |
| <span template='ngIf text.length != 0'></span> |
| """); |
|