When the target of the compiler is ES2015 or newer then we should be generating `let` and `const` variable declarations rather than `var`. PR Close #38775
		
			
				
	
	
		
			393 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC 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) {
 | |
|               let 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) {
 | |
|               let 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);
 | |
|      });
 | |
| });
 |