From 090eac068a4cb17ab71eec7ce6c7e57f2643c2ab Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Sun, 12 May 2019 21:29:53 +0200 Subject: [PATCH] test(ivy): move property render3 tests to acceptance (#30426) Moves all manual render3 property binding tests to TestBed acceptance tests. Unfortunately three property binding tests could not be migrated as these rely on manual Ivy template code that is not supported within TestBed. These can be revisited as discussed in the framework team meeting. PR Close #30426 --- .../test/acceptance/property_binding_spec.ts | 465 ++++++++++++- packages/core/test/render3/properties_spec.ts | 621 ------------------ 2 files changed, 464 insertions(+), 622 deletions(-) delete mode 100644 packages/core/test/render3/properties_spec.ts 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
`); - }); - - }); - -});