/**
* @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 {CommonModule} from '@angular/common';
import {Attribute, ChangeDetectorRef, Component, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Host, HostBinding, INJECTOR, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, NgZone, Optional, Output, Pipe, PipeTransform, Self, SkipSelf, TemplateRef, ViewChild, ViewContainerRef, forwardRef, ɵDEFAULT_LOCALE_ID as DEFAULT_LOCALE_ID} from '@angular/core';
import {ɵINJECTOR_SCOPE} from '@angular/core/src/core';
import {ViewRef} from '@angular/core/src/render3/view_ref';
import {TestBed} from '@angular/core/testing';
import {ivyEnabled, onlyInIvy} from '@angular/private/testing';
import {BehaviorSubject} from 'rxjs';
describe('di', () => {
describe('no dependencies', () => {
it('should create directive with no deps', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDirective {
value = 'Created';
}
@Component({template: '
'})
class MyComp {
constructor(public myService: MyService) {}
}
TestBed.configureTestingModule({declarations: [MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const divElement = fixture.nativeElement.querySelector('div');
expect(divElement.textContent).toEqual('MyService');
});
it('should support sub-classes with no @Injectable decorator', () => {
@Injectable()
class Dependency {
}
@Injectable()
class SuperClass {
constructor(public dep: Dependency) {}
}
// Note, no @Injectable decorators for these two classes
class SubClass extends SuperClass {}
class SubSubClass extends SubClass {}
@Component({template: ''})
class MyComp {
constructor(public myService: SuperClass) {}
}
TestBed.configureTestingModule({
declarations: [MyComp],
providers: [{provide: SuperClass, useClass: SubSubClass}, Dependency]
});
const warnSpy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(MyComp);
expect(fixture.componentInstance.myService.dep instanceof Dependency).toBe(true);
if (ivyEnabled) {
expect(warnSpy).toHaveBeenCalledWith(
`DEPRECATED: DI is instantiating a token "SubSubClass" that inherits its @Injectable decorator but does not provide one itself.\n` +
`This will become an error in v10. Please add @Injectable() to the "SubSubClass" class.`);
}
});
it('should instantiate correct class when undecorated class extends an injectable', () => {
@Injectable()
class MyService {
id = 1;
}
class MyRootService extends MyService {
id = 2;
}
@Component({template: ''})
class App {
}
TestBed.configureTestingModule({declarations: [App], providers: [MyRootService]});
const warnSpy = spyOn(console, 'warn');
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const provider = TestBed.inject(MyRootService);
expect(provider instanceof MyRootService).toBe(true);
expect(provider.id).toBe(2);
if (ivyEnabled) {
expect(warnSpy).toHaveBeenCalledWith(
`DEPRECATED: DI is instantiating a token "MyRootService" that inherits its @Injectable decorator but does not provide one itself.\n` +
`This will become an error in v10. Please add @Injectable() to the "MyRootService" class.`);
}
});
});
describe('inject', () => {
it('should inject from parent view', () => {
@Directive({selector: '[parentDir]'})
class ParentDirective {
}
@Directive({selector: '[childDir]', exportAs: 'childDir'})
class ChildDirective {
value: string;
constructor(public parent: ParentDirective) { this.value = parent.constructor.name; }
}
@Directive({selector: '[child2Dir]', exportAs: 'child2Dir'})
class Child2Directive {
value: boolean;
constructor(parent: ParentDirective, child: ChildDirective) {
this.value = parent === child.parent;
}
}
@Component({
template: `
{{ child1.value }}-{{ child2.value }}
`
})
class MyComp {
showing = true;
}
TestBed.configureTestingModule(
{declarations: [ParentDirective, ChildDirective, Child2Directive, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const divElement = fixture.nativeElement.querySelector('div');
expect(divElement.textContent).toBe('ParentDirective-true');
});
});
describe('Special tokens', () => {
describe('Injector', () => {
it('should inject the injector', () => {
@Directive({selector: '[injectorDir]'})
class InjectorDir {
constructor(public injector: Injector) {}
}
@Directive({selector: '[otherInjectorDir]'})
class OtherInjectorDir {
constructor(public otherDir: InjectorDir, public injector: Injector) {}
}
@Component({template: ''})
class MyComp {
@ViewChild(InjectorDir) injectorDir !: InjectorDir;
@ViewChild(OtherInjectorDir) otherInjectorDir !: OtherInjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, OtherInjectorDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const divElement = fixture.nativeElement.querySelector('div');
const injectorDir = fixture.componentInstance.injectorDir;
const otherInjectorDir = fixture.componentInstance.otherInjectorDir;
expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(otherInjectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(otherInjectorDir.injector.get(InjectorDir)).toBe(injectorDir);
expect(injectorDir.injector).not.toBe(otherInjectorDir.injector);
});
it('should inject INJECTOR', () => {
@Directive({selector: '[injectorDir]'})
class InjectorDir {
constructor(@Inject(INJECTOR) public injector: Injector) {}
}
@Component({template: ''})
class MyComp {
@ViewChild(InjectorDir) injectorDir !: InjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const divElement = fixture.nativeElement.querySelector('div');
const injectorDir = fixture.componentInstance.injectorDir;
expect(injectorDir.injector.get(ElementRef).nativeElement).toBe(divElement);
expect(injectorDir.injector.get(Injector).get(ElementRef).nativeElement).toBe(divElement);
expect(injectorDir.injector.get(INJECTOR).get(ElementRef).nativeElement).toBe(divElement);
});
});
describe('ElementRef', () => {
it('should create directive with ElementRef dependencies', () => {
@Directive({selector: '[dir]'})
class MyDir {
value: string;
constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name;
}
}
@Directive({selector: '[otherDir]'})
class MyOtherDir {
isSameInstance: boolean;
constructor(public elementRef: ElementRef, public directive: MyDir) {
this.isSameInstance = elementRef === directive.elementRef;
}
}
@Component({template: ''})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const divElement = fixture.nativeElement.querySelector('div');
const directive = fixture.componentInstance.directive;
const otherDirective = fixture.componentInstance.otherDirective;
expect(directive.value).toContain('ElementRef');
expect(directive.elementRef.nativeElement).toEqual(divElement);
expect(otherDirective.elementRef.nativeElement).toEqual(divElement);
// Each ElementRef instance should be unique
expect(otherDirective.isSameInstance).toBe(false);
});
it('should create ElementRef with comment if requesting directive is on node',
() => {
@Directive({selector: '[dir]'})
class MyDir {
value: string;
constructor(public elementRef: ElementRef) {
this.value = (elementRef.constructor as any).name;
}
}
@Component({template: ''})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directive;
expect(directive.value).toContain('ElementRef');
// the nativeElement should be a comment
expect(directive.elementRef.nativeElement.nodeType).toEqual(Node.COMMENT_NODE);
});
it('should be available if used in conjunction with other tokens', () => {
@Injectable()
class ServiceA {
subject: any;
constructor(protected zone: NgZone) {
this.subject = new BehaviorSubject(1);
// trigger change detection
zone.run(() => { this.subject.next(2); });
}
}
@Directive({selector: '[dir]'})
class DirectiveA {
constructor(public service: ServiceA, public elementRef: ElementRef) {}
}
@Component({
selector: 'child',
template: ``,
})
class ChildComp {
@ViewChild(DirectiveA) directive !: DirectiveA;
}
@Component({
selector: 'root',
template: '...',
})
class RootComp {
public childCompRef !: ComponentRef;
constructor(
public factoryResolver: ComponentFactoryResolver, public vcr: ViewContainerRef) {}
create() {
const factory = this.factoryResolver.resolveComponentFactory(ChildComp);
this.childCompRef = this.vcr.createComponent(factory);
this.childCompRef.changeDetectorRef.detectChanges();
}
}
// this module is needed, so that View Engine can generate factory for ChildComp
@NgModule({
declarations: [DirectiveA, RootComp, ChildComp],
entryComponents: [RootComp, ChildComp],
})
class ModuleA {
}
TestBed.configureTestingModule({
imports: [ModuleA],
providers: [ServiceA],
});
const fixture = TestBed.createComponent(RootComp);
fixture.autoDetectChanges();
fixture.componentInstance.create();
const {elementRef} = fixture.componentInstance.childCompRef.instance.directive;
expect(elementRef.nativeElement.id).toBe('test-id');
});
});
describe('TemplateRef', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDir {
value: string;
constructor(public templateRef: TemplateRef) {
this.value = (templateRef.constructor as any).name;
}
}
onlyInIvy('Ivy creates a unique instance of TemplateRef for each directive')
.it('should create directive with TemplateRef dependencies', () => {
@Directive({selector: '[otherDir]', exportAs: 'otherDir'})
class MyOtherDir {
isSameInstance: boolean;
constructor(public templateRef: TemplateRef, public directive: MyDir) {
this.isSameInstance = templateRef === directive.templateRef;
}
}
@Component({
template: ''
})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directive;
const otherDirective = fixture.componentInstance.otherDirective;
expect(directive.value).toContain('TemplateRef');
expect(directive.templateRef).not.toBeNull();
expect(otherDirective.templateRef).not.toBeNull();
// Each TemplateRef instance should be unique
expect(otherDirective.isSameInstance).toBe(false);
});
it('should throw if injected on an element', () => {
@Component({template: ''})
class MyComp {
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for TemplateRef/);
});
it('should throw if injected on an ng-container', () => {
@Component({template: ''})
class MyComp {
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
expect(() => TestBed.createComponent(MyComp)).toThrowError(/No provider for TemplateRef/);
});
it('should NOT throw if optional and injected on an element', () => {
@Directive({selector: '[optionalDir]', exportAs: 'optionalDir'})
class OptionalDir {
constructor(@Optional() public templateRef: TemplateRef) {}
}
@Component({template: ''})
class MyComp {
@ViewChild(OptionalDir) directive !: OptionalDir;
}
TestBed.configureTestingModule({declarations: [OptionalDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.directive.templateRef).toBeNull();
});
});
describe('ViewContainerRef', () => {
onlyInIvy('Ivy creates a unique instance of ViewContainerRef for each directive')
.it('should create directive with ViewContainerRef dependencies', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDir {
value: string;
constructor(public viewContainerRef: ViewContainerRef) {
this.value = (viewContainerRef.constructor as any).name;
}
}
@Directive({selector: '[otherDir]', exportAs: 'otherDir'})
class MyOtherDir {
isSameInstance: boolean;
constructor(public viewContainerRef: ViewContainerRef, public directive: MyDir) {
this.isSameInstance = viewContainerRef === directive.viewContainerRef;
}
}
@Component({template: ''})
class MyComp {
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyOtherDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directive;
const otherDirective = fixture.componentInstance.otherDirective;
expect(directive.value).toContain('ViewContainerRef');
expect(directive.viewContainerRef).not.toBeNull();
expect(otherDirective.viewContainerRef).not.toBeNull();
// Each ViewContainerRef instance should be unique
expect(otherDirective.isSameInstance).toBe(false);
});
it('should sync ViewContainerRef state between all injected instances', () => {
@Component({
selector: 'root',
template: `Test`,
})
class Root {
@ViewChild(TemplateRef, {static: true})
tmpl !: TemplateRef;
constructor(public vcr: ViewContainerRef, public vcr2: ViewContainerRef) {}
ngOnInit(): void { this.vcr.createEmbeddedView(this.tmpl); }
}
TestBed.configureTestingModule({
declarations: [Root],
});
const fixture = TestBed.createComponent(Root);
fixture.detectChanges();
const cmp = fixture.componentInstance;
expect(cmp.vcr.length).toBe(1);
expect(cmp.vcr2.length).toBe(1);
expect(cmp.vcr2.get(0)).toEqual(cmp.vcr.get(0));
cmp.vcr2.remove(0);
expect(cmp.vcr.length).toBe(0);
expect(cmp.vcr.get(0)).toBeNull();
});
});
describe('ChangeDetectorRef', () => {
@Directive({selector: '[dir]', exportAs: 'dir'})
class MyDir {
value: string;
constructor(public cdr: ChangeDetectorRef) { this.value = (cdr.constructor as any).name; }
}
@Directive({selector: '[otherDir]', exportAs: 'otherDir'})
class MyOtherDir {
constructor(public cdr: ChangeDetectorRef) {}
}
@Component({selector: 'my-comp', template: ''})
class MyComp {
constructor(public cdr: ChangeDetectorRef) {}
}
it('should inject host component ChangeDetectorRef into directives on templates', () => {
let pipeInstance: MyPipe;
@Pipe({name: 'pipe'})
class MyPipe implements PipeTransform {
constructor(public cdr: ChangeDetectorRef) { pipeInstance = this; }
transform(value: any): any { return value; }
}
@Component({
selector: 'my-app',
template: `
Visible
`,
})
class MyApp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
}
TestBed.configureTestingModule({declarations: [MyApp, MyPipe], imports: [CommonModule]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
expect((pipeInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance);
});
it('should inject current component ChangeDetectorRef into directives on the same node as components',
() => {
@Component({selector: 'my-app', template: ''})
class MyApp {
@ViewChild(MyComp) component !: MyComp;
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
const app = fixture.componentInstance;
const comp = fixture.componentInstance.component;
expect((comp !.cdr as ViewRef).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(app.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(app.directive !.cdr).not.toBe(comp !.cdr);
expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr);
});
it('should inject host component ChangeDetectorRef into directives on normal elements',
() => {
@Component({selector: 'my-comp', template: ''})
class MyComp {
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
});
it('should inject host component ChangeDetectorRef into directives in a component\'s ContentChildren',
() => {
@Component({
selector: 'my-app',
template: `
`
})
class MyApp {
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyComp) component !: MyComp;
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyApp, MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
const app = fixture.componentInstance;
expect((app !.cdr as ViewRef).context).toBe(app);
const comp = fixture.componentInstance.component;
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(app.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(app.directive !.cdr).not.toBe(comp.cdr);
expect(app.directive !.cdr).not.toBe(app.otherDirective !.cdr);
});
it('should inject host component ChangeDetectorRef into directives in embedded views', () => {
@Component({
selector: 'my-comp',
template: ``
})
class MyComp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
});
it('should inject host component ChangeDetectorRef into directives on containers', () => {
@Component(
{selector: 'my-comp', template: ''})
class MyComp {
showing = true;
constructor(public cdr: ChangeDetectorRef) {}
@ViewChild(MyDir) directive !: MyDir;
@ViewChild(MyOtherDir) otherDirective !: MyOtherDir;
}
TestBed.configureTestingModule({declarations: [MyComp, MyDir, MyOtherDir]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const comp = fixture.componentInstance;
expect((comp !.cdr as ViewRef).context).toBe(comp);
// ChangeDetectorRef is the token, ViewRef has historically been the constructor
expect(comp.directive.value).toContain('ViewRef');
// Each ChangeDetectorRef instance should be unique
expect(comp.directive !.cdr).not.toBe(comp.cdr);
expect(comp.directive !.cdr).not.toBe(comp.otherDirective !.cdr);
});
it('should inject host component ChangeDetectorRef into directives on ng-container', () => {
let dirInstance: MyDirective;
@Directive({selector: '[getCDR]'})
class MyDirective {
constructor(public cdr: ChangeDetectorRef) { dirInstance = this; }
}
@Component({
selector: 'my-app',
template: `Visible`,
})
class MyApp {
constructor(public cdr: ChangeDetectorRef) {}
}
TestBed.configureTestingModule({declarations: [MyApp, MyDirective]});
const fixture = TestBed.createComponent(MyApp);
fixture.detectChanges();
expect((dirInstance !.cdr as ViewRef).context).toBe(fixture.componentInstance);
});
});
});
describe('string tokens', () => {
it('should be able to provide a string token', () => {
@Directive({selector: '[injectorDir]', providers: [{provide: 'test', useValue: 'provided'}]})
class InjectorDir {
constructor(@Inject('test') public value: string) {}
}
@Component({template: ''})
class MyComp {
@ViewChild(InjectorDir) injectorDirInstance !: InjectorDir;
}
TestBed.configureTestingModule({declarations: [InjectorDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const injectorDir = fixture.componentInstance.injectorDirInstance;
expect(injectorDir.value).toBe('provided');
});
});
it('should not cause cyclic dependency if same token is requested in deps with @SkipSelf', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{
provide: LOCALE_ID,
useFactory: () => 'ja-JP',
// Note: `LOCALE_ID` is also provided within APPLICATION_MODULE_PROVIDERS, so we use it here
// as a dep and making sure it doesn't cause cyclic dependency (since @SkipSelf is present)
deps: [[new Inject(LOCALE_ID), new Optional(), new SkipSelf()]]
}]
})
class MyComp {
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.localeId).toBe('ja-JP');
});
it('module-level deps should not access Component/Directive providers', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{
provide: 'LOCALE_ID_DEP', //
useValue: 'LOCALE_ID_DEP_VALUE'
}]
})
class MyComp {
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({
declarations: [MyComp],
providers: [{
provide: LOCALE_ID,
// we expect `localeDepValue` to be undefined, since it's not provided at a module level
useFactory: (localeDepValue: any) => localeDepValue || 'en-GB',
deps: [[new Inject('LOCALE_ID_DEP'), new Optional()]]
}]
});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.localeId).toBe('en-GB');
});
it('should skip current level while retrieving tokens if @SkipSelf is defined', () => {
@Component({
selector: 'my-comp',
template: '...',
providers: [{provide: LOCALE_ID, useFactory: () => 'en-GB'}]
})
class MyComp {
constructor(@SkipSelf() @Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
// takes `LOCALE_ID` from module injector, since we skip Component level with @SkipSelf
expect(fixture.componentInstance.localeId).toBe(DEFAULT_LOCALE_ID);
});
it('should work when injecting dependency in Directives', () => {
@Directive({
selector: '[dir]', //
providers: [{provide: LOCALE_ID, useValue: 'ja-JP'}]
})
class MyDir {
constructor(@SkipSelf() @Inject(LOCALE_ID) public localeId: string) {}
}
@Component({
selector: 'my-comp',
template: '',
providers: [{provide: LOCALE_ID, useValue: 'en-GB'}]
})
class MyComp {
@ViewChild(MyDir) myDir !: MyDir;
constructor(@Inject(LOCALE_ID) public localeId: string) {}
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.componentInstance.myDir.localeId).toBe('en-GB');
});
describe('@Attribute', () => {
it('should inject attributes', () => {
@Directive({selector: '[dir]'})
class MyDir {
constructor(
@Attribute('exist') public exist: string,
@Attribute('nonExist') public nonExist: string) {}
}
@Component({template: ''})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.exist).toBe('existValue');
expect(directive.nonExist).toBeNull();
});
it('should inject attributes on ', () => {
@Directive({selector: '[dir]'})
class MyDir {
constructor(
@Attribute('exist') public exist: string,
@Attribute('dir') public myDirectiveAttrValue: string) {}
}
@Component(
{template: ''})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.exist).toBe('existValue');
expect(directive.myDirectiveAttrValue).toBe('initial');
});
it('should inject attributes on ', () => {
@Directive({selector: '[dir]'})
class MyDir {
constructor(
@Attribute('exist') public exist: string,
@Attribute('dir') public myDirectiveAttrValue: string) {}
}
@Component({
template: ''
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.exist).toBe('existValue');
expect(directive.myDirectiveAttrValue).toBe('initial');
});
it('should be able to inject different kinds of attributes', () => {
@Directive({selector: '[dir]'})
class MyDir {
constructor(
@Attribute('class') public className: string,
@Attribute('style') public inlineStyles: string,
@Attribute('other-attr') public otherAttr: string) {}
}
@Component({
template:
''
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.otherAttr).toBe('value');
expect(directive.className).toBe('hello there');
expect(directive.inlineStyles).toMatch(/color:\s*red/);
expect(directive.inlineStyles).toMatch(/margin:\s*1px/);
});
it('should not inject attributes with namespace', () => {
@Directive({selector: '[dir]'})
class MyDir {
constructor(
@Attribute('exist') public exist: string,
@Attribute('svg:exist') public namespacedExist: string,
@Attribute('other') public other: string) {}
}
@Component({
template: ''
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.exist).toBe('existValue');
expect(directive.namespacedExist).toBeNull();
expect(directive.other).toBe('otherValue');
});
it('should not inject attributes representing bindings and outputs', () => {
@Directive({selector: '[dir]'})
class MyDir {
@Input() binding !: string;
@Output() output = new EventEmitter();
constructor(
@Attribute('exist') public exist: string,
@Attribute('binding') public bindingAttr: string,
@Attribute('output') public outputAttr: string,
@Attribute('other') public other: string) {}
}
@Component({
template:
''
})
class MyComp {
@ViewChild(MyDir) directiveInstance !: MyDir;
}
TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
const directive = fixture.componentInstance.directiveInstance;
expect(directive.exist).toBe('existValue');
expect(directive.bindingAttr).toBeNull();
expect(directive.outputAttr).toBeNull();
expect(directive.other).toBe('otherValue');
});
});
it('should support dependencies in Pipes used inside ICUs', () => {
@Injectable()
class MyService {
transform(value: string): string { return `${value} (transformed)`; }
}
@Pipe({name: 'somePipe'})
class MyPipe {
constructor(private service: MyService) {}
transform(value: any): any { return this.service.transform(value); }
}
@Component({
template: `
{
count, select,
=1 {One}
other {Other value is: {{count | somePipe}}}
}
`
})
class MyComp {
count = '2';
}
TestBed.configureTestingModule({
declarations: [MyPipe, MyComp],
providers: [MyService],
});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(fixture.nativeElement.innerHTML).toContain('Other value is: 2 (transformed)');
});
it('should support dependencies in Pipes used inside i18n blocks', () => {
@Injectable()
class MyService {
transform(value: string): string { return `${value} (transformed)`; }
}
@Pipe({name: 'somePipe'})
class MyPipe {
constructor(private service: MyService) {}
transform(value: any): any { return this.service.transform(value); }
}
@Component({
template: `
{{count | somePipe}} items
`
})
class MyComp {
count = '2';
@ViewChild('target', {read: ViewContainerRef}) target !: ViewContainerRef;
@ViewChild('source', {read: TemplateRef}) source !: TemplateRef;
create() { this.target.createEmbeddedView(this.source); }
}
TestBed.configureTestingModule({
declarations: [MyPipe, MyComp],
providers: [MyService],
});
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
fixture.componentInstance.create();
fixture.detectChanges();
expect(fixture.nativeElement.textContent.trim()).toBe('2 (transformed) items');
});
// TODO: https://angular-team.atlassian.net/browse/FW-1779
it('should prioritize useFactory over useExisting', () => {
abstract class Base {}
@Directive({selector: '[dirA]'})
class DirA implements Base {
}
@Directive({selector: '[dirB]'})
class DirB implements Base {
}
const PROVIDER = {provide: Base, useExisting: DirA, useFactory: () => new DirB()};
@Component({selector: 'child', template: '', providers: [PROVIDER]})
class Child {
constructor(readonly base: Base) {}
}
@Component({template: `
`})
class App {
@ViewChild(DirA) dirA !: DirA;
@ViewChild(Child) child !: Child;
}
const fixture = TestBed.configureTestingModule({declarations: [DirA, DirB, App, Child]})
.createComponent(App);
fixture.detectChanges();
expect(fixture.componentInstance.dirA)
.not.toEqual(
fixture.componentInstance.child.base,
'should not get dirA from parent, but create new dirB from the useFactory provider');
});
});