test(ivy): move host binding tests to acceptance (#30308)
Switches over all host binding tests to use `TestBed` and to be under `acceptance`. PR Close #30308
This commit is contained in:
parent
7c8a62d64d
commit
d9b9ed56b6
|
@ -6,7 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Component, ComponentFactoryResolver, ComponentRef, Directive, HostBinding, Input, NgModule, ViewChild, ViewContainerRef} from '@angular/core';
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {AfterContentInit, Component, ComponentFactoryResolver, ComponentRef, ContentChildren, Directive, DoCheck, HostBinding, HostListener, Injectable, Input, NgModule, OnChanges, OnInit, QueryList, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||||
|
import {bypassSanitizationTrustHtml, bypassSanitizationTrustUrl} from '@angular/core/src/sanitization/bypass';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
|
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
|
||||||
|
|
||||||
|
@ -296,4 +298,774 @@ describe('host bindings', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@Directive({selector: '[hostBindingDir]'})
|
||||||
|
class HostBindingDir {
|
||||||
|
@HostBinding()
|
||||||
|
id = 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should support host bindings in directives', () => {
|
||||||
|
@Directive({selector: '[dir]'})
|
||||||
|
class Dir {
|
||||||
|
@HostBinding('className')
|
||||||
|
klass = 'foo';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<span dir></span>'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(Dir) directiveInstance !: Dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, Dir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
const element = fixture.nativeElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.innerHTML).toContain('class="foo"');
|
||||||
|
|
||||||
|
fixture.componentInstance.directiveInstance.klass = 'bar';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.innerHTML).toContain('class="bar"');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should support host bindings on root component', () => {
|
||||||
|
@Component({template: ''})
|
||||||
|
class HostBindingComp {
|
||||||
|
@HostBinding()
|
||||||
|
title = 'my-title';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [HostBindingComp]});
|
||||||
|
const fixture = TestBed.createComponent(HostBindingComp);
|
||||||
|
const element = fixture.nativeElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.title).toBe('my-title');
|
||||||
|
|
||||||
|
fixture.componentInstance.title = 'other-title';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.title).toBe('other-title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings on nodes with providers', () => {
|
||||||
|
@Injectable()
|
||||||
|
class ServiceOne {
|
||||||
|
value = 'one';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
class ServiceTwo {
|
||||||
|
value = 'two';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '', providers: [ServiceOne, ServiceTwo]})
|
||||||
|
class App {
|
||||||
|
constructor(public serviceOne: ServiceOne, public serviceTwo: ServiceTwo) {}
|
||||||
|
|
||||||
|
@HostBinding()
|
||||||
|
title = 'my-title';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
const element = fixture.nativeElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(element.title).toBe('my-title');
|
||||||
|
expect(fixture.componentInstance.serviceOne.value).toEqual('one');
|
||||||
|
expect(fixture.componentInstance.serviceTwo.value).toEqual('two');
|
||||||
|
|
||||||
|
fixture.componentInstance.title = 'other-title';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(element.title).toBe('other-title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings on multiple nodes', () => {
|
||||||
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'host-title-comp', template: ''})
|
||||||
|
class HostTitleComp {
|
||||||
|
@HostBinding()
|
||||||
|
title = 'my-title';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div hostBindingDir></div>
|
||||||
|
<div someDir></div>
|
||||||
|
<host-title-comp></host-title-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, SomeDir, HostTitleComp, HostBindingDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
const element = fixture.nativeElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingDiv = element.querySelector('div') as HTMLElement;
|
||||||
|
const hostTitleComp = element.querySelector('host-title-comp') as HTMLElement;
|
||||||
|
expect(hostBindingDiv.id).toEqual('foo');
|
||||||
|
expect(hostTitleComp.title).toEqual('my-title');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingDir !.id = 'bar';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingDiv.id).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support consecutive components with host bindings', () => {
|
||||||
|
@Component({selector: 'host-binding-comp', template: ''})
|
||||||
|
class HostBindingComp {
|
||||||
|
@HostBinding()
|
||||||
|
id = 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<host-binding-comp></host-binding-comp>
|
||||||
|
<host-binding-comp></host-binding-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChildren(HostBindingComp) hostBindingComp !: QueryList<HostBindingComp>;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingComp]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const comps = fixture.componentInstance.hostBindingComp.toArray();
|
||||||
|
|
||||||
|
const hostBindingEls =
|
||||||
|
fixture.nativeElement.querySelectorAll('host-binding-comp') as NodeListOf<HTMLElement>;
|
||||||
|
|
||||||
|
expect(hostBindingEls.length).toBe(2);
|
||||||
|
|
||||||
|
comps[0].id = 'red';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEls[0].id).toBe('red');
|
||||||
|
|
||||||
|
// second element should not be affected
|
||||||
|
expect(hostBindingEls[1].id).toBe('blue');
|
||||||
|
|
||||||
|
comps[1].id = 'red';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// now second element should take updated value
|
||||||
|
expect(hostBindingEls[1].id).toBe('red');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should support dirs with host bindings on the same node as dirs without host bindings',
|
||||||
|
() => {
|
||||||
|
@Directive({selector: '[someDir]'})
|
||||||
|
class SomeDir {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<div someDir hostBindingDir></div>'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, SomeDir, HostBindingDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingDiv = fixture.nativeElement.querySelector('div') as HTMLElement;
|
||||||
|
expect(hostBindingDiv.id).toEqual('foo');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingDir !.id = 'bar';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingDiv.id).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
it('should support host bindings that rely on values from init hooks', () => {
|
||||||
|
@Component({template: '', selector: 'init-hook-comp'})
|
||||||
|
class InitHookComp implements OnInit, OnChanges, DoCheck {
|
||||||
|
@Input()
|
||||||
|
inputValue = '';
|
||||||
|
|
||||||
|
changesValue = '';
|
||||||
|
initValue = '';
|
||||||
|
checkValue = '';
|
||||||
|
|
||||||
|
ngOnChanges() { this.changesValue = 'changes'; }
|
||||||
|
|
||||||
|
ngOnInit() { this.initValue = 'init'; }
|
||||||
|
|
||||||
|
ngDoCheck() { this.checkValue = 'check'; }
|
||||||
|
|
||||||
|
@HostBinding('title')
|
||||||
|
get value() {
|
||||||
|
return `${this.inputValue}-${this.changesValue}-${this.initValue}-${this.checkValue}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<init-hook-comp [inputValue]="value"></init-hook-comp>'})
|
||||||
|
class App {
|
||||||
|
value = 'input';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, InitHookComp]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const initHookComp = fixture.nativeElement.querySelector('init-hook-comp') as HTMLElement;
|
||||||
|
expect(initHookComp.title).toEqual('input-changes-init-check');
|
||||||
|
|
||||||
|
fixture.componentInstance.value = 'input2';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(initHookComp.title).toEqual('input2-changes-init-check');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings with the same name as inputs', () => {
|
||||||
|
@Directive({selector: '[hostBindingDir]'})
|
||||||
|
class HostBindingInputDir {
|
||||||
|
@Input()
|
||||||
|
disabled = false;
|
||||||
|
|
||||||
|
@HostBinding('disabled')
|
||||||
|
hostDisabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<input hostBindingDir [disabled]="isDisabled">'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingInputDir) hostBindingInputDir !: HostBindingInputDir;
|
||||||
|
isDisabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingInputDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const hostBindingInputDir = fixture.componentInstance.hostBindingInputDir;
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('input') as HTMLInputElement;
|
||||||
|
expect(hostBindingInputDir.disabled).toBe(true);
|
||||||
|
expect(hostBindingEl.disabled).toBe(false);
|
||||||
|
|
||||||
|
fixture.componentInstance.isDisabled = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingInputDir.disabled).toBe(false);
|
||||||
|
expect(hostBindingEl.disabled).toBe(false);
|
||||||
|
|
||||||
|
hostBindingInputDir.hostDisabled = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingInputDir.disabled).toBe(false);
|
||||||
|
expect(hostBindingEl.disabled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings on second template pass', () => {
|
||||||
|
@Component({selector: 'parent', template: '<div hostBindingDir></div>'})
|
||||||
|
class Parent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<parent></parent>
|
||||||
|
<parent></parent>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, Parent, HostBindingDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const divs = fixture.nativeElement.querySelectorAll('div');
|
||||||
|
expect(divs[0].id).toEqual('foo');
|
||||||
|
expect(divs[1].id).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings in for loop', () => {
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div *ngFor="let row of rows">
|
||||||
|
<p hostBindingDir></p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
rows: number[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({imports: [CommonModule], declarations: [App, HostBindingDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.componentInstance.rows = [1, 2, 3];
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const paragraphs = fixture.nativeElement.querySelectorAll('p');
|
||||||
|
expect(paragraphs[0].id).toEqual('foo');
|
||||||
|
expect(paragraphs[1].id).toEqual('foo');
|
||||||
|
expect(paragraphs[2].id).toEqual('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support component with host bindings and array literals', () => {
|
||||||
|
@Component({selector: 'host-binding-comp', template: ''})
|
||||||
|
class HostBindingComp {
|
||||||
|
@HostBinding()
|
||||||
|
id = 'my-id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'name-comp', template: ''})
|
||||||
|
class NameComp {
|
||||||
|
@Input()
|
||||||
|
names !: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<name-comp [names]="['Nancy', name, 'Ned']"></name-comp>
|
||||||
|
<host-binding-comp></host-binding-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild(NameComp) nameComp !: NameComp;
|
||||||
|
name = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingComp, NameComp]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const nameComp = fixture.componentInstance.nameComp;
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
fixture.componentInstance.name = 'Betty';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEl.id).toBe('my-id');
|
||||||
|
expect(nameComp.names).toEqual(['Nancy', 'Betty', 'Ned']);
|
||||||
|
|
||||||
|
const firstArray = nameComp.names;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(firstArray).toBe(nameComp.names);
|
||||||
|
|
||||||
|
fixture.componentInstance.name = 'my-id';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEl.id).toBe('my-id');
|
||||||
|
expect(nameComp.names).toEqual(['Nancy', 'my-id', 'Ned']);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Note: This is a contrived example. For feature parity with render2, we should make sure it
|
||||||
|
// works in this way (see https://stackblitz.com/edit/angular-cbqpbe), but a more realistic
|
||||||
|
// example would be an animation host binding with a literal defining the animation config.
|
||||||
|
// When animation support is added, we should add another test for that case.
|
||||||
|
it('should support host bindings that contain array literals', () => {
|
||||||
|
@Component({selector: 'name-comp', template: ''})
|
||||||
|
class NameComp {
|
||||||
|
@Input()
|
||||||
|
names !: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'host-binding-comp',
|
||||||
|
host: {'[id]': `['red', id]`, '[dir]': `dir`, '[title]': `[title, otherTitle]`},
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
class HostBindingComp {
|
||||||
|
id = 'blue';
|
||||||
|
dir = 'ltr';
|
||||||
|
title = 'my title';
|
||||||
|
otherTitle = 'other title';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<name-comp [names]="[name, 'Nancy', otherName]"></name-comp>
|
||||||
|
<host-binding-comp></host-binding-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
|
||||||
|
@ViewChild(NameComp) nameComp !: NameComp;
|
||||||
|
name = '';
|
||||||
|
otherName = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingComp, NameComp]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const {nameComp, hostBindingComp} = fixture.componentInstance;
|
||||||
|
|
||||||
|
fixture.componentInstance.name = 'Frank';
|
||||||
|
fixture.componentInstance.otherName = 'Joe';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
expect(hostBindingEl.id).toBe('red,blue');
|
||||||
|
expect(hostBindingEl.dir).toBe('ltr');
|
||||||
|
expect(hostBindingEl.title).toBe('my title,other title');
|
||||||
|
expect(nameComp !.names).toEqual(['Frank', 'Nancy', 'Joe']);
|
||||||
|
|
||||||
|
const firstArray = nameComp !.names;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(firstArray).toBe(nameComp !.names);
|
||||||
|
|
||||||
|
hostBindingComp.id = 'green';
|
||||||
|
hostBindingComp.dir = 'rtl';
|
||||||
|
hostBindingComp.title = 'TITLE';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEl.id).toBe('red,green');
|
||||||
|
expect(hostBindingEl.dir).toBe('rtl');
|
||||||
|
expect(hostBindingEl.title).toBe('TITLE,other title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support directives with and without allocHostVars on the same component', () => {
|
||||||
|
let events: string[] = [];
|
||||||
|
|
||||||
|
@Directive({selector: '[hostDir]', host: {'[title]': `[title, 'other title']`}})
|
||||||
|
class HostBindingDir {
|
||||||
|
title = 'my title';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[hostListenerDir]'})
|
||||||
|
class HostListenerDir {
|
||||||
|
@HostListener('click')
|
||||||
|
onClick() { events.push('click!'); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<button hostListenerDir hostDir>Click</button>'})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingDir, HostListenerDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const button = fixture.nativeElement.querySelector('button') !;
|
||||||
|
button.click();
|
||||||
|
expect(events).toEqual(['click!']);
|
||||||
|
expect(button.title).toEqual('my title,other title');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings with literals from multiple directives', () => {
|
||||||
|
@Component({selector: 'host-binding-comp', host: {'[id]': `['red', id]`}, template: ''})
|
||||||
|
class HostBindingComp {
|
||||||
|
id = 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[hostDir]', host: {'[title]': `[title, 'other title']`}})
|
||||||
|
class HostBindingDir {
|
||||||
|
title = 'my title';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<host-binding-comp hostDir></host-binding-comp>'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
|
||||||
|
@ViewChild(HostBindingDir) hostBindingDir !: HostBindingDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingComp, HostBindingDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const hostElement = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
expect(hostElement.id).toBe('red,blue');
|
||||||
|
expect(hostElement.title).toBe('my title,other title');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingDir.title = 'blue';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostElement.title).toBe('blue,other title');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingComp.id = 'green';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostElement.id).toBe('red,green');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support ternary expressions in host bindings', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'host-binding-comp',
|
||||||
|
template: '',
|
||||||
|
host: {
|
||||||
|
// Use `attr` since IE doesn't support the `title` property on all elements.
|
||||||
|
'[attr.id]': `condition ? ['red', id] : 'green'`,
|
||||||
|
'[attr.title]': `otherCondition ? [title] : 'other title'`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class HostBindingComp {
|
||||||
|
condition = true;
|
||||||
|
otherCondition = true;
|
||||||
|
id = 'blue';
|
||||||
|
title = 'blue';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: `<host-binding-comp></host-binding-comp>{{ name }}`})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingComp) hostBindingComp !: HostBindingComp;
|
||||||
|
name = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingComp]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostElement = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
fixture.componentInstance.name = 'Ned';
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
// Note that we assert for each binding individually, rather than checking against
|
||||||
|
// innerHTML, because IE10 changes the attribute order and makes it inconsistent with
|
||||||
|
// all other browsers.
|
||||||
|
expect(hostElement.id).toBe('red,blue');
|
||||||
|
expect(hostElement.title).toBe('blue');
|
||||||
|
expect(fixture.nativeElement.innerHTML.endsWith('Ned')).toBe(true);
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingComp.condition = false;
|
||||||
|
fixture.componentInstance.hostBindingComp.title = 'TITLE';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostElement.id).toBe('green');
|
||||||
|
expect(hostElement.title).toBe('TITLE');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingComp.otherCondition = false;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostElement.id).toBe('green');
|
||||||
|
expect(hostElement.title).toBe('other title');
|
||||||
|
});
|
||||||
|
|
||||||
|
onlyInIvy('Host bindings do not get merged in ViewEngine')
|
||||||
|
.it('should work correctly with inherited directives with hostBindings', () => {
|
||||||
|
@Directive({selector: '[superDir]', host: {'[id]': 'id'}})
|
||||||
|
class SuperDirective {
|
||||||
|
id = 'my-id';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[subDir]', host: {'[title]': 'title'}})
|
||||||
|
class SubDirective extends SuperDirective {
|
||||||
|
title = 'my-title';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<div subDir></div>
|
||||||
|
<div superDir></div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild(SubDirective) subDir !: SubDirective;
|
||||||
|
@ViewChild(SuperDirective) superDir !: SuperDirective;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, SuperDirective, SubDirective]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const els = fixture.nativeElement.querySelectorAll('div') as NodeListOf<HTMLElement>;
|
||||||
|
|
||||||
|
const firstDivEl = els[0];
|
||||||
|
const secondDivEl = els[1];
|
||||||
|
|
||||||
|
// checking first div element with inherited directive
|
||||||
|
expect(firstDivEl.id).toEqual('my-id');
|
||||||
|
expect(firstDivEl.title).toEqual('my-title');
|
||||||
|
|
||||||
|
fixture.componentInstance.subDir.title = 'new-title';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(firstDivEl.id).toEqual('my-id');
|
||||||
|
expect(firstDivEl.title).toEqual('new-title');
|
||||||
|
|
||||||
|
fixture.componentInstance.subDir.id = 'new-id';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(firstDivEl.id).toEqual('new-id');
|
||||||
|
expect(firstDivEl.title).toEqual('new-title');
|
||||||
|
|
||||||
|
// checking second div element with simple directive
|
||||||
|
expect(secondDivEl.id).toEqual('my-id');
|
||||||
|
|
||||||
|
fixture.componentInstance.superDir.id = 'new-id';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(secondDivEl.id).toEqual('new-id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host attributes', () => {
|
||||||
|
@Directive({selector: '[hostAttributeDir]', host: {'role': 'listbox'}})
|
||||||
|
class HostAttributeDir {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<div hostAttributeDir></div>'})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostAttributeDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(`role="listbox"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support content children in host bindings', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'host-binding-comp',
|
||||||
|
template: '<ng-content></ng-content>',
|
||||||
|
host: {'[id]': 'foos.length'}
|
||||||
|
})
|
||||||
|
class HostBindingWithContentChildren {
|
||||||
|
@ContentChildren('foo')
|
||||||
|
foos !: QueryList<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<host-binding-comp>
|
||||||
|
<div #foo></div>
|
||||||
|
<div #foo></div>
|
||||||
|
</host-binding-comp>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingWithContentChildren]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
expect(hostBindingEl.id).toEqual('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support host bindings dependent on content hooks', () => {
|
||||||
|
@Component({selector: 'host-binding-comp', template: '', host: {'[id]': 'myValue'}})
|
||||||
|
class HostBindingWithContentHooks implements AfterContentInit {
|
||||||
|
myValue = 'initial';
|
||||||
|
|
||||||
|
ngAfterContentInit() { this.myValue = 'after-content'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<host-binding-comp></host-binding-comp>'})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingWithContentHooks]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('host-binding-comp') as HTMLElement;
|
||||||
|
expect(hostBindingEl.id).toEqual('after-content');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('styles', () => {
|
||||||
|
|
||||||
|
it('should bind to host styles', () => {
|
||||||
|
@Component(
|
||||||
|
{selector: 'host-binding-to-styles', host: {'[style.width.px]': 'width'}, template: ''})
|
||||||
|
class HostBindingToStyles {
|
||||||
|
width = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<host-binding-to-styles></host-binding-to-styles>'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingToStyles) hostBindingDir !: HostBindingToStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingToStyles]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl =
|
||||||
|
fixture.nativeElement.querySelector('host-binding-to-styles') as HTMLElement;
|
||||||
|
expect(hostBindingEl.style.width).toEqual('2px');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingDir.width = 5;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEl.style.width).toEqual('5px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should bind to host styles on containers', () => {
|
||||||
|
@Directive({selector: '[hostStyles]', host: {'[style.width.px]': 'width'}})
|
||||||
|
class HostBindingToStyles {
|
||||||
|
width = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Directive({selector: '[containerDir]'})
|
||||||
|
class ContainerDir {
|
||||||
|
constructor(public vcr: ViewContainerRef) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<div hostStyles containerDir></div>'})
|
||||||
|
class App {
|
||||||
|
@ViewChild(HostBindingToStyles) hostBindingDir !: HostBindingToStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, HostBindingToStyles, ContainerDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('div') as HTMLElement;
|
||||||
|
expect(hostBindingEl.style.width).toEqual('2px');
|
||||||
|
|
||||||
|
fixture.componentInstance.hostBindingDir.width = 5;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(hostBindingEl.style.width).toEqual('5px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply static host classes', () => {
|
||||||
|
@Component({selector: 'static-host-class', host: {'class': 'mat-toolbar'}, template: ''})
|
||||||
|
class StaticHostClass {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: '<static-host-class></static-host-class>'})
|
||||||
|
class App {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, StaticHostClass]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const hostBindingEl = fixture.nativeElement.querySelector('static-host-class') as HTMLElement;
|
||||||
|
expect(hostBindingEl.className).toEqual('mat-toolbar');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sanitization', () => {
|
||||||
|
function verify(
|
||||||
|
tag: string, prop: string, value: any, expectedSanitizedValue: any, bypassFn: any,
|
||||||
|
isAttribute: boolean = true) {
|
||||||
|
it('should sanitize potentially unsafe properties and attributes', () => {
|
||||||
|
@Directive({
|
||||||
|
selector: '[unsafeUrlHostBindingDir]',
|
||||||
|
host: {
|
||||||
|
[`[${isAttribute ? 'attr.' : ''}${prop}]`]: 'value',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
class UnsafeDir {
|
||||||
|
value: any = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({template: `<${tag} unsafeUrlHostBindingDir></${tag}>`})
|
||||||
|
class App {
|
||||||
|
@ViewChild(UnsafeDir) unsafeDir !: UnsafeDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [App, UnsafeDir]});
|
||||||
|
const fixture = TestBed.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
const el = fixture.nativeElement.querySelector(tag) !;
|
||||||
|
const current = () => isAttribute ? el.getAttribute(prop) : (el as any)[prop];
|
||||||
|
|
||||||
|
fixture.componentInstance.unsafeDir.value = value;
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(current()).toEqual(expectedSanitizedValue);
|
||||||
|
|
||||||
|
fixture.componentInstance.unsafeDir.value = bypassFn(value);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(current()).toEqual(expectedSanitizedValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(
|
||||||
|
'a', 'href', 'javascript:alert(1)', 'unsafe:javascript:alert(1)',
|
||||||
|
bypassSanitizationTrustUrl);
|
||||||
|
verify(
|
||||||
|
'blockquote', 'cite', 'javascript:alert(2)', 'unsafe:javascript:alert(2)',
|
||||||
|
bypassSanitizationTrustUrl);
|
||||||
|
verify(
|
||||||
|
'b', 'innerHTML', '<img src="javascript:alert(3)">',
|
||||||
|
'<img src="unsafe:javascript:alert(3)">', bypassSanitizationTrustHtml,
|
||||||
|
/* isAttribute */ false);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue