mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Now the method `getConstructorDependencies` no longer needs to do any post analysis, and can rely on the reflection host's result to generate ctor params. This will automatically include invalid factories which fix the issue. PR Close #52215
1037 lines
36 KiB
TypeScript
1037 lines
36 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 ts from 'typescript';
|
|
|
|
import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
|
|
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
|
|
|
|
import {NgtscTestEnvironment} from './env';
|
|
|
|
const testFiles = loadStandardTestFiles();
|
|
|
|
runInEachFileSystem(() => {
|
|
describe('local compilation', () => {
|
|
let env!: NgtscTestEnvironment;
|
|
|
|
beforeEach(() => {
|
|
env = NgtscTestEnvironment.setup(testFiles);
|
|
const tsconfig: {[key: string]: any} = {
|
|
extends: '../tsconfig-base.json',
|
|
compilerOptions: {
|
|
baseUrl: '.',
|
|
rootDirs: ['/app'],
|
|
},
|
|
angularCompilerOptions: {
|
|
compilationMode: 'experimental-local',
|
|
},
|
|
};
|
|
env.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
|
});
|
|
|
|
it('should produce no TS semantic diagnostics', () => {
|
|
env.write('test.ts', `
|
|
import {SubExternalStuff} from './some-where';
|
|
`);
|
|
|
|
env.driveMain();
|
|
const diags = env.driveDiagnostics();
|
|
|
|
expect(diags).toEqual([]);
|
|
});
|
|
|
|
describe('ng module injector def', () => {
|
|
it('should produce empty injector def imports when module has no imports/exports', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
|
|
@NgModule({})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents).toContain('MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({})');
|
|
});
|
|
|
|
it('should include raw module imports array elements (including forward refs) in the injector def imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {SubModule1} from './some-where';
|
|
import {SubModule2} from './another-where';
|
|
|
|
@NgModule({})
|
|
class LocalModule1 {}
|
|
|
|
@NgModule({
|
|
imports: [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
|
|
@NgModule({})
|
|
class LocalModule2 {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)] })');
|
|
});
|
|
|
|
it('should include non-array raw module imports as it is in the injector def imports', () => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {SubModule1} from './some-where';
|
|
import {SubModule2} from './another-where';
|
|
|
|
const NG_IMPORTS = [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)];
|
|
|
|
@NgModule({})
|
|
class LocalModule1 {}
|
|
|
|
@NgModule({
|
|
imports: NG_IMPORTS,
|
|
})
|
|
export class MainModule {
|
|
}
|
|
|
|
@NgModule({})
|
|
class LocalModule2 {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [NG_IMPORTS] })');
|
|
});
|
|
|
|
it('should include raw module exports array elements (including forward refs) in the injector def imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {SubModule1} from './some-where';
|
|
import {SubModule2} from './another-where';
|
|
|
|
@NgModule({})
|
|
class LocalModule1 {}
|
|
|
|
@NgModule({
|
|
exports: [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
|
|
@NgModule({})
|
|
class LocalModule2 {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)] })');
|
|
});
|
|
|
|
it('should include non-array raw module exports (including forward refs) in the injector def imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {SubModule1} from './some-where';
|
|
import {SubModule2} from './another-where';
|
|
|
|
const NG_EXPORTS = [SubModule1, forwardRef(() => SubModule2), LocalModule1, forwardRef(() => LocalModule2)];
|
|
|
|
@NgModule({})
|
|
class LocalModule1 {}
|
|
|
|
@NgModule({
|
|
exports: NG_EXPORTS,
|
|
})
|
|
export class MainModule {
|
|
}
|
|
|
|
@NgModule({})
|
|
class LocalModule2 {}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [NG_EXPORTS] })');
|
|
});
|
|
|
|
it('should concat raw module imports and exports arrays (including forward refs) in the injector def imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {SubModule1, SubModule2} from './some-where';
|
|
import {SubModule3, SubModule4} from './another-where';
|
|
|
|
@NgModule({
|
|
imports: [SubModule1, forwardRef(() => SubModule2)],
|
|
exports: [SubModule3, forwardRef(() => SubModule4)],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [SubModule1, forwardRef(() => SubModule2), SubModule3, forwardRef(() => SubModule4)] })');
|
|
});
|
|
|
|
it('should combines non-array raw module imports and exports (including forward refs) in the injector def imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, forwardRef} from '@angular/core';
|
|
import {NG_IMPORTS} from './some-where';
|
|
import {NG_EXPORTS} from './another-where';
|
|
|
|
@NgModule({
|
|
imports: NG_IMPORTS,
|
|
exports: NG_EXPORTS,
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'MainModule.ɵinj = /*@__PURE__*/ i0.ɵɵdefineInjector({ imports: [NG_IMPORTS, NG_EXPORTS] })');
|
|
});
|
|
});
|
|
|
|
describe('component dependencies', () => {
|
|
it('should generate ɵɵgetComponentDepsFactory for component def dependencies - for non-standalone component ',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule, Component} from '@angular/core';
|
|
|
|
@Component({
|
|
selector: 'test-main',
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [MainComponent],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain('dependencies: i0.ɵɵgetComponentDepsFactory(MainComponent)');
|
|
});
|
|
|
|
it('should generate ɵɵgetComponentDepsFactory with raw imports as second param for component def dependencies - for standalone component with non-empty imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, forwardRef} from '@angular/core';
|
|
import {SomeThing} from 'some-where';
|
|
import {SomeThing2} from 'some-where2';
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: [SomeThing, forwardRef(()=>SomeThing2)],
|
|
selector: 'test-main',
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'dependencies: i0.ɵɵgetComponentDepsFactory(MainComponent, [SomeThing, forwardRef(() => SomeThing2)])');
|
|
});
|
|
|
|
it('should generate ɵɵgetComponentDepsFactory with raw non-array imports as second param for component def dependencies - for standalone component with non-empty imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, forwardRef} from '@angular/core';
|
|
import {SomeThing} from 'some-where';
|
|
import {SomeThing2} from 'some-where2';
|
|
|
|
const NG_IMPORTS = [SomeThing, forwardRef(()=>SomeThing2)];
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: NG_IMPORTS,
|
|
selector: 'test-main',
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain('dependencies: i0.ɵɵgetComponentDepsFactory(MainComponent, NG_IMPORTS)');
|
|
});
|
|
|
|
it('should generate ɵɵgetComponentDepsFactory with empty array as secon d arg for standalone component with empty imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: [],
|
|
selector: 'test-main',
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain('dependencies: i0.ɵɵgetComponentDepsFactory(MainComponent, [])');
|
|
});
|
|
|
|
it('should not generate ɵɵgetComponentDepsFactory for standalone component with no imports',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component} from '@angular/core';
|
|
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'test-main',
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents).not.toContain('i0.ɵɵgetComponentDepsFactory');
|
|
});
|
|
});
|
|
|
|
describe('component fields', () => {
|
|
it('should place the changeDetection as it is into the component def', () => {
|
|
env.write('test.ts', `
|
|
import {Component} from '@angular/core';
|
|
import {SomeWeirdThing} from 'some-where';
|
|
|
|
@Component({
|
|
changeDetection: SomeWeirdThing,
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents).toContain('changeDetection: SomeWeirdThing');
|
|
});
|
|
|
|
it('should place the correct value of encapsulation into the component def - case of ViewEncapsulation.Emulated with no styles',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, ViewEncapsulation} from '@angular/core';
|
|
|
|
@Component({
|
|
encapsulation: ViewEncapsulation.Emulated,
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
// If there is no style, don't generate css selectors on elements by setting
|
|
// encapsulation to none (=2)
|
|
expect(jsContents).toContain('encapsulation: 2');
|
|
});
|
|
|
|
it('should place the correct value of encapsulation into the component def - case of ViewEncapsulation.Emulated with styles',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, ViewEncapsulation} from '@angular/core';
|
|
|
|
@Component({
|
|
encapsulation: ViewEncapsulation.Emulated,
|
|
styles: ['color: blue'],
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
// encapsulation is set only for non-default value
|
|
expect(jsContents).not.toContain('encapsulation: 0');
|
|
expect(jsContents).toContain('styles: ["color: blue"]');
|
|
});
|
|
|
|
it('should place the correct value of encapsulation into the component def - case of ViewEncapsulation.ShadowDom',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, ViewEncapsulation} from '@angular/core';
|
|
|
|
@Component({
|
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents).toContain('encapsulation: 3');
|
|
});
|
|
|
|
it('should place the correct value of encapsulation into the component def - case of ViewEncapsulation.None',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, ViewEncapsulation} from '@angular/core';
|
|
|
|
@Component({
|
|
encapsulation: ViewEncapsulation.None,
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents).toContain('encapsulation: 2');
|
|
});
|
|
|
|
it('should default encapsulation to Emulated', () => {
|
|
env.write('test.ts', `
|
|
import {Component, ViewEncapsulation} from '@angular/core';
|
|
|
|
@Component({
|
|
template: '<span>Hello world!</span>',
|
|
})
|
|
export class MainComponent {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
// If there is no style, don't generate css selectors on elements by setting encapsulation
|
|
// to none (=2)
|
|
expect(jsContents).toContain('encapsulation: 2');
|
|
});
|
|
});
|
|
|
|
describe('constructor injection', () => {
|
|
it('should include injector types with all possible import/injection styles into component factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, NgModule, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Component({
|
|
selector: 'test-main',
|
|
template: '<span>Hello world</span>',
|
|
})
|
|
export class MainComponent {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [MainComponent],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainComponent.ɵfac = function MainComponent_Factory(t) { return new (t || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into standalone component factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, NgModule, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Component({
|
|
standalone: true,
|
|
selector: 'test-main',
|
|
template: '<span>Hello world</span>',
|
|
})
|
|
export class MainComponent {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainComponent.ɵfac = function MainComponent_Factory(t) { return new (t || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into directive factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive, NgModule, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Directive({
|
|
})
|
|
export class MainDirective {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [MainDirective],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainDirective.ɵfac = function MainDirective_Factory(t) { return new (t || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into standalone directive factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Directive({
|
|
standalone: true,
|
|
})
|
|
export class MainDirective {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainDirective.ɵfac = function MainDirective_Factory(t) { return new (t || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into pipe factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Pipe, NgModule, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Pipe({name: 'pipe'})
|
|
export class MainPipe {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
|
|
@NgModule({
|
|
declarations: [MainPipe],
|
|
})
|
|
export class MainModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainPipe.ɵfac = function MainPipe_Factory(t) { return new (t || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into standalone pipe factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Pipe, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Pipe({
|
|
name: 'pipe',
|
|
standalone: true,
|
|
})
|
|
export class MainPipe {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainPipe.ɵfac = function MainPipe_Factory(t) { return new (t || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into injectable factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Injectable, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@Injectable({
|
|
providedIn: 'root',
|
|
})
|
|
export class MainService {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainService.ɵfac = function MainService_Factory(t) { return new (t || MainService)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should include injector types with all possible import/injection styles into ng module factory',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component, NgModule, Attribute, Inject} from '@angular/core';
|
|
import {SomeClass} from './some-where'
|
|
import {SomeService1} from './some-where1'
|
|
import SomeService2 from './some-where2'
|
|
import * as SomeWhere3 from './some-where3'
|
|
import * as SomeWhere4 from './some-where4'
|
|
|
|
@NgModule({
|
|
})
|
|
export class MainModule {
|
|
constructor(
|
|
private someService1: SomeService1,
|
|
private someService2: SomeService2,
|
|
private someService3: SomeWhere3.SomeService3,
|
|
private someService4: SomeWhere4.nested.SomeService4,
|
|
@Attribute('title') title: string,
|
|
@Inject(MESSAGE_TOKEN) tokenMessage: SomeClass,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainModule.ɵfac = function MainModule_Factory(t) { return new (t || MainModule)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for injectable when type parameter types are used as token',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
@Injectable()
|
|
export class MainService<S> {
|
|
constructor(
|
|
private someService1: S,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainService.ɵfac = function MainService_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for injectable when when token is imported as type',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Injectable} from '@angular/core';
|
|
import {type MyService} from './somewhere';
|
|
|
|
@Injectable()
|
|
export class MainService {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainService.ɵfac = function MainService_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for injectable when when token is a type', () => {
|
|
env.write('test.ts', `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
type MyService = {a:string};
|
|
|
|
@Injectable()
|
|
export class MainService {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainService.ɵfac = function MainService_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for injectable when when token is an interface', () => {
|
|
env.write('test.ts', `
|
|
import {Injectable} from '@angular/core';
|
|
|
|
interface MyService {a:string}
|
|
|
|
@Injectable()
|
|
export class MainService {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MainService.ɵfac = function MainService_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for directive without selector type parameter types are used as token',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive} from '@angular/core';
|
|
|
|
@Directive()
|
|
export class MyDirective<S> {
|
|
constructor(
|
|
private myService: S,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MyDirective.ɵfac = function MyDirective_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for directive without selector when token is imported as type',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive} from '@angular/core';
|
|
import {type MyService} from './somewhere';
|
|
|
|
@Directive()
|
|
export class MyDirective {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MyDirective.ɵfac = function MyDirective_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for directive without selector when token is a type',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive} from '@angular/core';
|
|
|
|
type MyService = {a:string};
|
|
|
|
@Directive()
|
|
export class MyDirective {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MyDirective.ɵfac = function MyDirective_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
|
|
it('should generate invalid factory for directive without selector when token is an interface',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Directive} from '@angular/core';
|
|
|
|
interface MyService {a:string}
|
|
|
|
@Directive()
|
|
export class MyDirective {
|
|
constructor(
|
|
private myService: MyService,
|
|
) {}
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
`MyDirective.ɵfac = function MyDirective_Factory(t) { i0.ɵɵinvalidFactory(); };`);
|
|
});
|
|
});
|
|
|
|
describe('local compilation specific errors', () => {
|
|
it('should show extensive error message when using an imported symbol for component template',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component} from '@angular/core';
|
|
import {ExternalString} from './some-where';
|
|
|
|
@Component({
|
|
template: ExternalString,
|
|
})
|
|
export class Main {
|
|
}
|
|
`);
|
|
|
|
const errors = env.driveDiagnostics();
|
|
|
|
expect(errors.length).toBe(1);
|
|
|
|
const {code, messageText} = errors[0];
|
|
|
|
expect(code).toBe(ngErrorCode(ErrorCode.
|
|
|
|
LOCAL_COMPILATION_IMPORTED_TEMPLATE_STRING));
|
|
const text = ts.flattenDiagnosticMessageText(messageText, '\n');
|
|
|
|
expect(text).toContain('Unknown identifier used as template string: ExternalString');
|
|
expect(text).toContain('either inline it or move it to a separate file');
|
|
});
|
|
|
|
it('should show extensive error message when using an imported symbol for component styles',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {Component} from '@angular/core';
|
|
import {ExternalString} from './some-where';
|
|
|
|
@Component({
|
|
styles: [ExternalString],
|
|
template: '',
|
|
|
|
})
|
|
export class Main {
|
|
}
|
|
`);
|
|
|
|
const errors = env.driveDiagnostics();
|
|
|
|
expect(errors.length).toBe(1);
|
|
|
|
const {code, messageText} = errors[0];
|
|
|
|
expect(code).toBe(ngErrorCode(ErrorCode.LOCAL_COMPILATION_IMPORTED_STYLES_STRING));
|
|
const text = ts.flattenDiagnosticMessageText(messageText, '\n');
|
|
|
|
expect(text).toContain('Unknown identifier used as styles string: ExternalString');
|
|
expect(text).toContain('either inline it or move it to a separate file');
|
|
});
|
|
});
|
|
|
|
describe('ng module bootstrap def', () => {
|
|
it('should include the bootstrap definition in ɵɵsetNgModuleScope instead of ɵɵdefineNgModule',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
import {App} from './some-where';
|
|
|
|
@NgModule({
|
|
declarations: [App],
|
|
bootstrap: [App],
|
|
})
|
|
export class AppModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'AppModule.ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: AppModule });');
|
|
expect(jsContents)
|
|
.toContain(
|
|
'ɵɵsetNgModuleScope(AppModule, { declarations: [App], bootstrap: [App] }); })();');
|
|
});
|
|
|
|
it('should include no bootstrap definition in ɵɵsetNgModuleScope if the NgModule has no bootstrap field',
|
|
() => {
|
|
env.write('test.ts', `
|
|
import {NgModule} from '@angular/core';
|
|
import {App} from './some-where';
|
|
|
|
@NgModule({
|
|
declarations: [App],
|
|
})
|
|
export class AppModule {
|
|
}
|
|
`);
|
|
|
|
env.driveMain();
|
|
const jsContents = env.getContents('test.js');
|
|
|
|
expect(jsContents)
|
|
.toContain(
|
|
'AppModule.ɵmod = /*@__PURE__*/ i0.ɵɵdefineNgModule({ type: AppModule });');
|
|
expect(jsContents)
|
|
.toContain('ɵɵsetNgModuleScope(AppModule, { declarations: [App] }); })();');
|
|
});
|
|
});
|
|
});
|
|
});
|