diff --git a/packages/core/test/acceptance/property_binding_spec.ts b/packages/core/test/acceptance/property_binding_spec.ts
index 1bfe65e1b7..8c96cfed80 100644
--- a/packages/core/test/acceptance/property_binding_spec.ts
+++ b/packages/core/test/acceptance/property_binding_spec.ts
@@ -5,11 +5,30 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-import {Component, Input} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {Component, Directive, EventEmitter, Input, Output} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By, DomSanitizer, SafeUrl} from '@angular/platform-browser';
describe('property bindings', () => {
+ it('should support bindings to properties', () => {
+ @Component({template: ``})
+ class Comp {
+ id: string|undefined;
+ }
+
+ TestBed.configureTestingModule({declarations: [Comp]});
+ const fixture = TestBed.createComponent(Comp);
+ const spanEl = fixture.nativeElement.querySelector('span');
+
+ expect(spanEl.id).toBeFalsy();
+
+ fixture.componentInstance.id = 'testId';
+ fixture.detectChanges();
+
+ expect(spanEl.id).toBe('testId');
+ });
+
it('should update bindings when value changes', () => {
@Component({
template: ``,
@@ -135,4 +154,448 @@ describe('property bindings', () => {
expect(fixture.debugElement.query(By.css('input')).nativeElement.required).toBe(false);
});
+
+ it('should support interpolation for properties', () => {
+ @Component({template: ``})
+ class Comp {
+ id: string|undefined;
+ }
+
+ TestBed.configureTestingModule({declarations: [Comp]});
+ const fixture = TestBed.createComponent(Comp);
+ const spanEl = fixture.nativeElement.querySelector('span');
+
+ fixture.componentInstance.id = 'testId';
+ fixture.detectChanges();
+ expect(spanEl.id).toBe('_testId_');
+
+ fixture.componentInstance.id = 'otherId';
+ fixture.detectChanges();
+ expect(spanEl.id).toBe('_otherId_');
+ });
+
+ describe('input properties', () => {
+ @Directive({
+ selector: '[myButton]',
+ })
+ class MyButton {
+ @Input() disabled: boolean|undefined;
+ }
+
+ @Directive({
+ selector: '[otherDir]',
+ })
+ class OtherDir {
+ @Input() id: number|undefined;
+ @Output('click') clickStream = new EventEmitter();
+ }
+
+ @Directive({
+ selector: '[otherDisabledDir]',
+ })
+ class OtherDisabledDir {
+ @Input() disabled: boolean|undefined;
+ }
+
+ @Directive({
+ selector: '[idDir]',
+ })
+ class IdDir {
+ @Input('id') idNumber: string|undefined;
+ }
+
+ it('should check input properties before setting (directives)', () => {
+ @Component({
+ template: ``
+ })
+ class App {
+ id = 0;
+ isDisabled = true;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyButton, OtherDir]});
+ const fixture = TestBed.createComponent(App);
+ const button = fixture.debugElement.query(By.directive(MyButton)).injector.get(MyButton);
+ const otherDir = fixture.debugElement.query(By.directive(OtherDir)).injector.get(OtherDir);
+ const buttonEl = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(buttonEl.getAttribute('mybutton')).toBe('');
+ expect(buttonEl.getAttribute('otherdir')).toBe('');
+ expect(buttonEl.hasAttribute('id')).toBe(false);
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(true);
+ expect(otherDir.id).toEqual(0);
+
+ fixture.componentInstance.isDisabled = false;
+ fixture.componentInstance.id = 1;
+ fixture.detectChanges();
+
+ expect(buttonEl.getAttribute('mybutton')).toBe('');
+ expect(buttonEl.getAttribute('otherdir')).toBe('');
+ expect(buttonEl.hasAttribute('id')).toBe(false);
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(false);
+ expect(otherDir.id).toEqual(1);
+ });
+
+ it('should support mixed element properties and input properties', () => {
+ @Component({template: ``})
+ class App {
+ isDisabled = true;
+ id = 0;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyButton]});
+ const fixture = TestBed.createComponent(App);
+ const button = fixture.debugElement.query(By.directive(MyButton)).injector.get(MyButton);
+ const buttonEl = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(buttonEl.getAttribute('id')).toBe('0');
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(true);
+
+ fixture.componentInstance.isDisabled = false;
+ fixture.componentInstance.id = 1;
+ fixture.detectChanges();
+
+ expect(buttonEl.getAttribute('id')).toBe('1');
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(false);
+ });
+
+ it('should check that property is not an input property before setting (component)', () => {
+ @Component({
+ selector: 'comp',
+ template: '',
+ })
+ class Comp {
+ @Input() id: number|undefined;
+ }
+
+ @Component({template: ``})
+ class App {
+ id = 1;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, Comp]});
+ const fixture = TestBed.createComponent(App);
+ const compDebugEl = fixture.debugElement.query(By.directive(Comp));
+ fixture.detectChanges();
+
+ expect(compDebugEl.nativeElement.hasAttribute('id')).toBe(false);
+ expect(compDebugEl.componentInstance.id).toEqual(1);
+
+ fixture.componentInstance.id = 2;
+ fixture.detectChanges();
+
+ expect(compDebugEl.nativeElement.hasAttribute('id')).toBe(false);
+ expect(compDebugEl.componentInstance.id).toEqual(2);
+ });
+
+ it('should support two input properties with the same name', () => {
+ @Component(
+ {template: ``})
+ class App {
+ isDisabled = true;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyButton, OtherDisabledDir]});
+ const fixture = TestBed.createComponent(App);
+ const button = fixture.debugElement.query(By.directive(MyButton)).injector.get(MyButton);
+ const otherDisabledDir =
+ fixture.debugElement.query(By.directive(OtherDisabledDir)).injector.get(OtherDisabledDir);
+ const buttonEl = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(true);
+ expect(otherDisabledDir.disabled).toEqual(true);
+
+ fixture.componentInstance.isDisabled = false;
+ fixture.detectChanges();
+
+ expect(buttonEl.hasAttribute('disabled')).toBe(false);
+ expect(button.disabled).toEqual(false);
+ expect(otherDisabledDir.disabled).toEqual(false);
+ });
+
+ it('should set input property if there is an output first', () => {
+ @Component({
+ template: ``,
+ })
+ class App {
+ id = 1;
+ counter = 0;
+ onClick = () => this.counter++;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, OtherDir]});
+ const fixture = TestBed.createComponent(App);
+ const otherDir = fixture.debugElement.query(By.directive(OtherDir)).injector.get(OtherDir);
+ const buttonEl = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(buttonEl.hasAttribute('id')).toBe(false);
+ expect(otherDir.id).toEqual(1);
+
+ otherDir.clickStream.next();
+ expect(fixture.componentInstance.counter).toEqual(1);
+
+ fixture.componentInstance.id = 2;
+ fixture.detectChanges();
+ expect(otherDir.id).toEqual(2);
+ });
+
+ it('should support unrelated element properties at same index in if-else block', () => {
+ @Component({
+ template: `
+
+
+
+ `
+ })
+ class App {
+ condition = true;
+ id1 = 'one';
+ id2 = 'two';
+ id3 = 3;
+ }
+
+ TestBed.configureTestingModule(
+ {declarations: [App, IdDir, OtherDir], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ let buttonElements = fixture.nativeElement.querySelectorAll('button');
+ const idDir = fixture.debugElement.query(By.directive(IdDir)).injector.get(IdDir);
+
+ expect(buttonElements.length).toBe(2);
+ expect(buttonElements[0].hasAttribute('id')).toBe(false);
+ expect(buttonElements[1].getAttribute('id')).toBe('two');
+ expect(buttonElements[1].textContent).toBe('Click me too (2)');
+ expect(idDir.idNumber).toBe('one');
+
+ fixture.componentInstance.condition = false;
+ fixture.componentInstance.id1 = 'four';
+ fixture.detectChanges();
+
+ const otherDir = fixture.debugElement.query(By.directive(OtherDir)).injector.get(OtherDir);
+ buttonElements = fixture.nativeElement.querySelectorAll('button');
+ expect(buttonElements.length).toBe(2);
+ expect(buttonElements[0].hasAttribute('id')).toBe(false);
+ expect(buttonElements[1].hasAttribute('id')).toBe(false);
+ expect(buttonElements[1].textContent).toBe('Click me too (3)');
+ expect(idDir.idNumber).toBe('four');
+ expect(otherDir.id).toBe(3);
+ });
+ });
+
+ describe('attributes and input properties', () => {
+
+ @Directive({selector: '[myDir]', exportAs: 'myDir'})
+ class MyDir {
+ @Input() role: string|undefined;
+ @Input('dir') direction: string|undefined;
+ @Output('change') changeStream = new EventEmitter();
+ }
+
+ @Directive({selector: '[myDirB]'})
+ class MyDirB {
+ @Input('role') roleB: string|undefined;
+ }
+
+ it('should set input property based on attribute if existing', () => {
+ @Component({template: `
`})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const divElement = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(divElement.getAttribute('role')).toBe('button');
+ expect(divElement.getAttribute('mydir')).toBe('');
+ expect(myDir.role).toEqual('button');
+ });
+
+ it('should set input property and attribute if both defined', () => {
+ @Component({template: ``})
+ class App {
+ role = 'listbox';
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const divElement = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(divElement.getAttribute('role')).toBe('button');
+ expect(myDir.role).toEqual('listbox');
+
+ fixture.componentInstance.role = 'button';
+ fixture.detectChanges();
+ expect(myDir.role).toEqual('button');
+ });
+
+ it('should set two directive input properties based on same attribute', () => {
+ @Component({template: ``})
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir, MyDirB]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const myDirB = fixture.debugElement.query(By.directive(MyDirB)).injector.get(MyDirB);
+ const divElement = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(divElement.getAttribute('role')).toBe('button');
+ expect(myDir.role).toEqual('button');
+ expect(myDirB.roleB).toEqual('button');
+ });
+
+ it('should process two attributes on same directive', () => {
+ @Component({
+ template: ``,
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const divElement = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(divElement.getAttribute('role')).toBe('button');
+ expect(divElement.getAttribute('dir')).toBe('rtl');
+ expect(myDir.role).toEqual('button');
+ expect(myDir.direction).toEqual('rtl');
+ });
+
+ it('should process attributes and outputs properly together', () => {
+ @Component({template: ``})
+ class App {
+ counter = 0;
+ onChange = () => this.counter++;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const divElement = fixture.nativeElement.children[0];
+ fixture.detectChanges();
+
+ expect(divElement.getAttribute('role')).toBe('button');
+ expect(myDir.role).toEqual('button');
+
+ myDir.changeStream.next();
+ expect(fixture.componentInstance.counter).toEqual(1);
+ });
+
+ it('should process attributes properly for directives with later indices', () => {
+ @Component({
+ template: `
+
+
+ `,
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir, MyDirB]});
+ const fixture = TestBed.createComponent(App);
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const myDirB = fixture.debugElement.query(By.directive(MyDirB)).injector.get(MyDirB);
+ const [buttonEl, listboxEl] = fixture.nativeElement.children;
+ fixture.detectChanges();
+
+ expect(buttonEl.getAttribute('role')).toBe('button');
+ expect(buttonEl.getAttribute('dir')).toBe('rtl');
+ expect(listboxEl.getAttribute('role')).toBe('listbox');
+
+ expect(myDir.role).toEqual('button');
+ expect(myDir.direction).toEqual('rtl');
+ expect(myDirB.roleB).toEqual('listbox');
+ });
+
+ it('should support attributes at same index inside an if-else block', () => {
+ @Component({
+ template: `
+
+
+
+ `,
+ })
+ class App {
+ condition = true;
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir, MyDirB], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+ const myDir = fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir);
+ const myDirB = fixture.debugElement.query(By.directive(MyDirB)).injector.get(MyDirB);
+ let divElements = fixture.nativeElement.querySelectorAll('div');
+
+ expect(divElements.length).toBe(2);
+ expect(divElements[0].getAttribute('role')).toBe('listbox');
+ expect(divElements[1].getAttribute('role')).toBe('button');
+ expect(myDir.role).toEqual('listbox');
+ expect(myDirB.roleB).toEqual('button');
+ expect((myDirB as any).role).toBeUndefined();
+
+ fixture.componentInstance.condition = false;
+ fixture.detectChanges();
+
+ divElements = fixture.nativeElement.querySelectorAll('div');
+ expect(divElements.length).toBe(2);
+ expect(divElements[0].getAttribute('role')).toBe('listbox');
+ expect(divElements[1].getAttribute('role')).toBe('menu');
+ expect(myDir.role).toEqual('listbox');
+ expect(myDirB.roleB).toEqual('button');
+ });
+
+ it('should process attributes properly inside a for loop', () => {
+ @Component({
+ selector: 'comp',
+ template: `role: {{dir.role}}`
+ })
+ class Comp {
+ }
+
+ @Component({
+ template: `
+
+ `
+ })
+ class App {
+ }
+
+ TestBed.configureTestingModule({declarations: [App, MyDir, Comp], imports: [CommonModule]});
+ const fixture = TestBed.createComponent(App);
+ fixture.detectChanges();
+
+ expect(fixture.nativeElement.children.length).toBe(2);
+
+ const [comp1, comp2] = fixture.nativeElement.children;
+
+ expect(comp1.tagName).toBe('COMP');
+ expect(comp2.tagName).toBe('COMP');
+
+ expect(comp1.children[0].tagName).toBe('DIV');
+ expect(comp1.children[0].getAttribute('role')).toBe('button');
+ expect(comp1.textContent).toBe('role: button');
+
+ expect(comp2.children[0].tagName).toBe('DIV');
+ expect(comp2.children[0].getAttribute('role')).toBe('button');
+ expect(comp2.textContent).toBe('role: button');
+ });
+
+ });
+
});
diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts
deleted file mode 100644
index d7efadec1a..0000000000
--- a/packages/core/test/render3/properties_spec.ts
+++ /dev/null
@@ -1,621 +0,0 @@
-/**
- * @license
- * Copyright Google Inc. All Rights Reserved.
- *
- * Use of this source code is governed by an MIT-style license that can be
- * found in the LICENSE file at https://angular.io/license
- */
-
-import {EventEmitter} from '@angular/core';
-
-import {ɵɵdefineComponent, ɵɵdefineDirective} from '../../src/render3/index';
-import {ɵɵbind, ɵɵcontainer, ɵɵcontainerRefreshEnd, ɵɵcontainerRefreshStart, ɵɵelement, ɵɵelementEnd, ɵɵelementProperty, ɵɵelementStart, ɵɵembeddedViewEnd, ɵɵembeddedViewStart, ɵɵinterpolation1, ɵɵlistener, ɵɵload, ɵɵreference, ɵɵtext, ɵɵtextBinding} from '../../src/render3/instructions/all';
-import {RenderFlags} from '../../src/render3/interfaces/definition';
-
-import {ComponentFixture, createComponent, renderToHtml} from './render_util';
-
-describe('elementProperty', () => {
-
- it('should support bindings to properties', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'span');
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id));
- }
- }, 1, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.id = 'testId';
- fixture.update();
- expect(fixture.html).toEqual('');
-
- fixture.component.id = 'otherId';
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- it('should support creation time bindings to properties', () => {
- function expensive(ctx: string): any {
- if (ctx === 'cheapId') {
- return ctx;
- } else {
- throw 'Too expensive!';
- }
- }
-
- function Template(rf: RenderFlags, ctx: string) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'span');
- ɵɵelementProperty(0, 'id', expensive(ctx));
- }
- }
-
- expect(renderToHtml(Template, 'cheapId', 1)).toEqual('');
- expect(renderToHtml(Template, 'expensiveId', 1)).toEqual('');
- });
-
- it('should support interpolation for properties', () => {
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'span');
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵinterpolation1('_', ctx.id, '_'));
- }
- }, 1, 1);
-
- const fixture = new ComponentFixture(App);
- fixture.component.id = 'testId';
- fixture.update();
- expect(fixture.html).toEqual('');
-
- fixture.component.id = 'otherId';
- fixture.update();
- expect(fixture.html).toEqual('');
- });
-
- describe('input properties', () => {
- let button: MyButton;
- let otherDir: OtherDir;
- let otherDisabledDir: OtherDisabledDir;
- let idDir: IdDir;
-
- class MyButton {
- // TODO(issue/24571): remove '!'.
- disabled !: boolean;
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: MyButton,
- selectors: [['', 'myButton', '']],
- factory: () => button = new MyButton(),
- inputs: {disabled: 'disabled'}
- });
- }
-
- class OtherDir {
- // TODO(issue/24571): remove '!'.
- id !: number;
- clickStream = new EventEmitter();
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: OtherDir,
- selectors: [['', 'otherDir', '']],
- factory: () => otherDir = new OtherDir(),
- inputs: {id: 'id'},
- outputs: {clickStream: 'click'}
- });
- }
-
- class OtherDisabledDir {
- // TODO(issue/24571): remove '!'.
- disabled !: boolean;
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: OtherDisabledDir,
- selectors: [['', 'otherDisabledDir', '']],
- factory: () => otherDisabledDir = new OtherDisabledDir(),
- inputs: {disabled: 'disabled'}
- });
- }
-
- class IdDir {
- // TODO(issue/24571): remove '!'.
- idNumber !: string;
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: IdDir,
- selectors: [['', 'idDir', '']],
- factory: () => idDir = new IdDir(),
- inputs: {idNumber: 'id'}
- });
- }
-
-
- const deps = [MyButton, OtherDir, OtherDisabledDir, IdDir];
-
- it('should check input properties before setting (directives)', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['otherDir', '', 'myButton', '']);
- { ɵɵtext(1, 'Click me'); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'disabled', ɵɵbind(ctx.isDisabled));
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id));
- }
- }, 2, 2, deps);
-
- const fixture = new ComponentFixture(App);
- fixture.component.isDisabled = true;
- fixture.component.id = 0;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(true);
- expect(otherDir !.id).toEqual(0);
-
- fixture.component.isDisabled = false;
- fixture.component.id = 1;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(false);
- expect(otherDir !.id).toEqual(1);
- });
-
- it('should support mixed element properties and input properties', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['myButton', '']);
- { ɵɵtext(1, 'Click me'); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'disabled', ɵɵbind(ctx.isDisabled));
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id));
- }
- }, 2, 2, deps);
-
-
- const fixture = new ComponentFixture(App);
- fixture.component.isDisabled = true;
- fixture.component.id = 0;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(true);
-
- fixture.component.isDisabled = false;
- fixture.component.id = 1;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(false);
- });
-
- it('should check that property is not an input property before setting (component)', () => {
- let comp: Comp;
-
- class Comp {
- // TODO(issue/24571): remove '!'.
- id !: number;
-
- static ngComponentDef = ɵɵdefineComponent({
- type: Comp,
- selectors: [['comp']],
- consts: 0,
- vars: 0,
- template: function(rf: RenderFlags, ctx: any) {},
- factory: () => comp = new Comp(),
- inputs: {id: 'id'}
- });
- }
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'comp');
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id));
- }
- }, 1, 1, [Comp]);
-
- const fixture = new ComponentFixture(App);
- fixture.component.id = 1;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(comp !.id).toEqual(1);
-
- fixture.component.id = 2;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(comp !.id).toEqual(2);
- });
-
- it('should support two input properties with the same name', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['myButton', '', 'otherDisabledDir', '']);
- { ɵɵtext(1, 'Click me'); }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'disabled', ɵɵbind(ctx.isDisabled));
- }
- }, 2, 1, deps);
-
- const fixture = new ComponentFixture(App);
- fixture.component.isDisabled = true;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(true);
- expect(otherDisabledDir !.disabled).toEqual(true);
-
- fixture.component.isDisabled = false;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(button !.disabled).toEqual(false);
- expect(otherDisabledDir !.disabled).toEqual(false);
- });
-
- it('should set input property if there is an output first', () => {
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['otherDir', '']);
- {
- ɵɵlistener('click', () => ctx.onClick());
- ɵɵtext(1, 'Click me');
- }
- ɵɵelementEnd();
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id));
- }
- }, 2, 1, deps);
-
- const fixture = new ComponentFixture(App);
- let counter = 0;
- fixture.component.id = 1;
- fixture.component.onClick = () => counter++;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(otherDir !.id).toEqual(1);
-
- otherDir !.clickStream.next();
- expect(counter).toEqual(1);
-
- fixture.component.id = 2;
- fixture.update();
- fixture.html;
- expect(otherDir !.id).toEqual(2);
- });
-
- it('should support unrelated element properties at same index in if-else block', () => {
- /**
- * // inputs: {'id': [0, 'idNumber']}
- * % if (condition) {
- * // inputs: null
- * % } else {
- * // inputs: {'id': [0, 'id']}
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['idDir', '']);
- { ɵɵtext(1, 'Click me'); }
- ɵɵelementEnd();
- ɵɵcontainer(2);
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id1));
- ɵɵcontainerRefreshStart(2);
- {
- if (ctx.condition) {
- let rf0 = ɵɵembeddedViewStart(0, 2, 1);
- if (rf0 & RenderFlags.Create) {
- ɵɵelementStart(0, 'button');
- { ɵɵtext(1, 'Click me too'); }
- ɵɵelementEnd();
- }
- if (rf0 & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id2));
- }
- ɵɵembeddedViewEnd();
- } else {
- let rf1 = ɵɵembeddedViewStart(1, 2, 1);
- if (rf1 & RenderFlags.Create) {
- ɵɵelementStart(0, 'button', ['otherDir', '']);
- { ɵɵtext(1, 'Click me too'); }
- ɵɵelementEnd();
- }
- if (rf1 & RenderFlags.Update) {
- ɵɵelementProperty(0, 'id', ɵɵbind(ctx.id3));
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 3, 1, deps);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.component.id1 = 'one';
- fixture.component.id2 = 'two';
- fixture.component.id3 = 3;
- fixture.update();
- expect(fixture.html)
- .toEqual(``);
- expect(idDir !.idNumber).toEqual('one');
-
- fixture.component.condition = false;
- fixture.component.id1 = 'four';
- fixture.update();
- expect(fixture.html)
- .toEqual(``);
- expect(idDir !.idNumber).toEqual('four');
- expect(otherDir !.id).toEqual(3);
- });
-
- });
-
- describe('attributes and input properties', () => {
- let myDir: MyDir;
- class MyDir {
- // TODO(issue/24571): remove '!'.
- role !: string;
- // TODO(issue/24571): remove '!'.
- direction !: string;
- changeStream = new EventEmitter();
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: MyDir,
- selectors: [['', 'myDir', '']],
- factory: () => myDir = new MyDir(),
- inputs: {role: 'role', direction: 'dir'},
- outputs: {changeStream: 'change'},
- exportAs: ['myDir']
- });
- }
-
- let dirB: MyDirB;
- class MyDirB {
- // TODO(issue/24571): remove '!'.
- roleB !: string;
-
- static ngDirectiveDef = ɵɵdefineDirective({
- type: MyDirB,
- selectors: [['', 'myDirB', '']],
- factory: () => dirB = new MyDirB(),
- inputs: {roleB: 'role'}
- });
- }
-
- const deps = [MyDir, MyDirB];
-
- it('should set input property based on attribute if existing', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'myDir', '']);
- }
- }, 1, 0, deps);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('button');
- });
-
- it('should set input property and attribute if both defined', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'myDir', '']);
- }
- if (rf & RenderFlags.Update) {
- ɵɵelementProperty(0, 'role', ɵɵbind(ctx.role));
- }
- }, 1, 1, deps);
-
- const fixture = new ComponentFixture(App);
- fixture.component.role = 'listbox';
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('listbox');
-
- fixture.component.role = 'button';
- fixture.update();
- expect(myDir !.role).toEqual('button');
- });
-
- it('should set two directive input properties based on same attribute', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'myDir', '', 'myDirB', '']);
- }
- }, 1, 0, deps);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('button');
- expect(dirB !.roleB).toEqual('button');
- });
-
- it('should process two attributes on same directive', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'dir', 'rtl', 'myDir', '']);
- }
- }, 1, 0, deps);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('button');
- expect(myDir !.direction).toEqual('rtl');
- });
-
- it('should process attributes and outputs properly together', () => {
-
- /** */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelementStart(0, 'div', ['role', 'button', 'myDir', '']);
- { ɵɵlistener('change', () => ctx.onChange()); }
- ɵɵelementEnd();
- }
- }, 1, 0, deps);
-
- const fixture = new ComponentFixture(App);
- let counter = 0;
- fixture.component.onChange = () => counter++;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('button');
-
- myDir !.changeStream.next();
- expect(counter).toEqual(1);
- });
-
- it('should process attributes properly for directives with later indices', () => {
-
- /**
- *
- *
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'dir', 'rtl', 'myDir', '']);
- ɵɵelement(1, 'div', ['role', 'listbox', 'myDirB', '']);
- }
- }, 2, 0, deps);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html)
- .toEqual(
- ``);
- expect(myDir !.role).toEqual('button');
- expect(myDir !.direction).toEqual('rtl');
- expect(dirB !.roleB).toEqual('listbox');
- });
-
- it('should support attributes at same index inside an if-else block', () => {
- /**
- * // initialInputs: [['role', 'listbox']]
- *
- * % if (condition) {
- * // initialInputs: [['role', 'button']]
- * % } else {
- * // initialInputs: [null]
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'listbox', 'myDir', '']);
- ɵɵcontainer(1);
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(1);
- {
- if (ctx.condition) {
- let rf1 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'myDirB', '']);
- }
- ɵɵembeddedViewEnd();
- } else {
- let rf2 = ɵɵembeddedViewStart(1, 1, 0);
- if (rf2 & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'menu']);
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 2, 0, deps);
-
- const fixture = new ComponentFixture(App);
- fixture.component.condition = true;
- fixture.update();
- expect(fixture.html)
- .toEqual(``);
- expect(myDir !.role).toEqual('listbox');
- expect(dirB !.roleB).toEqual('button');
- expect((dirB !as any).role).toBeUndefined();
-
- fixture.component.condition = false;
- fixture.update();
- expect(fixture.html).toEqual(``);
- expect(myDir !.role).toEqual('listbox');
- });
-
- it('should process attributes properly inside a for loop', () => {
-
- class Comp {
- static ngComponentDef = ɵɵdefineComponent({
- type: Comp,
- selectors: [['comp']],
- consts: 3,
- vars: 1,
- /** {{ dir.role }} */
- template: function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵelement(0, 'div', ['role', 'button', 'myDir', ''], ['dir', 'myDir']);
- ɵɵtext(2);
- }
- if (rf & RenderFlags.Update) {
- const tmp = ɵɵreference(1) as any;
- ɵɵtextBinding(2, ɵɵbind(tmp.role));
- }
- },
- factory: () => new Comp(),
- directives: () => [MyDir]
- });
- }
-
- /**
- * % for (let i = 0; i < 3; i++) {
- *
- * % }
- */
- const App = createComponent('app', function(rf: RenderFlags, ctx: any) {
- if (rf & RenderFlags.Create) {
- ɵɵcontainer(0);
- }
- if (rf & RenderFlags.Update) {
- ɵɵcontainerRefreshStart(0);
- {
- for (let i = 0; i < 2; i++) {
- let rf1 = ɵɵembeddedViewStart(0, 1, 0);
- if (rf1 & RenderFlags.Create) {
- ɵɵelement(0, 'comp');
- }
- ɵɵembeddedViewEnd();
- }
- }
- ɵɵcontainerRefreshEnd();
- }
- }, 1, 0, [Comp]);
-
- const fixture = new ComponentFixture(App);
- expect(fixture.html)
- .toEqual(
- `buttonbutton`);
- });
-
- });
-
-});