393 lines
12 KiB
TypeScript
393 lines
12 KiB
TypeScript
/**
|
|
* @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 {MockDirectory, setup} from '@angular/compiler/test/aot/test_util';
|
|
import {compile, expectEmit} from './mock_compile';
|
|
|
|
describe('compiler compliance: dependency injection', () => {
|
|
const angularFiles = setup({
|
|
compileAngular: false,
|
|
compileFakeCore: true,
|
|
compileAnimations: false,
|
|
});
|
|
|
|
it('should create factory methods', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Component, NgModule, Injectable, Attribute, Host, SkipSelf, Self, Optional} from '@angular/core';
|
|
|
|
@Injectable()
|
|
export class MyService {}
|
|
|
|
@Component({
|
|
selector: 'my-component',
|
|
template: \`\`
|
|
})
|
|
export class MyComponent {
|
|
constructor(
|
|
@Attribute('name') name:string,
|
|
s1: MyService,
|
|
@Host() s2: MyService,
|
|
@Self() s4: MyService,
|
|
@SkipSelf() s3: MyService,
|
|
@Optional() s5: MyService,
|
|
@Self() @Optional() s6: MyService,
|
|
) {}
|
|
}
|
|
|
|
@NgModule({declarations: [MyComponent], providers: [MyService]})
|
|
export class MyModule {}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
MyComponent.ɵfac = function MyComponent_Factory(t) {
|
|
return new (t || MyComponent)(
|
|
$r3$.ɵɵinjectAttribute('name'),
|
|
$r3$.ɵɵdirectiveInject(MyService),
|
|
$r3$.ɵɵdirectiveInject(MyService, 1),
|
|
$r3$.ɵɵdirectiveInject(MyService, 2),
|
|
$r3$.ɵɵdirectiveInject(MyService, 4),
|
|
$r3$.ɵɵdirectiveInject(MyService, 8),
|
|
$r3$.ɵɵdirectiveInject(MyService, 10)
|
|
);
|
|
}`;
|
|
|
|
|
|
const result = compile(files, angularFiles);
|
|
|
|
expectEmit(result.source, factory, 'Incorrect factory');
|
|
});
|
|
|
|
it('should create a factory definition for an injectable', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
class MyDependency {}
|
|
|
|
@Injectable()
|
|
export class MyService {
|
|
constructor(dep: MyDependency) {}
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
MyService.ɵfac = function MyService_Factory(t) {
|
|
return new (t || MyService)($r3$.ɵɵinject(MyDependency));
|
|
}`;
|
|
|
|
const def = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: MyService.ɵfac
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
|
});
|
|
|
|
it('should create a factory definition for an injectable with an overloaded constructor', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable, Optional} from '@angular/core';
|
|
|
|
class MyDependency {}
|
|
class MyOptionalDependency {}
|
|
|
|
@Injectable()
|
|
export class MyService {
|
|
constructor(dep: MyDependency);
|
|
constructor(dep: MyDependency, @Optional() optionalDep?: MyOptionalDependency) {}
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
MyService.ɵfac = function MyService_Factory(t) {
|
|
return new (t || MyService)($r3$.ɵɵinject(MyDependency), $r3$.ɵɵinject(MyOptionalDependency, 8));
|
|
}`;
|
|
|
|
const def = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: MyService.ɵfac
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
|
});
|
|
|
|
it('should create a single factory def if the class has more than one decorator', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable, Pipe} from '@angular/core';
|
|
|
|
@Injectable()
|
|
@Pipe({name: 'my-pipe'})
|
|
export class MyPipe {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const result = compile(files, angularFiles).source;
|
|
const matches = result.match(/MyPipe\.ɵfac = function MyPipe_Factory/g);
|
|
expect(matches ? matches.length : 0).toBe(1);
|
|
});
|
|
|
|
it('should delegate directly to the alternate factory when setting `useFactory` without `deps`',
|
|
() => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
class MyAlternateService {}
|
|
|
|
function alternateFactory() {
|
|
return new MyAlternateService();
|
|
}
|
|
|
|
@Injectable({
|
|
useFactory: alternateFactory
|
|
})
|
|
export class MyService {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const def = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: function() {
|
|
return alternateFactory();
|
|
}
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
|
});
|
|
|
|
it('should not delegate directly to the alternate factory when setting `useFactory` with `deps`',
|
|
() => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
class SomeDep {}
|
|
class MyAlternateService {}
|
|
|
|
@Injectable({
|
|
useFactory: () => new MyAlternateFactory(),
|
|
deps: [SomeDep]
|
|
})
|
|
export class MyService {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const def = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: function MyService_Factory(t) {
|
|
var r = null;
|
|
if (t) {
|
|
r = new t();
|
|
} else {
|
|
r = (() => new MyAlternateFactory())($r3$.ɵɵinject(SomeDep));
|
|
}
|
|
return r;
|
|
}
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, def, 'Incorrect injectable definition');
|
|
});
|
|
|
|
it('should delegate directly to the alternate class factory when setting `useClass` without `deps`',
|
|
() => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
@Injectable()
|
|
class MyAlternateService {}
|
|
|
|
@Injectable({
|
|
useClass: MyAlternateService
|
|
})
|
|
export class MyService {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: function(t) {
|
|
return MyAlternateService.ɵfac(t);
|
|
}
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
|
});
|
|
|
|
it('should not delegate directly to the alternate class when setting `useClass` with `deps`',
|
|
() => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
class SomeDep {}
|
|
|
|
@Injectable()
|
|
class MyAlternateService {}
|
|
|
|
@Injectable({
|
|
useClass: MyAlternateService,
|
|
deps: [SomeDep]
|
|
})
|
|
export class MyService {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
MyService.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: MyService,
|
|
factory: function MyService_Factory(t) {
|
|
var r = null;
|
|
if (t) {
|
|
r = new t();
|
|
} else {
|
|
r = new MyAlternateService($r3$.ɵɵinject(SomeDep));
|
|
}
|
|
return r;
|
|
}
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
|
});
|
|
|
|
it('should unwrap forward refs when delegating to a different class', () => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Injectable, forwardRef} from '@angular/core';
|
|
|
|
@Injectable({providedIn: 'root', useClass: forwardRef(() => SomeProviderImpl)})
|
|
abstract class SomeProvider {
|
|
}
|
|
|
|
@Injectable()
|
|
class SomeProviderImpl extends SomeProvider {
|
|
}
|
|
`
|
|
}
|
|
};
|
|
|
|
const factory = `
|
|
SomeProvider.ɵprov = $r3$.ɵɵdefineInjectable({
|
|
token: SomeProvider,
|
|
factory: function(t) {
|
|
return SomeProviderImpl.ɵfac(t);
|
|
},
|
|
providedIn: 'root'
|
|
});
|
|
`;
|
|
|
|
const result = compile(files, angularFiles);
|
|
expectEmit(result.source, factory, 'Incorrect factory definition');
|
|
});
|
|
|
|
it('should have the pipe factory take precedence over the injectable factory, if a class has multiple decorators',
|
|
() => {
|
|
const files = {
|
|
app: {
|
|
'spec.ts': `
|
|
import {Component, NgModule, Pipe, PipeTransform, Injectable} from '@angular/core';
|
|
|
|
@Injectable()
|
|
class Service {}
|
|
|
|
@Injectable()
|
|
@Pipe({name: 'myPipe'})
|
|
export class MyPipe implements PipeTransform {
|
|
constructor(service: Service) {}
|
|
transform(value: any, ...args: any[]) { return value; }
|
|
}
|
|
|
|
@Pipe({name: 'myOtherPipe'})
|
|
@Injectable()
|
|
export class MyOtherPipe implements PipeTransform {
|
|
constructor(service: Service) {}
|
|
transform(value: any, ...args: any[]) { return value; }
|
|
}
|
|
|
|
@Component({
|
|
selector: 'my-app',
|
|
template: '{{0 | myPipe | myOtherPipe}}'
|
|
})
|
|
export class MyApp {}
|
|
|
|
@NgModule({declarations: [MyPipe, MyOtherPipe, MyApp], declarations: [Service]})
|
|
export class MyModule {}
|
|
`
|
|
}
|
|
};
|
|
|
|
const result = compile(files, angularFiles);
|
|
const source = result.source;
|
|
|
|
// The prov definition must be last so MyPipe.fac is defined
|
|
const MyPipeDefs = `
|
|
MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)(i0.ɵɵdirectiveInject(Service)); };
|
|
MyPipe.ɵpipe = i0.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: true });
|
|
MyPipe.ɵprov = i0.ɵɵdefineInjectable({ token: MyPipe, factory: MyPipe.ɵfac });
|
|
`;
|
|
|
|
// The prov definition must be last so MyOtherPipe.fac is defined
|
|
const MyOtherPipeDefs = `
|
|
MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($r3$.ɵɵdirectiveInject(Service)); };
|
|
MyOtherPipe.ɵpipe = i0.ɵɵdefinePipe({ name: "myOtherPipe", type: MyOtherPipe, pure: true });
|
|
MyOtherPipe.ɵprov = i0.ɵɵdefineInjectable({ token: MyOtherPipe, factory: MyOtherPipe.ɵfac });
|
|
`;
|
|
|
|
expectEmit(source, MyPipeDefs, 'Invalid pipe factory function');
|
|
expectEmit(source, MyOtherPipeDefs, 'Invalid pipe factory function');
|
|
expect(source.match(/MyPipe\.ɵfac =/g)!.length).toBe(1);
|
|
expect(source.match(/MyOtherPipe\.ɵfac =/g)!.length).toBe(1);
|
|
});
|
|
});
|