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( - `
button
button
`); - }); - - }); - -});