test(ivy): add ability to find already passing tests (#27449)

PR Close #27449
This commit is contained in:
Misko Hevery 2018-12-03 17:57:07 -08:00 committed by Igor Minar
parent 4b9948c1be
commit f52600e261
35 changed files with 4501 additions and 4410 deletions

View File

@ -74,9 +74,9 @@ describe('@angular/core ng_package', () => {
describe('typescript support', () => { describe('typescript support', () => {
fixmeIvy('FW-738: ngtsc doesn\'t generate flat index files') && fixmeIvy('FW-738: ngtsc doesn\'t generate flat index files')
it('should have an index d.ts file', .it('should have an index d.ts file',
() => { expect(shx.cat('core.d.ts')).toContain(`export *`); }); () => { expect(shx.cat('core.d.ts')).toContain(`export *`); });
it('should not have amd module names', it('should not have amd module names',
() => { expect(shx.cat('public_api.d.ts')).not.toContain('<amd-module name'); }); () => { expect(shx.cat('public_api.d.ts')).not.toContain('<amd-module name'); });
@ -90,8 +90,8 @@ describe('@angular/core ng_package', () => {
}); });
obsoleteInIvy('metadata files are no longer needed or produced in Ivy') && obsoleteInIvy('metadata files are no longer needed or produced in Ivy')
describe('angular metadata', () => { .describe('angular metadata', () => {
it('should have metadata.json files', it('should have metadata.json files',
() => { expect(shx.cat('core.metadata.json')).toContain(`"__symbolic":"module"`); }); () => { expect(shx.cat('core.metadata.json')).toContain(`"__symbolic":"module"`); });
@ -112,8 +112,8 @@ describe('@angular/core ng_package', () => {
.toMatch(/@license Angular v\d+\.\d+\.\d+(?!-PLACEHOLDER)/); .toMatch(/@license Angular v\d+\.\d+\.\d+(?!-PLACEHOLDER)/);
}); });
obsoleteInIvy('we no longer need to export private symbols') && obsoleteInIvy('we no longer need to export private symbols')
it('should have been built from the generated bundle index', () => { .it('should have been built from the generated bundle index', () => {
expect(shx.cat('fesm2015/core.js')).toMatch('export {.*makeParamDecorator'); expect(shx.cat('fesm2015/core.js')).toMatch('export {.*makeParamDecorator');
}); });
}); });
@ -147,9 +147,9 @@ describe('@angular/core ng_package', () => {
expect(shx.cat('fesm5/core.js')).toMatch('import {.*__extends'); expect(shx.cat('fesm5/core.js')).toMatch('import {.*__extends');
}); });
obsoleteInIvy('we no longer need to export private symbols') && obsoleteInIvy('we no longer need to export private symbols')
it('should have been built from the generated bundle index', .it('should have been built from the generated bundle index',
() => { expect(shx.cat('fesm5/core.js')).toMatch('export {.*makeParamDecorator'); }); () => { expect(shx.cat('fesm5/core.js')).toMatch('export {.*makeParamDecorator'); });
}); });
@ -228,13 +228,13 @@ describe('@angular/core ng_package', () => {
() => { expect(shx.cat(typingsFile)).toContain('export * from \'./public_api\';'); }); () => { expect(shx.cat(typingsFile)).toContain('export * from \'./public_api\';'); });
obsoleteInIvy( obsoleteInIvy(
'now that we don\'t need metadata files, we don\'t need these redirects to help resolve paths to them') && 'now that we don\'t need metadata files, we don\'t need these redirects to help resolve paths to them')
it('should have an \'redirect\' d.ts file in the parent dir', .it('should have an \'redirect\' d.ts file in the parent dir',
() => { expect(shx.cat('testing.d.ts')).toContain(`export *`); }); () => { expect(shx.cat('testing.d.ts')).toContain(`export *`); });
}); });
obsoleteInIvy('metadata files are no longer needed or produced in Ivy') && obsoleteInIvy('metadata files are no longer needed or produced in Ivy')
describe('angular metadata file', () => { .describe('angular metadata file', () => {
it('should have a \'redirect\' metadata.json file next to the d.ts file', () => { it('should have a \'redirect\' metadata.json file next to the d.ts file', () => {
expect(shx.cat('testing.metadata.json')) expect(shx.cat('testing.metadata.json'))
.toContain( .toContain(

View File

@ -17,7 +17,7 @@ describe('ngc_wrapped', () => {
// fixmeIvy placeholder to prevent jasmine from erroring out because there are no specs // fixmeIvy placeholder to prevent jasmine from erroring out because there are no specs
it('should be removed once the fixmeIvy below is resolved', () => {}); it('should be removed once the fixmeIvy below is resolved', () => {});
fixmeIvy('FW-741: ngtsc breaks tsc module resolution') && it('should work', () => { fixmeIvy('FW-741: ngtsc breaks tsc module resolution').it('should work', () => {
const {read, write, runOneBuild, writeConfig, shouldExist, basePath} = setup(); const {read, write, runOneBuild, writeConfig, shouldExist, basePath} = setup();
write('some_project/index.ts', ` write('some_project/index.ts', `

View File

@ -157,22 +157,22 @@ describe('insert/remove', () => {
expect(fixture.nativeElement).toHaveText('baz'); expect(fixture.nativeElement).toHaveText('baz');
})); }));
fixmeIvy('FW-739: destroy on NgModuleRef is not being called') && fixmeIvy('FW-739: destroy on NgModuleRef is not being called')
it('should clean up moduleRef, if supplied', async(() => { .it('should clean up moduleRef, if supplied', async(() => {
let destroyed = false; let destroyed = false;
const compiler = TestBed.get(Compiler) as Compiler; const compiler = TestBed.get(Compiler) as Compiler;
const fixture = TestBed.createComponent(TestComponent); const fixture = TestBed.createComponent(TestComponent);
fixture.componentInstance.module = compiler.compileModuleSync(TestModule2); fixture.componentInstance.module = compiler.compileModuleSync(TestModule2);
fixture.componentInstance.currentComponent = Module2InjectedComponent; fixture.componentInstance.currentComponent = Module2InjectedComponent;
fixture.detectChanges(); fixture.detectChanges();
const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'] !; const moduleRef = fixture.componentInstance.ngComponentOutlet['_moduleRef'] !;
spyOn(moduleRef, 'destroy').and.callThrough(); spyOn(moduleRef, 'destroy').and.callThrough();
expect(moduleRef.destroy).not.toHaveBeenCalled(); expect(moduleRef.destroy).not.toHaveBeenCalled();
fixture.destroy(); fixture.destroy();
expect(moduleRef.destroy).toHaveBeenCalled(); expect(moduleRef.destroy).toHaveBeenCalled();
})); }));
it('should not re-create moduleRef when it didn\'t actually change', async(() => { it('should not re-create moduleRef when it didn\'t actually change', async(() => {
const compiler = TestBed.get(Compiler) as Compiler; const compiler = TestBed.get(Compiler) as Compiler;

View File

@ -168,8 +168,8 @@ describe('ngInjectableDef Bazel Integration', () => {
expect(TestBed.get(INJECTOR).get('foo')).toEqual('bar'); expect(TestBed.get(INJECTOR).get('foo')).toEqual('bar');
}); });
fixmeIvy('FW-646: Directive providers don\'t support primitive types') && fixmeIvy('FW-646: Directive providers don\'t support primitive types')
it('Component injector understands requests for INJECTABLE', () => { .it('Component injector understands requests for INJECTABLE', () => {
@Component({ @Component({
selector: 'test-cmp', selector: 'test-cmp',
template: 'test', template: 'test',

View File

@ -41,8 +41,8 @@ describe('Ivy NgModule', () => {
it('works', () => { createInjector(JitAppModule); }); it('works', () => { createInjector(JitAppModule); });
fixmeIvy('FW-645: jit doesn\'t support forwardRefs') && fixmeIvy('FW-645: jit doesn\'t support forwardRefs')
it('throws an error on circular module dependencies', () => { .it('throws an error on circular module dependencies', () => {
@NgModule({ @NgModule({
imports: [forwardRef(() => BModule)], imports: [forwardRef(() => BModule)],
}) })

View File

@ -60,7 +60,7 @@ describe('ngtools_api (deprecated)', () => {
}); });
} }
fixmeIvy('FW-629: ngtsc lists lazy routes') && it('should list lazy routes recursively', () => { fixmeIvy('FW-629: ngtsc lists lazy routes').it('should list lazy routes recursively', () => {
writeSomeRoutes(); writeSomeRoutes();
const {program, host, options} = createProgram(['src/main.ts']); const {program, host, options} = createProgram(['src/main.ts']);
const routes = NgTools_InternalApi_NG_2.listLazyRoutes({ const routes = NgTools_InternalApi_NG_2.listLazyRoutes({

View File

@ -35,26 +35,26 @@ ivyEnabled && describe('ApplicationRef bootstrap', () => {
class MyAppModule { class MyAppModule {
} }
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should bootstrap hello world', withBody('<hello-world></hello-world>', async() => { 'should bootstrap hello world', withBody('<hello-world></hello-world>', async() => {
const MyAppModuleFactory = new NgModuleFactory(MyAppModule); const MyAppModuleFactory = new NgModuleFactory(MyAppModule);
const moduleRef = await getTestBed().platform.bootstrapModuleFactory( const moduleRef = await getTestBed().platform.bootstrapModuleFactory(
MyAppModuleFactory, {ngZone: 'noop'}); MyAppModuleFactory, {ngZone: 'noop'});
const appRef = moduleRef.injector.get(ApplicationRef); const appRef = moduleRef.injector.get(ApplicationRef);
const helloWorldComponent = appRef.components[0].instance as HelloWorldComponent; const helloWorldComponent = appRef.components[0].instance as HelloWorldComponent;
expect(document.body.innerHTML) expect(document.body.innerHTML)
.toEqual('<hello-world><div>Hello World</div></hello-world>'); .toEqual('<hello-world><div>Hello World</div></hello-world>');
expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck']); expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck']);
helloWorldComponent.name = 'Mundo'; helloWorldComponent.name = 'Mundo';
appRef.tick(); appRef.tick();
expect(document.body.innerHTML) expect(document.body.innerHTML)
.toEqual('<hello-world><div>Hello Mundo</div></hello-world>'); .toEqual('<hello-world><div>Hello Mundo</div></hello-world>');
expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck', 'DoCheck']); expect(helloWorldComponent.log).toEqual(['OnInit', 'DoCheck', 'DoCheck']);
// Cleanup TestabilityRegistry // Cleanup TestabilityRegistry
const registry: TestabilityRegistry = getTestBed().get(TestabilityRegistry); const registry: TestabilityRegistry = getTestBed().get(TestabilityRegistry);
registry.unregisterAllApplications(); registry.unregisterAllApplications();
})); }));
}); });

View File

@ -74,63 +74,63 @@ class SomeComponent {
return MyModule; return MyModule;
} }
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running') && fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running')
it('should bootstrap a component from a child module', .it('should bootstrap a component from a child module',
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({ @Component({
selector: 'bootstrap-app', selector: 'bootstrap-app',
template: '', template: '',
}) })
class SomeComponent { class SomeComponent {
} }
@NgModule({ @NgModule({
providers: [{provide: 'hello', useValue: 'component'}], providers: [{provide: 'hello', useValue: 'component'}],
declarations: [SomeComponent], declarations: [SomeComponent],
entryComponents: [SomeComponent], entryComponents: [SomeComponent],
}) })
class SomeModule { class SomeModule {
} }
createRootEl(); createRootEl();
const modFactory = compiler.compileModuleSync(SomeModule); const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed); const module = modFactory.create(TestBed);
const cmpFactory = const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !; module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const component = app.bootstrap(cmpFactory); const component = app.bootstrap(cmpFactory);
// The component should see the child module providers // The component should see the child module providers
expect(component.injector.get('hello')).toEqual('component'); expect(component.injector.get('hello')).toEqual('component');
}))); })));
fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running') && fixmeIvy('FW-776: Cannot bootstrap as there are still asynchronous initializers running')
it('should bootstrap a component with a custom selector', .it('should bootstrap a component with a custom selector',
async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => { async(inject([ApplicationRef, Compiler], (app: ApplicationRef, compiler: Compiler) => {
@Component({ @Component({
selector: 'bootstrap-app', selector: 'bootstrap-app',
template: '', template: '',
}) })
class SomeComponent { class SomeComponent {
} }
@NgModule({ @NgModule({
providers: [{provide: 'hello', useValue: 'component'}], providers: [{provide: 'hello', useValue: 'component'}],
declarations: [SomeComponent], declarations: [SomeComponent],
entryComponents: [SomeComponent], entryComponents: [SomeComponent],
}) })
class SomeModule { class SomeModule {
} }
createRootEl('custom-selector'); createRootEl('custom-selector');
const modFactory = compiler.compileModuleSync(SomeModule); const modFactory = compiler.compileModuleSync(SomeModule);
const module = modFactory.create(TestBed); const module = modFactory.create(TestBed);
const cmpFactory = const cmpFactory =
module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !; module.componentFactoryResolver.resolveComponentFactory(SomeComponent) !;
const component = app.bootstrap(cmpFactory, 'custom-selector'); const component = app.bootstrap(cmpFactory, 'custom-selector');
// The component should see the child module providers // The component should see the child module providers
expect(component.injector.get('hello')).toEqual('component'); expect(component.injector.get('hello')).toEqual('component');
}))); })));
describe('ApplicationRef', () => { describe('ApplicationRef', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); }); beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });

View File

@ -33,10 +33,10 @@ const ProxyZoneSpec: {assertPresent: () => void} = (Zone as any)['ProxyZoneSpec'
})('foo', 'bar'); })('foo', 'bar');
}); });
fixmeIvy('unknown') && it('should work with inject()', fixmeIvy('unknown').it(
fakeAsync(inject([Parser], (parser: any /** TODO #9100 */) => { 'should work with inject()', fakeAsync(inject([Parser], (parser: any /** TODO #9100 */) => {
expect(parser).toBeAnInstanceOf(Parser); expect(parser).toBeAnInstanceOf(Parser);
}))); })));
it('should throw on nested calls', () => { it('should throw on nested calls', () => {
expect(() => { expect(() => {

View File

@ -15,8 +15,8 @@ import {fixmeIvy} from '@angular/private/testing';
describe('forwardRef integration', function() { describe('forwardRef integration', function() {
beforeEach(() => { TestBed.configureTestingModule({imports: [Module], declarations: [App]}); }); beforeEach(() => { TestBed.configureTestingModule({imports: [Module], declarations: [App]}); });
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should instantiate components which are declared using forwardRef', () => { .it('should instantiate components which are declared using forwardRef', () => {
const a = const a =
TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}).createComponent(App); TestBed.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]}).createComponent(App);
a.detectChanges(); a.detectChanges();

View File

@ -69,21 +69,20 @@ function declareTests(config?: {useJit: boolean}) {
expect(childComp.cfr.resolveComponentFactory(ChildComp) !.componentType).toBe(ChildComp); expect(childComp.cfr.resolveComponentFactory(ChildComp) !.componentType).toBe(ChildComp);
}); });
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should not be able to get components from a parent component (content hierarchy)', 'should not be able to get components from a parent component (content hierarchy)', () => {
() => { TestBed.overrideComponent(
TestBed.overrideComponent( MainComp, {set: {template: '<child><nested></nested></child>'}});
MainComp, {set: {template: '<child><nested></nested></child>'}}); TestBed.overrideComponent(ChildComp, {set: {template: '<ng-content></ng-content>'}});
TestBed.overrideComponent(ChildComp, {set: {template: '<ng-content></ng-content>'}});
const compFixture = TestBed.createComponent(MainComp); const compFixture = TestBed.createComponent(MainComp);
const nestedChildCompEl = compFixture.debugElement.children[0].children[0]; const nestedChildCompEl = compFixture.debugElement.children[0].children[0];
const nestedChildComp: NestedChildComp = nestedChildCompEl.componentInstance; const nestedChildComp: NestedChildComp = nestedChildCompEl.componentInstance;
expect(nestedChildComp.cfr.resolveComponentFactory(ChildComp) !.componentType) expect(nestedChildComp.cfr.resolveComponentFactory(ChildComp) !.componentType)
.toBe(ChildComp); .toBe(ChildComp);
expect(() => nestedChildComp.cfr.resolveComponentFactory(NestedChildComp)) expect(() => nestedChildComp.cfr.resolveComponentFactory(NestedChildComp))
.toThrow(noComponentFactoryError(NestedChildComp)); .toThrow(noComponentFactoryError(NestedChildComp));
}); });
}); });
} }

View File

@ -209,8 +209,8 @@ function declareTests(config?: {useJit: boolean}) {
.toEqual('Some other <div>HTML</div>'); .toEqual('Some other <div>HTML</div>');
}); });
modifiedInIvy('Binding to the class property directly works differently') && modifiedInIvy('Binding to the class property directly works differently')
it('should consume binding to className using class alias', () => { .it('should consume binding to className using class alias', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div class="initial" [class]="ctxProp"></div>'; const template = '<div class="initial" [class]="ctxProp"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -238,8 +238,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getProperty(nativeEl, 'htmlFor')).toBe('foo'); expect(getDOM().getProperty(nativeEl, 'htmlFor')).toBe('foo');
}); });
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work') && fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
it('should consume directive watch expression change.', () => { .it('should consume directive watch expression change.', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '<span>' + const template = '<span>' +
'<div my-dir [elprop]="ctxProp"></div>' + '<div my-dir [elprop]="ctxProp"></div>' +
@ -263,8 +263,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('pipes', () => { describe('pipes', () => {
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work') && fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
it('should support pipes in bindings', () => { .it('should support pipes in bindings', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir, DoublePipe]});
const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>'; const template = '<div my-dir #dir="mydir" [elprop]="ctxProp | double"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -290,8 +290,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
// GH issue 328 - https://github.com/angular/angular/issues/328 // GH issue 328 - https://github.com/angular/angular/issues/328
fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work') && fixmeIvy('FW-587: Inputs with aliases in component decorators don\'t work')
it('should support different directive types on a single node', () => { .it('should support different directive types on a single node', () => {
TestBed.configureTestingModule({declarations: [MyComp, ChildComp, MyDir]}); TestBed.configureTestingModule({declarations: [MyComp, ChildComp, MyDir]});
const template = '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>'; const template = '<child-cmp my-dir [elprop]="ctxProp"></child-cmp>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -350,8 +350,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(tc.injector.get(EventDir)).not.toBeNull(); expect(tc.injector.get(EventDir)).not.toBeNull();
}); });
fixmeIvy('FW-680: Throw meaningful error for uninitialized @Output') && fixmeIvy('FW-680: Throw meaningful error for uninitialized @Output')
it('should display correct error message for uninitialized @Output', () => { .it('should display correct error message for uninitialized @Output', () => {
@Component({selector: 'my-uninitialized-output', template: '<p>It works!</p>'}) @Component({selector: 'my-uninitialized-output', template: '<p>It works!</p>'})
class UninitializedOutputComp { class UninitializedOutputComp {
@Output() customEvent !: EventEmitter<any>; @Output() customEvent !: EventEmitter<any>;
@ -374,8 +374,8 @@ function declareTests(config?: {useJit: boolean}) {
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
}); });
modifiedInIvy('Comment node order changed') && modifiedInIvy('Comment node order changed')
it('should support template directives via `<ng-template>` elements.', () => { .it('should support template directives via `<ng-template>` elements.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
const template = const template =
'<ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template>'; '<ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template>';
@ -403,8 +403,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
fixmeIvy( fixmeIvy(
'FW-665: Discovery util fails with "Unable to find the given context data for the given target"') && 'FW-665: Discovery util fails with "Unable to find the given context data for the given target"')
it('should not detach views in ViewContainers when the parent view is destroyed.', () => { .it('should not detach views in ViewContainers when the parent view is destroyed.', () => {
TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]}); TestBed.configureTestingModule({declarations: [MyComp, SomeViewport]});
const template = const template =
'<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>'; '<div *ngIf="ctxBoolProp"><ng-template some-viewport let-greeting="someTmpl"><span>{{greeting}}</span></ng-template></div>';
@ -478,8 +478,8 @@ function declareTests(config?: {useJit: boolean}) {
.toBeAnInstanceOf(ExportDir); .toBeAnInstanceOf(ExportDir);
}); });
fixmeIvy('FW-708: Directives with multiple exports are not supported') && fixmeIvy('FW-708: Directives with multiple exports are not supported')
it('should assign a directive to a ref when it has multiple exportAs names', () => { .it('should assign a directive to a ref when it has multiple exportAs names', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveWithMultipleExportAsNames]}); {declarations: [MyComp, DirectiveWithMultipleExportAsNames]});
@ -543,8 +543,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(value.tagName.toLowerCase()).toEqual('div'); expect(value.tagName.toLowerCase()).toEqual('div');
}); });
fixmeIvy('FW-709: Context discovery does not support templates (comment nodes)') && fixmeIvy('FW-709: Context discovery does not support templates (comment nodes)')
it('should assign the TemplateRef to a user-defined variable', () => { .it('should assign the TemplateRef to a user-defined variable', () => {
const fixture = const fixture =
TestBed.configureTestingModule({declarations: [MyComp]}) TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent( .overrideComponent(
@ -567,8 +567,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('variables', () => { describe('variables', () => {
modifiedInIvy('Comment node order changed') && modifiedInIvy('Comment node order changed')
it('should allow to use variables in a for loop', () => { .it('should allow to use variables in a for loop', () => {
const template = const template =
'<ng-template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</ng-template>'; '<ng-template ngFor [ngForOf]="[1]" let-i><child-cmp-no-template #cmp></child-cmp-no-template>{{i}}-{{cmp.ctxProp}}</ng-template>';
@ -586,8 +586,8 @@ function declareTests(config?: {useJit: boolean}) {
describe('OnPush components', () => { describe('OnPush components', () => {
fixmeIvy( fixmeIvy(
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') && 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
it('should use ChangeDetectorRef to manually request a check', () => { .it('should use ChangeDetectorRef to manually request a check', () => {
TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]}); TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithRef]]]});
const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>'; const template = '<push-cmp-with-ref #cmp></push-cmp-with-ref>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -608,8 +608,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
fixmeIvy( fixmeIvy(
'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template') && 'FW-764: fixture.detectChanges() is not respecting OnPush flag on components in the root template')
it('should be checked when its bindings got updated', () => { .it('should be checked when its bindings got updated', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>'; const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
@ -628,28 +628,27 @@ function declareTests(config?: {useJit: boolean}) {
}); });
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should allow to destroy a component from within a host event handler', 'should allow to destroy a component from within a host event handler',
fakeAsync(() => { fakeAsync(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule({declarations: [MyComp, [[PushCmpWithHostEvent]]]});
{declarations: [MyComp, [[PushCmpWithHostEvent]]]}); const template = '<push-cmp-with-host-event></push-cmp-with-host-event>';
const template = '<push-cmp-with-host-event></push-cmp-with-host-event>'; TestBed.overrideComponent(MyComp, {set: {template}});
TestBed.overrideComponent(MyComp, {set: {template}}); const fixture = TestBed.createComponent(MyComp);
const fixture = TestBed.createComponent(MyComp);
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
const cmpEl = fixture.debugElement.children[0]; const cmpEl = fixture.debugElement.children[0];
const cmp: PushCmpWithHostEvent = cmpEl.injector.get(PushCmpWithHostEvent); const cmp: PushCmpWithHostEvent = cmpEl.injector.get(PushCmpWithHostEvent);
cmp.ctxCallback = (_: any) => fixture.destroy(); cmp.ctxCallback = (_: any) => fixture.destroy();
expect(() => cmpEl.triggerEventHandler('click', <Event>{})).not.toThrow(); expect(() => cmpEl.triggerEventHandler('click', <Event>{})).not.toThrow();
})); }));
} }
fixmeIvy('FW-758: OnPush events not marking view dirty when using renderer2') && fixmeIvy('FW-758: OnPush events not marking view dirty when using renderer2')
it('should be checked when an event is fired', () => { .it('should be checked when an event is fired', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]}); {declarations: [MyComp, PushCmp, EventCmp], imports: [CommonModule]});
const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>'; const template = '<push-cmp [prop]="ctxProp" #cmp></push-cmp>';
@ -774,71 +773,71 @@ function declareTests(config?: {useJit: boolean}) {
}); });
fixmeIvy( fixmeIvy(
'FW-763: LView tree not properly constructed / destroyed for dynamically inserted components') && 'FW-763: LView tree not properly constructed / destroyed for dynamically inserted components')
it('should support events via EventEmitter on regular elements', async(() => { .it('should support events via EventEmitter on regular elements', async(() => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]}); {declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]});
const template = '<div emitter listener></div>'; const template = '<div emitter listener></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
const tc = fixture.debugElement.children[0]; const tc = fixture.debugElement.children[0];
const emitter = tc.injector.get(DirectiveEmittingEvent); const emitter = tc.injector.get(DirectiveEmittingEvent);
const listener = tc.injector.get(DirectiveListeningEvent); const listener = tc.injector.get(DirectiveListeningEvent);
expect(listener.msg).toEqual(''); expect(listener.msg).toEqual('');
let eventCount = 0; let eventCount = 0;
emitter.event.subscribe({ emitter.event.subscribe({
next: () => { next: () => {
eventCount++; eventCount++;
if (eventCount === 1) { if (eventCount === 1) {
expect(listener.msg).toEqual('fired !'); expect(listener.msg).toEqual('fired !');
fixture.destroy(); fixture.destroy();
emitter.fireEvent('fired again !'); emitter.fireEvent('fired again !');
} else { } else {
expect(listener.msg).toEqual('fired !'); expect(listener.msg).toEqual('fired !');
} }
} }
}); });
emitter.fireEvent('fired !'); emitter.fireEvent('fired !');
})); }));
fixmeIvy( fixmeIvy(
'FW-665: Discovery util fails with Unable to find the given context data for the given target') && 'FW-665: Discovery util fails with Unable to find the given context data for the given target')
it('should support events via EventEmitter on template elements', async(() => { .it('should support events via EventEmitter on template elements', async(() => {
const fixture = const fixture =
TestBed TestBed
.configureTestingModule({ .configureTestingModule({
declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent] declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent]
}) })
.overrideComponent(MyComp, { .overrideComponent(MyComp, {
set: { set: {
template: template:
'<ng-template emitter listener (event)="ctxProp=$event"></ng-template>' '<ng-template emitter listener (event)="ctxProp=$event"></ng-template>'
} }
}) })
.createComponent(MyComp); .createComponent(MyComp);
const tc = fixture.debugElement.childNodes[0]; const tc = fixture.debugElement.childNodes[0];
const emitter = tc.injector.get(DirectiveEmittingEvent); const emitter = tc.injector.get(DirectiveEmittingEvent);
const myComp = fixture.debugElement.injector.get(MyComp); const myComp = fixture.debugElement.injector.get(MyComp);
const listener = tc.injector.get(DirectiveListeningEvent); const listener = tc.injector.get(DirectiveListeningEvent);
myComp.ctxProp = ''; myComp.ctxProp = '';
expect(listener.msg).toEqual(''); expect(listener.msg).toEqual('');
emitter.event.subscribe({ emitter.event.subscribe({
next: () => { next: () => {
expect(listener.msg).toEqual('fired !'); expect(listener.msg).toEqual('fired !');
expect(myComp.ctxProp).toEqual('fired !'); expect(myComp.ctxProp).toEqual('fired !');
} }
}); });
emitter.fireEvent('fired !'); emitter.fireEvent('fired !');
})); }));
it('should support [()] syntax', async(() => { it('should support [()] syntax', async(() => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]}); TestBed.configureTestingModule({declarations: [MyComp, DirectiveWithTwoWayBinding]});
@ -860,8 +859,8 @@ function declareTests(config?: {useJit: boolean}) {
})); }));
fixmeIvy( fixmeIvy(
'FW-743: Registering events on global objects (document, window, body) is not supported') && 'FW-743: Registering events on global objects (document, window, body) is not supported')
it('should support render events', () => { .it('should support render events', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]}); TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]});
const template = '<div listener></div>'; const template = '<div listener></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -883,8 +882,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
fixmeIvy( fixmeIvy(
'FW-743: Registering events on global objects (document, window, body) is not supported') && 'FW-743: Registering events on global objects (document, window, body) is not supported')
it('should support render global events', () => { .it('should support render global events', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]}); TestBed.configureTestingModule({declarations: [MyComp, DirectiveListeningDomEvent]});
const template = '<div listener></div>'; const template = '<div listener></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -947,8 +946,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getProperty(tc.nativeElement, 'id')).toEqual('newId'); expect(getDOM().getProperty(tc.nativeElement, 'id')).toEqual('newId');
}); });
fixmeIvy('FW-681: not possible to retrieve host property bindings from TView') && fixmeIvy('FW-681: not possible to retrieve host property bindings from TView')
it('should not use template variables for expressions in hostProperties', () => { .it('should not use template variables for expressions in hostProperties', () => {
@Directive( @Directive(
{selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}}) {selector: '[host-properties]', host: {'[id]': 'id', '[title]': 'unknownProp'}})
class DirectiveWithHostProps { class DirectiveWithHostProps {
@ -968,8 +967,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(tc.properties['title']).toBe(undefined); expect(tc.properties['title']).toBe(undefined);
}); });
fixmeIvy('FW-725: Pipes in host bindings fail with a cryptic error') && fixmeIvy('FW-725: Pipes in host bindings fail with a cryptic error')
it('should not allow pipes in hostProperties', () => { .it('should not allow pipes in hostProperties', () => {
@Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}}) @Directive({selector: '[host-properties]', host: {'[id]': 'id | uppercase'}})
class DirectiveWithHostProps { class DirectiveWithHostProps {
} }
@ -1004,8 +1003,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(dir.receivedArgs).toEqual(['one', undefined]); expect(dir.receivedArgs).toEqual(['one', undefined]);
}); });
fixmeIvy('FW-742: Pipes in host listeners should throw a descriptive error') && fixmeIvy('FW-742: Pipes in host listeners should throw a descriptive error')
it('should not allow pipes in hostListeners', () => { .it('should not allow pipes in hostListeners', () => {
@Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}}) @Directive({selector: '[host-listener]', host: {'(click)': 'doIt() | somePipe'}})
class DirectiveWithHostListener { class DirectiveWithHostListener {
} }
@ -1042,8 +1041,8 @@ function declareTests(config?: {useJit: boolean}) {
} }
fixmeIvy( fixmeIvy(
'FW-743: Registering events on global objects (document, window, body) is not supported') && 'FW-743: Registering events on global objects (document, window, body) is not supported')
it('should support render global events from multiple directives', () => { .it('should support render global events from multiple directives', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MyComp, DirectiveListeningDomEvent, DirectiveListeningDomEventOther] declarations: [MyComp, DirectiveListeningDomEvent, DirectiveListeningDomEventOther]
}); });
@ -1123,8 +1122,8 @@ function declareTests(config?: {useJit: boolean}) {
.toHaveText('dynamic greet'); .toHaveText('dynamic greet');
})); }));
fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation') && fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation')
it('should create a component that has been freshly compiled', () => { .it('should create a component that has been freshly compiled', () => {
@Component({template: ''}) @Component({template: ''})
class RootComp { class RootComp {
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
@ -1163,8 +1162,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(compRef.instance.someToken).toBe('someRootValue'); expect(compRef.instance.someToken).toBe('someRootValue');
}); });
fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation') && fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation')
it('should create a component with the passed NgModuleRef', () => { .it('should create a component with the passed NgModuleRef', () => {
@Component({template: ''}) @Component({template: ''})
class RootComp { class RootComp {
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
@ -1204,48 +1203,48 @@ function declareTests(config?: {useJit: boolean}) {
expect(compRef.instance.someToken).toBe('someValue'); expect(compRef.instance.someToken).toBe('someValue');
}); });
fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation') && fixmeIvy('FW-707: TestBed: No LView in getParentInjectorLocation')
it('should create a component with the NgModuleRef of the ComponentFactoryResolver', .it('should create a component with the NgModuleRef of the ComponentFactoryResolver',
() => { () => {
@Component({template: ''}) @Component({template: ''})
class RootComp { class RootComp {
constructor(public vc: ViewContainerRef) {} constructor(public vc: ViewContainerRef) {}
} }
@NgModule({ @NgModule({
declarations: [RootComp], declarations: [RootComp],
providers: [{provide: 'someToken', useValue: 'someRootValue'}], providers: [{provide: 'someToken', useValue: 'someRootValue'}],
}) })
class RootModule { class RootModule {
} }
@Component({template: ''}) @Component({template: ''})
class MyComp { class MyComp {
constructor(@Inject('someToken') public someToken: string) {} constructor(@Inject('someToken') public someToken: string) {}
} }
@NgModule({ @NgModule({
declarations: [MyComp], declarations: [MyComp],
entryComponents: [MyComp], entryComponents: [MyComp],
providers: [{provide: 'someToken', useValue: 'someValue'}], providers: [{provide: 'someToken', useValue: 'someValue'}],
}) })
class MyModule { class MyModule {
} }
const compFixture = TestBed.configureTestingModule({imports: [RootModule]}) const compFixture = TestBed.configureTestingModule({imports: [RootModule]})
.createComponent(RootComp); .createComponent(RootComp);
const compiler = <Compiler>TestBed.get(Compiler); const compiler = <Compiler>TestBed.get(Compiler);
const myModule = const myModule =
compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef)); compiler.compileModuleSync(MyModule).create(TestBed.get(NgModuleRef));
const myCompFactory = const myCompFactory =
myModule.componentFactoryResolver.resolveComponentFactory(MyComp); myModule.componentFactoryResolver.resolveComponentFactory(MyComp);
// Note: MyComp was declared as entryComponent in MyModule, // Note: MyComp was declared as entryComponent in MyModule,
// and we don't pass an explicit ModuleRef to the createComponent call. // and we don't pass an explicit ModuleRef to the createComponent call.
// -> expect the providers of MyModule! // -> expect the providers of MyModule!
const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory); const compRef = compFixture.componentInstance.vc.createComponent(myCompFactory);
expect(compRef.instance.someToken).toBe('someValue'); expect(compRef.instance.someToken).toBe('someValue');
}); });
}); });
describe('.insert', () => { describe('.insert', () => {
@ -1336,7 +1335,7 @@ function declareTests(config?: {useJit: boolean}) {
expect(comp.injectable).toBeAnInstanceOf(InjectableService); expect(comp.injectable).toBeAnInstanceOf(InjectableService);
}); });
fixmeIvy('unknown') && it('should support viewProviders', () => { fixmeIvy('unknown').it('should support viewProviders', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MyComp, DirectiveProvidingInjectableInView, DirectiveConsumingInjectable], declarations: [MyComp, DirectiveProvidingInjectableInView, DirectiveConsumingInjectable],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
@ -1448,8 +1447,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().querySelectorAll(fixture.nativeElement, 'script').length).toEqual(0); expect(getDOM().querySelectorAll(fixture.nativeElement, 'script').length).toEqual(0);
}); });
fixmeIvy('FW-662: Components without selector are not supported') && fixmeIvy('FW-662: Components without selector are not supported')
it('should throw when using directives without selector', () => { .it('should throw when using directives without selector', () => {
@Directive({}) @Directive({})
class SomeDirective { class SomeDirective {
} }
@ -1496,8 +1495,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('error handling', () => { describe('error handling', () => {
fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error') && fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error')
it('should report a meaningful error when a directive is missing annotation', () => { .it('should report a meaningful error when a directive is missing annotation', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, SomeDirectiveMissingAnnotation]}); {declarations: [MyComp, SomeDirectiveMissingAnnotation]});
@ -1506,20 +1505,21 @@ function declareTests(config?: {useJit: boolean}) {
`Unexpected value '${stringify(SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`); `Unexpected value '${stringify(SomeDirectiveMissingAnnotation)}' declared by the module 'DynamicTestModule'. Please add a @Pipe/@Directive/@Component annotation.`);
}); });
fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error') && fixmeIvy('FW-682: TestBed: tests assert that compilation produces specific error')
it('should report a meaningful error when a component is missing view annotation', () => { .it('should report a meaningful error when a component is missing view annotation',
TestBed.configureTestingModule({declarations: [MyComp, ComponentWithoutView]}); () => {
try { TestBed.configureTestingModule({declarations: [MyComp, ComponentWithoutView]});
TestBed.createComponent(ComponentWithoutView); try {
expect(true).toBe(false); TestBed.createComponent(ComponentWithoutView);
} catch (e) { expect(true).toBe(false);
expect(e.message).toContain( } catch (e) {
`No template specified for component ${stringify(ComponentWithoutView)}`); expect(e.message).toContain(
} `No template specified for component ${stringify(ComponentWithoutView)}`);
}); }
});
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented') && fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
it('should provide an error context when an error happens in DI', () => { .it('should provide an error context when an error happens in DI', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MyComp, DirectiveThrowingAnError], declarations: [MyComp, DirectiveThrowingAnError],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
@ -1537,8 +1537,8 @@ function declareTests(config?: {useJit: boolean}) {
} }
}); });
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented') && fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
it('should provide an error context when an error happens in change detection', () => { .it('should provide an error context when an error happens in change detection', () => {
TestBed.configureTestingModule({declarations: [MyComp, DirectiveThrowingAnError]}); TestBed.configureTestingModule({declarations: [MyComp, DirectiveThrowingAnError]});
const template = `<input [value]="one.two.three" #local>`; const template = `<input [value]="one.two.three" #local>`;
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -1556,50 +1556,50 @@ function declareTests(config?: {useJit: boolean}) {
} }
}); });
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented') && fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
it('should provide an error context when an error happens in change detection (text node)', .it('should provide an error context when an error happens in change detection (text node)',
() => { () => {
TestBed.configureTestingModule({declarations: [MyComp]}); TestBed.configureTestingModule({declarations: [MyComp]});
const template = `<div>{{one.two.three}}</div>`; const template = `<div>{{one.two.three}}</div>`;
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
try { try {
fixture.detectChanges(); fixture.detectChanges();
throw 'Should throw'; throw 'Should throw';
} catch (e) { } catch (e) {
const c = getDebugContext(e); const c = getDebugContext(e);
expect(c.renderNode).toBeTruthy(); expect(c.renderNode).toBeTruthy();
} }
}); });
if (getDOM().supportsDOMEvents()) { // this is required to use fakeAsync if (getDOM().supportsDOMEvents()) { // this is required to use fakeAsync
fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented') && fixmeIvy('FW-722: getDebugContext needs to be replaced / re-implemented')
it('should provide an error context when an error happens in an event handler', .it('should provide an error context when an error happens in an event handler',
fakeAsync(() => { fakeAsync(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent], declarations: [MyComp, DirectiveEmittingEvent, DirectiveListeningEvent],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}); });
const template = `<span emitter listener (event)="throwError()" #local></span>`; const template = `<span emitter listener (event)="throwError()" #local></span>`;
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
tick(); tick();
const tc = fixture.debugElement.children[0]; const tc = fixture.debugElement.children[0];
const errorHandler = tc.injector.get(ErrorHandler); const errorHandler = tc.injector.get(ErrorHandler);
let err: any; let err: any;
spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e); spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e);
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom'); tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
expect(err).toBeTruthy(); expect(err).toBeTruthy();
const c = getDebugContext(err); const c = getDebugContext(err);
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN'); expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV'); expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
expect((<Injector>c.injector).get).toBeTruthy(); expect((<Injector>c.injector).get).toBeTruthy();
expect(c.context).toBe(fixture.componentInstance); expect(c.context).toBe(fixture.componentInstance);
expect(c.references['local']).toBeDefined(); expect(c.references['local']).toBeDefined();
})); }));
} }
}); });
@ -1636,8 +1636,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('Property bindings', () => { describe('Property bindings', () => {
fixmeIvy('FW-721: Bindings to unknown properties are not reported as errors') && fixmeIvy('FW-721: Bindings to unknown properties are not reported as errors')
it('should throw on bindings to unknown properties', () => { .it('should throw on bindings to unknown properties', () => {
TestBed.configureTestingModule({declarations: [MyComp]}); TestBed.configureTestingModule({declarations: [MyComp]});
const template = '<div unknown="{{ctxProp}}"></div>'; const template = '<div unknown="{{ctxProp}}"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -1671,8 +1671,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(el.title).toBeFalsy(); expect(el.title).toBeFalsy();
}); });
fixmeIvy('FW-711: elementProperty instruction should not be used in host bindings') && fixmeIvy('FW-711: elementProperty instruction should not be used in host bindings')
it('should work when a directive uses hostProperty to update the DOM element', () => { .it('should work when a directive uses hostProperty to update the DOM element', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MyComp, DirectiveWithTitleAndHostProperty]}); {declarations: [MyComp, DirectiveWithTitleAndHostProperty]});
const template = '<span [title]="ctxProp"></span>'; const template = '<span [title]="ctxProp"></span>';
@ -1688,8 +1688,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('logging property updates', () => { describe('logging property updates', () => {
fixmeIvy('FW-664: ng-reflect-* is not supported') && fixmeIvy('FW-664: ng-reflect-* is not supported')
it('should reflect property values as attributes', () => { .it('should reflect property values as attributes', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '<div>' + const template = '<div>' +
'<div my-dir [elprop]="ctxProp"></div>' + '<div my-dir [elprop]="ctxProp"></div>' +
@ -1712,8 +1712,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test_="hello"'); expect(getDOM().getInnerHTML(fixture.nativeElement)).toContain('ng-reflect-test_="hello"');
}); });
fixmeIvy('FW-664: ng-reflect-* is not supported') && fixmeIvy('FW-664: ng-reflect-* is not supported')
it('should reflect property values on template comments', () => { .it('should reflect property values on template comments', () => {
const fixture = const fixture =
TestBed.configureTestingModule({declarations: [MyComp]}) TestBed.configureTestingModule({declarations: [MyComp]})
.overrideComponent( .overrideComponent(
@ -1729,8 +1729,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
// also affected by FW-587: Inputs with aliases in component decorators don't work // also affected by FW-587: Inputs with aliases in component decorators don't work
fixmeIvy('FW-664: ng-reflect-* is not supported') && fixmeIvy('FW-664: ng-reflect-* is not supported')
it('should indicate when toString() throws', () => { .it('should indicate when toString() throws', () => {
TestBed.configureTestingModule({declarations: [MyComp, MyDir]}); TestBed.configureTestingModule({declarations: [MyComp, MyDir]});
const template = '<div my-dir [elprop]="toStringThrow"></div>'; const template = '<div my-dir [elprop]="toStringThrow"></div>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});

View File

@ -138,8 +138,8 @@ import {fixmeIvy} from '@angular/private/testing';
afterEach(() => { resetTestEnvironmentWithSummaries(); }); afterEach(() => { resetTestEnvironmentWithSummaries(); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should use directive metadata from summaries', () => { .it('should use directive metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
@Component({template: '<div someDir></div>'}) @Component({template: '<div someDir></div>'})
@ -153,8 +153,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomeDirective); expectInstanceCreated(SomeDirective);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should use pipe metadata from summaries', () => { .it('should use pipe metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
@Component({template: '{{1 | somePipe}}'}) @Component({template: '{{1 | somePipe}}'})
@ -166,8 +166,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomePipe); expectInstanceCreated(SomePipe);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should use Service metadata from summaries', () => { .it('should use Service metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -177,8 +177,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomeService); expectInstanceCreated(SomeService);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should use NgModule metadata from summaries', () => { .it('should use NgModule metadata from summaries', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
TestBed TestBed
@ -192,8 +192,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomeService); expectInstanceCreated(SomeService);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should allow to create private components from imported NgModule summaries', () => { .it('should allow to create private components from imported NgModule summaries', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]}) TestBed.configureTestingModule({providers: [SomeDep], imports: [SomeModule]})
@ -201,8 +201,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomePrivateComponent); expectInstanceCreated(SomePrivateComponent);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should throw when trying to mock a type with a summary', () => { .it('should throw when trying to mock a type with a summary', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
TestBed.resetTestingModule(); TestBed.resetTestingModule();
@ -221,35 +221,35 @@ import {fixmeIvy} from '@angular/private/testing';
.toThrowError('SomeModule was AOT compiled, so its metadata cannot be changed.'); .toThrowError('SomeModule was AOT compiled, so its metadata cannot be changed.');
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should return stack trace and component data on resetTestingModule when error is thrown', .it('should return stack trace and component data on resetTestingModule when error is thrown',
() => { () => {
resetTestEnvironmentWithSummaries(); resetTestEnvironmentWithSummaries();
const fixture = const fixture =
TestBed.configureTestingModule({declarations: [TestCompErrorOnDestroy]}) TestBed.configureTestingModule({declarations: [TestCompErrorOnDestroy]})
.createComponent<TestCompErrorOnDestroy>(TestCompErrorOnDestroy); .createComponent<TestCompErrorOnDestroy>(TestCompErrorOnDestroy);
const expectedError = 'Error from ngOnDestroy'; const expectedError = 'Error from ngOnDestroy';
const component: TestCompErrorOnDestroy = fixture.componentInstance; const component: TestCompErrorOnDestroy = fixture.componentInstance;
spyOn(console, 'error'); spyOn(console, 'error');
spyOn(component, 'ngOnDestroy').and.throwError(expectedError); spyOn(component, 'ngOnDestroy').and.throwError(expectedError);
const expectedObject = { const expectedObject = {
stacktrace: new Error(expectedError), stacktrace: new Error(expectedError),
component, component,
}; };
TestBed.resetTestingModule(); TestBed.resetTestingModule();
expect(console.error) expect(console.error)
.toHaveBeenCalledWith('Error during cleanup of component', expectedObject); .toHaveBeenCalledWith('Error during cleanup of component', expectedObject);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should allow to add summaries via configureTestingModule', () => { .it('should allow to add summaries via configureTestingModule', () => {
resetTestEnvironmentWithSummaries(); resetTestEnvironmentWithSummaries();
@Component({template: '<div someDir></div>'}) @Component({template: '<div someDir></div>'})
@ -266,8 +266,8 @@ import {fixmeIvy} from '@angular/private/testing';
expectInstanceCreated(SomeDirective); expectInstanceCreated(SomeDirective);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should allow to override a provider', () => { .it('should allow to override a provider', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
const overwrittenValue = {}; const overwrittenValue = {};
@ -280,8 +280,8 @@ import {fixmeIvy} from '@angular/private/testing';
expect(fixture.componentInstance.dep).toBe(overwrittenValue); expect(fixture.componentInstance.dep).toBe(overwrittenValue);
}); });
fixmeIvy('FW-514: ngSummary shims not generated by ngtsc') && fixmeIvy('FW-514: ngSummary shims not generated by ngtsc')
it('should allow to override a template', () => { .it('should allow to override a template', () => {
resetTestEnvironmentWithSummaries(summaries); resetTestEnvironmentWithSummaries(summaries);
TestBed.overrideTemplateUsingTestingModule(SomePublicComponent, 'overwritten'); TestBed.overrideTemplateUsingTestingModule(SomePublicComponent, 'overwritten');

View File

@ -52,8 +52,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(el).toHaveText('foo'); expect(el).toHaveText('foo');
}); });
fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>') && fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>')
it('should be rendered as comment with children as siblings', () => { .it('should be rendered as comment with children as siblings', () => {
const template = '<ng-container><p></p></ng-container>'; const template = '<ng-container><p></p></ng-container>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -67,8 +67,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().tagName(children[1]).toUpperCase()).toEqual('P'); expect(getDOM().tagName(children[1]).toUpperCase()).toEqual('P');
}); });
fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>') && fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>')
it('should support nesting', () => { .it('should support nesting', () => {
const template = const template =
'<ng-container>1</ng-container><ng-container><ng-container>2</ng-container></ng-container>'; '<ng-container>1</ng-container><ng-container><ng-container>2</ng-container></ng-container>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
@ -86,8 +86,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(children[4]).toHaveText('2'); expect(children[4]).toHaveText('2');
}); });
fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>') && fixmeIvy('FW-678: ivy generates different DOM structure for <ng-container>')
it('should group inner nodes', () => { .it('should group inner nodes', () => {
const template = '<ng-container *ngIf="ctxBoolProp"><p></p><b></b></ng-container>'; const template = '<ng-container *ngIf="ctxBoolProp"><p></p><b></b></ng-container>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
@ -136,8 +136,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(dir.text).toEqual('container'); expect(dir.text).toEqual('container');
}); });
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should contain all direct child directives in a <ng-container> (content dom)', () => { 'should contain all direct child directives in a <ng-container> (content dom)', () => {
const template = const template =
'<needs-content-children #q><ng-container><div text="foo"></div></ng-container></needs-content-children>'; '<needs-content-children #q><ng-container><div text="foo"></div></ng-container></needs-content-children>';
TestBed.overrideComponent(MyComp, {set: {template}}); TestBed.overrideComponent(MyComp, {set: {template}});

View File

@ -143,8 +143,8 @@ function declareTests(config?: {useJit: boolean}) {
} }
describe('errors', () => { describe('errors', () => {
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error when exporting a directive that was neither declared nor imported', () => { .it('should error when exporting a directive that was neither declared nor imported', () => {
@NgModule({exports: [SomeDirective]}) @NgModule({exports: [SomeDirective]})
class SomeModule { class SomeModule {
} }
@ -154,8 +154,8 @@ function declareTests(config?: {useJit: boolean}) {
`Can't export directive ${stringify(SomeDirective)} from ${stringify(SomeModule)} as it was neither declared nor imported!`); `Can't export directive ${stringify(SomeDirective)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error when exporting a pipe that was neither declared nor imported', () => { .it('should error when exporting a pipe that was neither declared nor imported', () => {
@NgModule({exports: [SomePipe]}) @NgModule({exports: [SomePipe]})
class SomeModule { class SomeModule {
} }
@ -165,8 +165,8 @@ function declareTests(config?: {useJit: boolean}) {
`Can't export pipe ${stringify(SomePipe)} from ${stringify(SomeModule)} as it was neither declared nor imported!`); `Can't export pipe ${stringify(SomePipe)} from ${stringify(SomeModule)} as it was neither declared nor imported!`);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error if a directive is declared in more than 1 module', () => { .it('should error if a directive is declared in more than 1 module', () => {
@NgModule({declarations: [SomeDirective]}) @NgModule({declarations: [SomeDirective]})
class Module1 { class Module1 {
} }
@ -184,26 +184,26 @@ function declareTests(config?: {useJit: boolean}) {
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`); `You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error if a directive is declared in more than 1 module also if the module declaring it is imported', .it('should error if a directive is declared in more than 1 module also if the module declaring it is imported',
() => { () => {
@NgModule({declarations: [SomeDirective], exports: [SomeDirective]}) @NgModule({declarations: [SomeDirective], exports: [SomeDirective]})
class Module1 { class Module1 {
} }
@NgModule({declarations: [SomeDirective], imports: [Module1]}) @NgModule({declarations: [SomeDirective], imports: [Module1]})
class Module2 { class Module2 {
} }
expect(() => createModule(Module2)) expect(() => createModule(Module2))
.toThrowError( .toThrowError(
`Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` + `Type ${stringify(SomeDirective)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` + `Please consider moving ${stringify(SomeDirective)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`); `You can also create a new NgModule that exports and includes ${stringify(SomeDirective)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error if a pipe is declared in more than 1 module', () => { .it('should error if a pipe is declared in more than 1 module', () => {
@NgModule({declarations: [SomePipe]}) @NgModule({declarations: [SomePipe]})
class Module1 { class Module1 {
} }
@ -221,29 +221,29 @@ function declareTests(config?: {useJit: boolean}) {
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`); `You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error if a pipe is declared in more than 1 module also if the module declaring it is imported', .it('should error if a pipe is declared in more than 1 module also if the module declaring it is imported',
() => { () => {
@NgModule({declarations: [SomePipe], exports: [SomePipe]}) @NgModule({declarations: [SomePipe], exports: [SomePipe]})
class Module1 { class Module1 {
} }
@NgModule({declarations: [SomePipe], imports: [Module1]}) @NgModule({declarations: [SomePipe], imports: [Module1]})
class Module2 { class Module2 {
} }
expect(() => createModule(Module2)) expect(() => createModule(Module2))
.toThrowError( .toThrowError(
`Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` + `Type ${stringify(SomePipe)} is part of the declarations of 2 modules: ${stringify(Module1)} and ${stringify(Module2)}! ` +
`Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` + `Please consider moving ${stringify(SomePipe)} to a higher module that imports ${stringify(Module1)} and ${stringify(Module2)}. ` +
`You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`); `You can also create a new NgModule that exports and includes ${stringify(SomePipe)} then import that NgModule in ${stringify(Module1)} and ${stringify(Module2)}.`);
}); });
}); });
describe('schemas', () => { describe('schemas', () => {
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should error on unknown bound properties on custom elements by default', () => { .it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'}) @Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty { class ComponentUsingInvalidProperty {
} }
@ -281,16 +281,16 @@ function declareTests(config?: {useJit: boolean}) {
afterEach(() => clearModulesForTest()); afterEach(() => clearModulesForTest());
fixmeIvy('FW-740: missing global registry of NgModules by id') && fixmeIvy('FW-740: missing global registry of NgModules by id')
it('should register loaded modules', () => { .it('should register loaded modules', () => {
createModule(SomeModule); createModule(SomeModule);
const factory = getModuleFactory(token); const factory = getModuleFactory(token);
expect(factory).toBeTruthy(); expect(factory).toBeTruthy();
expect(factory.moduleType).toBe(SomeModule); expect(factory.moduleType).toBe(SomeModule);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when registering a duplicate module', () => { .it('should throw when registering a duplicate module', () => {
createModule(SomeModule); createModule(SomeModule);
expect(() => createModule(SomeOtherModule)).toThrowError(/Duplicate module registered/); expect(() => createModule(SomeOtherModule)).toThrowError(/Duplicate module registered/);
}); });
@ -311,37 +311,37 @@ function declareTests(config?: {useJit: boolean}) {
.toBe(SomeComp); .toBe(SomeComp);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw if we cannot find a module associated with a module-level entryComponent', .it('should throw if we cannot find a module associated with a module-level entryComponent',
() => { () => {
@Component({template: ''}) @Component({template: ''})
class SomeCompWithEntryComponents { class SomeCompWithEntryComponents {
} }
@NgModule({declarations: [], entryComponents: [SomeCompWithEntryComponents]}) @NgModule({declarations: [], entryComponents: [SomeCompWithEntryComponents]})
class SomeModule { class SomeModule {
} }
expect(() => createModule(SomeModule)) expect(() => createModule(SomeModule))
.toThrowError( .toThrowError(
'Component SomeCompWithEntryComponents is not part of any NgModule or the module has not been imported into your module.'); 'Component SomeCompWithEntryComponents is not part of any NgModule or the module has not been imported into your module.');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw if we cannot find a module associated with a component-level entryComponent', .it('should throw if we cannot find a module associated with a component-level entryComponent',
() => { () => {
@Component({template: '', entryComponents: [SomeComp]}) @Component({template: '', entryComponents: [SomeComp]})
class SomeCompWithEntryComponents { class SomeCompWithEntryComponents {
} }
@NgModule({declarations: [SomeCompWithEntryComponents]}) @NgModule({declarations: [SomeCompWithEntryComponents]})
class SomeModule { class SomeModule {
} }
expect(() => createModule(SomeModule)) expect(() => createModule(SomeModule))
.toThrowError( .toThrowError(
'Component SomeComp is not part of any NgModule or the module has not been imported into your module.'); 'Component SomeComp is not part of any NgModule or the module has not been imported into your module.');
}); });
it('should create ComponentFactories via ANALYZE_FOR_ENTRY_COMPONENTS', () => { it('should create ComponentFactories via ANALYZE_FOR_ENTRY_COMPONENTS', () => {
@NgModule({ @NgModule({
@ -426,8 +426,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('directives and pipes', () => { describe('directives and pipes', () => {
fixmeIvy('FW-681: not possible to retrieve host property bindings from TView') && fixmeIvy('FW-681: not possible to retrieve host property bindings from TView')
describe('declarations', () => { .describe('declarations', () => {
it('should be supported in root modules', () => { it('should be supported in root modules', () => {
@NgModule({ @NgModule({
declarations: [CompUsingModuleDirectiveAndPipe, SomeDirective, SomePipe], declarations: [CompUsingModuleDirectiveAndPipe, SomeDirective, SomePipe],
@ -489,8 +489,8 @@ function declareTests(config?: {useJit: boolean}) {
describe('import/export', () => { describe('import/export', () => {
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should support exported directives and pipes', () => { .it('should support exported directives and pipes', () => {
@NgModule( @NgModule(
{declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]}) {declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeImportedModule { class SomeImportedModule {
@ -511,31 +511,31 @@ function declareTests(config?: {useJit: boolean}) {
.toBe('transformed someValue'); .toBe('transformed someValue');
}); });
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should support exported directives and pipes if the module is wrapped into an `ModuleWithProviders`', .it('should support exported directives and pipes if the module is wrapped into an `ModuleWithProviders`',
() => { () => {
@NgModule( @NgModule(
{declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]}) {declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeImportedModule { class SomeImportedModule {
} }
@NgModule({ @NgModule({
declarations: [CompUsingModuleDirectiveAndPipe], declarations: [CompUsingModuleDirectiveAndPipe],
imports: [{ngModule: SomeImportedModule}], imports: [{ngModule: SomeImportedModule}],
entryComponents: [CompUsingModuleDirectiveAndPipe] entryComponents: [CompUsingModuleDirectiveAndPipe]
}) })
class SomeModule { class SomeModule {
} }
const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule); const compFixture = createComp(CompUsingModuleDirectiveAndPipe, SomeModule);
compFixture.detectChanges(); compFixture.detectChanges();
expect(compFixture.debugElement.children[0].properties['title']) expect(compFixture.debugElement.children[0].properties['title'])
.toBe('transformed someValue'); .toBe('transformed someValue');
}); });
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should support reexported modules', () => { .it('should support reexported modules', () => {
@NgModule( @NgModule(
{declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]}) {declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeReexportedModule { class SomeReexportedModule {
@ -559,8 +559,8 @@ function declareTests(config?: {useJit: boolean}) {
.toBe('transformed someValue'); .toBe('transformed someValue');
}); });
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should support exporting individual directives of an imported module', () => { .it('should support exporting individual directives of an imported module', () => {
@NgModule( @NgModule(
{declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]}) {declarations: [SomeDirective, SomePipe], exports: [SomeDirective, SomePipe]})
class SomeReexportedModule { class SomeReexportedModule {
@ -584,8 +584,8 @@ function declareTests(config?: {useJit: boolean}) {
.toBe('transformed someValue'); .toBe('transformed someValue');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should not use non exported pipes of an imported module', () => { .it('should not use non exported pipes of an imported module', () => {
@NgModule({ @NgModule({
declarations: [SomePipe], declarations: [SomePipe],
}) })
@ -604,8 +604,8 @@ function declareTests(config?: {useJit: boolean}) {
.toThrowError(/The pipe 'somePipe' could not be found/); .toThrowError(/The pipe 'somePipe' could not be found/);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should not use non exported directives of an imported module', () => { .it('should not use non exported directives of an imported module', () => {
@NgModule({ @NgModule({
declarations: [SomeDirective], declarations: [SomeDirective],
}) })
@ -667,14 +667,14 @@ function declareTests(config?: {useJit: boolean}) {
expect(car.engine).toBeAnInstanceOf(TurboEngine); expect(car.engine).toBeAnInstanceOf(TurboEngine);
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when no type and not @Inject (class case)', () => { .it('should throw when no type and not @Inject (class case)', () => {
expect(() => createInjector([NoAnnotations])) expect(() => createInjector([NoAnnotations]))
.toThrowError('Can\'t resolve all parameters for NoAnnotations: (?).'); .toThrowError('Can\'t resolve all parameters for NoAnnotations: (?).');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when no type and not @Inject (factory case)', () => { .it('should throw when no type and not @Inject (factory case)', () => {
expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}])) expect(() => createInjector([{provide: 'someToken', useFactory: factoryFn}]))
.toThrowError('Can\'t resolve all parameters for factoryFn: (?).'); .toThrowError('Can\'t resolve all parameters for factoryFn: (?).');
}); });
@ -745,8 +745,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(cars[0]).toBe(injector.get(SportsCar)); expect(cars[0]).toBe(injector.get(SportsCar));
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when the aliased provider does not exist', () => { .it('should throw when the aliased provider does not exist', () => {
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]); const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
const e = `NullInjectorError: No provider for ${stringify(SportsCar)}!`; const e = `NullInjectorError: No provider for ${stringify(SportsCar)}!`;
expect(() => injector.get('car')).toThrowError(e); expect(() => injector.get('car')).toThrowError(e);
@ -796,14 +796,13 @@ function declareTests(config?: {useJit: boolean}) {
expect(injector.get('token')).toEqual('value'); expect(injector.get('token')).toEqual('value');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling').it('should throw when given invalid providers', () => {
it('should throw when given invalid providers', () => { expect(() => createInjector(<any>['blah']))
expect(() => createInjector(<any>['blah'])) .toThrowError(
.toThrowError( `Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?blah?]`);
`Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?blah?]`); });
});
fixmeIvy('FW-682: Compiler error handling') && it('should throw when given blank providers', () => { fixmeIvy('FW-682: Compiler error handling').it('should throw when given blank providers', () => {
expect(() => createInjector(<any>[null, {provide: 'token', useValue: 'value'}])) expect(() => createInjector(<any>[null, {provide: 'token', useValue: 'value'}]))
.toThrowError( .toThrowError(
`Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?null?, ...]`); `Invalid provider for the NgModule 'SomeModule' - only instances of Provider and Type are allowed, got: [?null?, ...]`);
@ -949,8 +948,8 @@ function declareTests(config?: {useJit: boolean}) {
.toThrowError('NullInjectorError: No provider for NonExisting!'); .toThrowError('NullInjectorError: No provider for NonExisting!');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when trying to instantiate a cyclic dependency', () => { .it('should throw when trying to instantiate a cyclic dependency', () => {
expect(() => createInjector([Car, {provide: Engine, useClass: CyclicEngine}])) expect(() => createInjector([Car, {provide: Engine, useClass: CyclicEngine}]))
.toThrowError(/Cannot instantiate cyclic dependency! Car/g); .toThrowError(/Cannot instantiate cyclic dependency! Car/g);
}); });
@ -1053,8 +1052,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(created).toBe(false); expect(created).toBe(false);
}); });
fixmeIvy('FW-739: TestBed: destroy on NgModuleRef is not being called') && fixmeIvy('FW-739: TestBed: destroy on NgModuleRef is not being called')
it('should support ngOnDestroy on any provider', () => { .it('should support ngOnDestroy on any provider', () => {
let destroyed = false; let destroyed = false;
class SomeInjectable { class SomeInjectable {
@ -1073,8 +1072,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(destroyed).toBe(true); expect(destroyed).toBe(true);
}); });
fixmeIvy('FW-739: TestBed: destroy on NgModuleRef is not being called') && fixmeIvy('FW-739: TestBed: destroy on NgModuleRef is not being called')
it('should support ngOnDestroy for lazy providers', () => { .it('should support ngOnDestroy for lazy providers', () => {
let created = false; let created = false;
let destroyed = false; let destroyed = false;
@ -1318,8 +1317,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(injector.get('token1')).toBe('imported2'); expect(injector.get('token1')).toBe('imported2');
}); });
fixmeIvy('FW-682: Compiler error handling') && fixmeIvy('FW-682: Compiler error handling')
it('should throw when given invalid providers in an imported ModuleWithProviders', () => { .it('should throw when given invalid providers in an imported ModuleWithProviders', () => {
@NgModule() @NgModule()
class ImportedModule1 { class ImportedModule1 {
} }
@ -1335,8 +1334,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
describe('tree shakable providers', () => { describe('tree shakable providers', () => {
fixmeIvy('FW-794: NgModuleDefinition not exposed on NgModuleData') && fixmeIvy('FW-794: NgModuleDefinition not exposed on NgModuleData')
it('definition should not persist across NgModuleRef instances', () => { .it('definition should not persist across NgModuleRef instances', () => {
@NgModule() @NgModule()
class SomeModule { class SomeModule {
} }

View File

@ -82,8 +82,8 @@ import {fixmeIvy} from '@angular/private/testing';
expect(main.nativeElement).toHaveText(''); expect(main.nativeElement).toHaveText('');
}); });
fixmeIvy('FW-789: select attribute on <ng-content> should not be case-sensitive') && fixmeIvy('FW-789: select attribute on <ng-content> should not be case-sensitive')
it('should support multiple content tags', () => { .it('should support multiple content tags', () => {
TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]}); TestBed.configureTestingModule({declarations: [MultipleContentTagsComponent]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
set: { set: {
@ -114,8 +114,8 @@ import {fixmeIvy} from '@angular/private/testing';
expect(main.nativeElement).toHaveText('(, BAC)'); expect(main.nativeElement).toHaveText('(, BAC)');
}); });
fixmeIvy('FW-665: Unable to find the given context data for the given target') && fixmeIvy('FW-665: Unable to find the given context data for the given target')
it('should redistribute direct child viewcontainers when the light dom changes', () => { .it('should redistribute direct child viewcontainers when the light dom changes', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [MultipleContentTagsComponent, ManualViewportDirective]}); {declarations: [MultipleContentTagsComponent, ManualViewportDirective]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
@ -158,8 +158,8 @@ import {fixmeIvy} from '@angular/private/testing';
expect(main.nativeElement).toHaveText('OUTER(SIMPLE(AB))'); expect(main.nativeElement).toHaveText('OUTER(SIMPLE(AB))');
}); });
fixmeIvy('FW-665: Unable to find the given context data for the given target') && fixmeIvy('FW-665: Unable to find the given context data for the given target')
it('should support nesting with content being direct child of a nested component', () => { .it('should support nesting with content being direct child of a nested component', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: declarations:
[InnerComponent, InnerInnerComponent, OuterComponent, ManualViewportDirective] [InnerComponent, InnerInnerComponent, OuterComponent, ManualViewportDirective]
@ -186,8 +186,8 @@ import {fixmeIvy} from '@angular/private/testing';
}); });
fixmeIvy( fixmeIvy(
'FW-745: Compiler isn\'t generating projectionDefs for <ng-content> tags inside <ng-templates>') && 'FW-745: Compiler isn\'t generating projectionDefs for <ng-content> tags inside <ng-templates>')
it('should redistribute when the shadow dom changes', () => { .it('should redistribute when the shadow dom changes', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [ConditionalContentComponent, ManualViewportDirective]}); {declarations: [ConditionalContentComponent, ManualViewportDirective]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {
@ -295,8 +295,8 @@ import {fixmeIvy} from '@angular/private/testing';
expect(main.nativeElement).toHaveText('SIMPLE()START(A)END'); expect(main.nativeElement).toHaveText('SIMPLE()START(A)END');
}); });
fixmeIvy('FW-665: Unable to find the given context data for the given target') && fixmeIvy('FW-665: Unable to find the given context data for the given target')
it('should support moving ng-content around', () => { .it('should support moving ng-content around', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: declarations:
[ConditionalContentComponent, ProjectDirective, ManualViewportDirective] [ConditionalContentComponent, ProjectDirective, ManualViewportDirective]
@ -435,8 +435,8 @@ import {fixmeIvy} from '@angular/private/testing';
}); });
} }
fixmeIvy('FW-665: Unable to find the given context data for the given target') && fixmeIvy('FW-665: Unable to find the given context data for the given target')
it('should support nested conditionals that contain ng-contents', () => { .it('should support nested conditionals that contain ng-contents', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [ConditionalTextComponent, ManualViewportDirective]}); {declarations: [ConditionalTextComponent, ManualViewportDirective]});
TestBed.overrideComponent( TestBed.overrideComponent(
@ -482,8 +482,8 @@ import {fixmeIvy} from '@angular/private/testing';
}); });
fixmeIvy( fixmeIvy(
'FW-745: Compiler isn\'t generating projectionDefs for <ng-content> tags inside <ng-templates>') && 'FW-745: Compiler isn\'t generating projectionDefs for <ng-content> tags inside <ng-templates>')
it('should project filled view containers into a view container', () => { .it('should project filled view containers into a view container', () => {
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [ConditionalContentComponent, ManualViewportDirective]}); {declarations: [ConditionalContentComponent, ManualViewportDirective]});
TestBed.overrideComponent(MainComp, { TestBed.overrideComponent(MainComp, {

View File

@ -54,8 +54,8 @@ describe('Query API', () => {
describe('querying by directive type', () => { describe('querying by directive type', () => {
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should contain all direct child directives in the light dom (constructor)', () => { .it('should contain all direct child directives in the light dom (constructor)', () => {
const template = ` const template = `
<div text="1"></div> <div text="1"></div>
<needs-query text="2"> <needs-query text="2">
@ -97,23 +97,23 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)', .it('should contain the first content child when target is on <ng-template> with embedded view (issue #16568)',
() => { () => {
const template = const template =
'<div directive-needs-content-child><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></div>' + '<div directive-needs-content-child><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></div>' +
'<needs-content-child #q><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></needs-content-child>'; '<needs-content-child #q><ng-template text="foo" [ngIf]="true"><div text="bar"></div></ng-template></needs-content-child>';
const view = createTestCmp(MyComp0, template); const view = createTestCmp(MyComp0, template);
view.detectChanges(); view.detectChanges();
const q: NeedsContentChild = view.debugElement.children[1].references !['q']; const q: NeedsContentChild = view.debugElement.children[1].references !['q'];
expect(q.child.text).toEqual('foo'); expect(q.child.text).toEqual('foo');
const directive: DirectiveNeedsContentChild = const directive: DirectiveNeedsContentChild =
view.debugElement.children[0].injector.get(DirectiveNeedsContentChild); view.debugElement.children[0].injector.get(DirectiveNeedsContentChild);
expect(directive.child.text).toEqual('foo'); expect(directive.child.text).toEqual('foo');
}); });
fixmeIvy('FW-782 - View queries are executed twice in some cases') && fixmeIvy('FW-782 - View queries are executed twice in some cases')
it('should contain the first view child', () => { .it('should contain the first view child', () => {
const template = '<needs-view-child #q></needs-view-child>'; const template = '<needs-view-child #q></needs-view-child>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -128,8 +128,8 @@ describe('Query API', () => {
]); ]);
}); });
fixmeIvy('FW-782 - View queries are executed twice in some cases') && fixmeIvy('FW-782 - View queries are executed twice in some cases')
it('should set static view and content children already after the constructor call', () => { .it('should set static view and content children already after the constructor call', () => {
const template = const template =
'<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>'; '<needs-static-content-view-child #q><div text="contentFoo"></div></needs-static-content-view-child>';
const view = createTestCmp(MyComp0, template); const view = createTestCmp(MyComp0, template);
@ -142,8 +142,8 @@ describe('Query API', () => {
expect(q.viewChild.text).toEqual('viewFoo'); expect(q.viewChild.text).toEqual('viewFoo');
}); });
fixmeIvy('FW-782 - View queries are executed twice in some cases') && fixmeIvy('FW-782 - View queries are executed twice in some cases')
it('should contain the first view child across embedded views', () => { .it('should contain the first view child across embedded views', () => {
TestBed.overrideComponent( TestBed.overrideComponent(
MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}}); MyComp0, {set: {template: '<needs-view-child #q></needs-view-child>'}});
TestBed.overrideComponent(NeedsViewChild, { TestBed.overrideComponent(NeedsViewChild, {
@ -172,8 +172,8 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should contain all directives in the light dom when descendants flag is used', () => { .it('should contain all directives in the light dom when descendants flag is used', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query-desc text="2"><div text="3">' + '<needs-query-desc text="2"><div text="3">' +
'<div text="4"></div>' + '<div text="4"></div>' +
@ -185,8 +185,8 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should contain all directives in the light dom', () => { .it('should contain all directives in the light dom', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div text="3"></div></needs-query>' + '<needs-query text="2"><div text="3"></div></needs-query>' +
'<div text="4"></div>'; '<div text="4"></div>';
@ -196,8 +196,8 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should reflect dynamically inserted directives', () => { .it('should reflect dynamically inserted directives', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' + '<needs-query text="2"><div *ngIf="shouldShow" [text]="\'3\'"></div></needs-query>' +
'<div text="4"></div>'; '<div text="4"></div>';
@ -221,8 +221,8 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should reflect moved directives', () => { .it('should reflect moved directives', () => {
const template = '<div text="1"></div>' + const template = '<div text="1"></div>' +
'<needs-query text="2"><div *ngFor="let i of list" [text]="i"></div></needs-query>' + '<needs-query text="2"><div *ngFor="let i of list" [text]="i"></div></needs-query>' +
'<div text="4"></div>'; '<div text="4"></div>';
@ -234,8 +234,8 @@ describe('Query API', () => {
expect(asNativeElements(view.debugElement.children)).toHaveText('2|3d|2d|'); expect(asNativeElements(view.debugElement.children)).toHaveText('2|3d|2d|');
}); });
fixmeIvy('FW-682 - TestBed: tests assert that compilation produces specific error') && fixmeIvy('FW-682 - TestBed: tests assert that compilation produces specific error')
it('should throw with descriptive error when query selectors are not present', () => { .it('should throw with descriptive error when query selectors are not present', () => {
TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]}); TestBed.configureTestingModule({declarations: [MyCompBroken0, HasNullQueryCondition]});
const template = '<has-null-query-condition></has-null-query-condition>'; const template = '<has-null-query-condition></has-null-query-condition>';
TestBed.overrideComponent(MyCompBroken0, {set: {template}}); TestBed.overrideComponent(MyCompBroken0, {set: {template}});
@ -268,8 +268,8 @@ describe('Query API', () => {
describe('read a different token', () => { describe('read a different token', () => {
modifiedInIvy( modifiedInIvy(
'Breaking change in Ivy: no longer allow multiple local refs with the same name, all local refs are now unique') && 'Breaking change in Ivy: no longer allow multiple local refs with the same name, all local refs are now unique')
it('should contain all content children', () => { .it('should contain all content children', () => {
const template = const template =
'<needs-content-children-read #q text="ca"><div #q text="cb"></div></needs-content-children-read>'; '<needs-content-children-read #q text="ca"><div #q text="cb"></div></needs-content-children-read>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -477,17 +477,19 @@ describe('Query API', () => {
describe('querying in the view', () => { describe('querying in the view', () => {
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should contain all the elements in the view with that have the given directive', () => { .it('should contain all the elements in the view with that have the given directive',
const template = '<needs-view-query #q><div text="ignoreme"></div></needs-view-query>'; () => {
const view = createTestCmpAndDetectChanges(MyComp0, template); const template =
const q: NeedsViewQuery = view.debugElement.children[0].references !['q']; '<needs-view-query #q><div text="ignoreme"></div></needs-view-query>';
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']); const view = createTestCmpAndDetectChanges(MyComp0, template);
}); const q: NeedsViewQuery = view.debugElement.children[0].references !['q'];
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2', '3', '4']);
});
fixmeIvy( fixmeIvy(
'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy') && 'FW-781 - Directives invocation sequence on root and nested elements is different in Ivy')
it('should not include directive present on the host element', () => { .it('should not include directive present on the host element', () => {
const template = '<needs-view-query #q text="self"></needs-view-query>'; const template = '<needs-view-query #q text="self"></needs-view-query>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const q: NeedsViewQuery = view.debugElement.children[0].references !['q']; const q: NeedsViewQuery = view.debugElement.children[0].references !['q'];
@ -588,8 +590,8 @@ describe('Query API', () => {
}); });
// Note: this test is just document our current behavior, which we do for performance reasons. // Note: this test is just document our current behavior, which we do for performance reasons.
fixmeIvy('FW-782 - View queries are executed twice in some cases') && fixmeIvy('FW-782 - View queries are executed twice in some cases')
it('should not affected queries for projected templates if views are detached or moved', () => { .it('should not affected queries for projected templates if views are detached or moved', () => {
const template = const template =
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>'; '<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
@ -615,8 +617,8 @@ describe('Query API', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-763 - LView tree not properly constructed / destroyed for dynamically inserted components') && 'FW-763 - LView tree not properly constructed / destroyed for dynamically inserted components')
it('should remove manually projected templates if their parent view is destroyed', () => { .it('should remove manually projected templates if their parent view is destroyed', () => {
const template = ` const template = `
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting> <manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
<div *ngIf="shouldShow"> <div *ngIf="shouldShow">
@ -635,34 +637,34 @@ describe('Query API', () => {
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
}); });
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should not throw if a content template is queried and created in the view during change detection', 'should not throw if a content template is queried and created in the view during change detection',
() => { () => {
@Component( @Component(
{selector: 'auto-projecting', template: '<div *ngIf="true; then: content"></div>'}) {selector: 'auto-projecting', template: '<div *ngIf="true; then: content"></div>'})
class AutoProjecting { class AutoProjecting {
// TODO(issue/24571): // TODO(issue/24571):
// remove '!'. // remove '!'.
@ContentChild(TemplateRef) @ContentChild(TemplateRef)
content !: TemplateRef<any>; content !: TemplateRef<any>;
// TODO(issue/24571): // TODO(issue/24571):
// remove '!'. // remove '!'.
@ContentChildren(TextDirective) @ContentChildren(TextDirective)
query !: QueryList<TextDirective>; query !: QueryList<TextDirective>;
} }
TestBed.configureTestingModule({declarations: [AutoProjecting]}); TestBed.configureTestingModule({declarations: [AutoProjecting]});
const template = const template =
'<auto-projecting #q><ng-template><div text="1"></div></ng-template></auto-projecting>'; '<auto-projecting #q><ng-template><div text="1"></div></ng-template></auto-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template); const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q']; const q = view.debugElement.children[0].references !['q'];
// This should be 1, but due to // This should be 1, but due to
// https://github.com/angular/angular/issues/15117 // https://github.com/angular/angular/issues/15117
// this is 0. // this is 0.
expect(q.query.length).toBe(0); expect(q.query.length).toBe(0);
}); });
}); });
}); });

View File

@ -32,7 +32,7 @@ function declareTests(config?: {useJit: boolean}) {
describe('platform pipes', () => { describe('platform pipes', () => {
beforeEach(() => { TestBed.configureCompiler({...config}); }); beforeEach(() => { TestBed.configureCompiler({...config}); });
fixmeIvy('unknown') && it('should overwrite them by custom pipes', () => { fixmeIvy('unknown').it('should overwrite them by custom pipes', () => {
TestBed.configureTestingModule({declarations: [CustomPipe]}); TestBed.configureTestingModule({declarations: [CustomPipe]});
const template = '{{true | somePipe}}'; const template = '{{true | somePipe}}';
TestBed.overrideComponent(MyComp1, {set: {template}}); TestBed.overrideComponent(MyComp1, {set: {template}});
@ -74,46 +74,46 @@ function declareTests(config?: {useJit: boolean}) {
expect(CountingPipe.calls).toBe(1); expect(CountingPipe.calls).toBe(1);
}); });
fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account') && fixmeIvy('FW-756: Pipes and directives from imported modules are not taken into account')
it('should only update the bound property when using asyncPipe - #15205', .it('should only update the bound property when using asyncPipe - #15205',
fakeAsync(() => { fakeAsync(() => {
@Component({template: '<div myDir [a]="p | async" [b]="2"></div>'}) @Component({template: '<div myDir [a]="p | async" [b]="2"></div>'})
class MyComp { class MyComp {
p = Promise.resolve(1); p = Promise.resolve(1);
} }
@Directive({selector: '[myDir]'}) @Directive({selector: '[myDir]'})
class MyDir { class MyDir {
setterCalls: {[key: string]: any} = {}; setterCalls: {[key: string]: any} = {};
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
changes !: SimpleChanges; changes !: SimpleChanges;
@Input() @Input()
set a(v: number) { this.setterCalls['a'] = v; } set a(v: number) { this.setterCalls['a'] = v; }
@Input() @Input()
set b(v: number) { this.setterCalls['b'] = v; } set b(v: number) { this.setterCalls['b'] = v; }
ngOnChanges(changes: SimpleChanges) { this.changes = changes; } ngOnChanges(changes: SimpleChanges) { this.changes = changes; }
} }
TestBed.configureTestingModule({declarations: [MyDir, MyComp]}); TestBed.configureTestingModule({declarations: [MyDir, MyComp]});
const fixture = TestBed.createComponent(MyComp); const fixture = TestBed.createComponent(MyComp);
const dir = const dir =
fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir) as MyDir; fixture.debugElement.query(By.directive(MyDir)).injector.get(MyDir) as MyDir;
fixture.detectChanges(); fixture.detectChanges();
expect(dir.setterCalls).toEqual({'a': null, 'b': 2}); expect(dir.setterCalls).toEqual({'a': null, 'b': 2});
expect(Object.keys(dir.changes)).toEqual(['a', 'b']); expect(Object.keys(dir.changes)).toEqual(['a', 'b']);
dir.setterCalls = {}; dir.setterCalls = {};
dir.changes = {}; dir.changes = {};
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
expect(dir.setterCalls).toEqual({'a': 1}); expect(dir.setterCalls).toEqual({'a': 1});
expect(Object.keys(dir.changes)).toEqual(['a']); expect(Object.keys(dir.changes)).toEqual(['a']);
})); }));
it('should only evaluate methods once - #10639', () => { it('should only evaluate methods once - #10639', () => {
TestBed.configureTestingModule({declarations: [MyCountingComp]}); TestBed.configureTestingModule({declarations: [MyCountingComp]});
@ -333,8 +333,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(fixture.debugElement.childNodes.length).toBe(0); expect(fixture.debugElement.childNodes.length).toBe(0);
}); });
modifiedInIvy('Comment node order changed') && modifiedInIvy('Comment node order changed')
it('should allow empty embedded templates', () => { .it('should allow empty embedded templates', () => {
@Component({template: '<ng-template [ngIf]="true"></ng-template>'}) @Component({template: '<ng-template [ngIf]="true"></ng-template>'})
class MyComp { class MyComp {
} }
@ -351,37 +351,36 @@ function declareTests(config?: {useJit: boolean}) {
}); });
}); });
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should support @ContentChild and @Input on the same property for static queries', 'should support @ContentChild and @Input on the same property for static queries', () => {
() => { @Directive({selector: 'test'})
@Directive({selector: 'test'}) class Test {
class Test { // TODO(issue/24571): remove '!'.
// TODO(issue/24571): remove '!'. @Input() @ContentChild(TemplateRef) tpl !: TemplateRef<any>;
@Input() @ContentChild(TemplateRef) tpl !: TemplateRef<any>; }
}
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
template: ` template: `
<test></test><br> <test></test><br>
<test><ng-template>Custom as a child</ng-template></test><br> <test><ng-template>Custom as a child</ng-template></test><br>
<ng-template #custom>Custom as a binding</ng-template> <ng-template #custom>Custom as a binding</ng-template>
<test [tpl]="custom"></test><br> <test [tpl]="custom"></test><br>
` `
}) })
class App { class App {
} }
const fixture = const fixture =
TestBed.configureTestingModule({declarations: [App, Test]}).createComponent(App); TestBed.configureTestingModule({declarations: [App, Test]}).createComponent(App);
fixture.detectChanges(); fixture.detectChanges();
const testDirs = const testDirs =
fixture.debugElement.queryAll(By.directive(Test)).map(el => el.injector.get(Test)); fixture.debugElement.queryAll(By.directive(Test)).map(el => el.injector.get(Test));
expect(testDirs[0].tpl).toBeUndefined(); expect(testDirs[0].tpl).toBeUndefined();
expect(testDirs[1].tpl).toBeDefined(); expect(testDirs[1].tpl).toBeDefined();
expect(testDirs[2].tpl).toBeDefined(); expect(testDirs[2].tpl).toBeDefined();
}); });
it('should not add ng-version for dynamically created components', () => { it('should not add ng-version for dynamically created components', () => {
@Component({template: ''}) @Component({template: ''})

View File

@ -52,8 +52,8 @@ function declareTests(config?: {useJit: boolean}) {
afterEach(() => { getDOM().log = originalLog; }); afterEach(() => { getDOM().log = originalLog; });
describe('events', () => { describe('events', () => {
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state') && fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
it('should disallow binding to attr.on*', () => { .it('should disallow binding to attr.on*', () => {
const template = `<div [attr.onclick]="ctxProp"></div>`; const template = `<div [attr.onclick]="ctxProp"></div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}); TestBed.overrideComponent(SecuredComponent, {set: {template}});
@ -62,8 +62,8 @@ function declareTests(config?: {useJit: boolean}) {
/Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../); /Binding to event attribute 'onclick' is disallowed for security reasons, please use \(click\)=.../);
}); });
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state') && fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => { .it('should disallow binding to on* with NO_ERRORS_SCHEMA', () => {
const template = `<div [onclick]="ctxProp"></div>`; const template = `<div [onclick]="ctxProp"></div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({ TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@ -75,8 +75,8 @@ function declareTests(config?: {useJit: boolean}) {
}); });
fixmeIvy( fixmeIvy(
'FW-786: Element properties and directive inputs are not distinguished for sanitisation purposes') && 'FW-786: Element properties and directive inputs are not distinguished for sanitisation purposes')
it('should disallow binding to on* unless it is consumed by a directive', () => { .it('should disallow binding to on* unless it is consumed by a directive', () => {
const template = `<div [onPrefixedProp]="ctxProp" [onclick]="ctxProp"></div>`; const template = `<div [onPrefixedProp]="ctxProp" [onclick]="ctxProp"></div>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({ TestBed.overrideComponent(SecuredComponent, {set: {template}}).configureTestingModule({
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
@ -173,8 +173,8 @@ function declareTests(config?: {useJit: boolean}) {
checkEscapeOfHrefProperty(fixture, true); checkEscapeOfHrefProperty(fixture, true);
}); });
fixmeIvy('FW-785: Host bindings are not sanitised') && fixmeIvy('FW-785: Host bindings are not sanitised')
it('should escape unsafe properties if they are used in host bindings', () => { .it('should escape unsafe properties if they are used in host bindings', () => {
@Directive({selector: '[dirHref]'}) @Directive({selector: '[dirHref]'})
class HrefDirective { class HrefDirective {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@ -190,8 +190,8 @@ function declareTests(config?: {useJit: boolean}) {
checkEscapeOfHrefProperty(fixture, false); checkEscapeOfHrefProperty(fixture, false);
}); });
fixmeIvy('FW-785: Host bindings are not sanitised') && fixmeIvy('FW-785: Host bindings are not sanitised')
it('should escape unsafe attributes if they are used in host bindings', () => { .it('should escape unsafe attributes if they are used in host bindings', () => {
@Directive({selector: '[dirHref]'}) @Directive({selector: '[dirHref]'})
class HrefDirective { class HrefDirective {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@ -227,8 +227,8 @@ function declareTests(config?: {useJit: boolean}) {
expect(getDOM().getStyle(e, 'background')).not.toContain('javascript'); expect(getDOM().getStyle(e, 'background')).not.toContain('javascript');
}); });
fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state') && fixmeIvy('FW-787: Exception in template parsing leaves TestBed in corrupted state')
it('should escape unsafe SVG attributes', () => { .it('should escape unsafe SVG attributes', () => {
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`; const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
TestBed.overrideComponent(SecuredComponent, {set: {template}}); TestBed.overrideComponent(SecuredComponent, {set: {template}});

View File

@ -102,167 +102,167 @@ import {fixmeIvy} from '@angular/private/testing';
function declareTests( function declareTests(
{ngUrl, templateDecorator}: {ngUrl, templateDecorator}:
{ngUrl: string, templateDecorator: (template: string) => { [key: string]: any }}) { {ngUrl: string, templateDecorator: (template: string) => { [key: string]: any }}) {
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should use the right source url in html parse errors', fakeAsync(() => { 'should use the right source url in html parse errors', fakeAsync(() => {
@Component({...templateDecorator('<div>\n </error>')}) @Component({...templateDecorator('<div>\n </error>')})
class MyComp { class MyComp {
} }
expect(() => compileAndCreateComponent(MyComp)) expect(() => compileAndCreateComponent(MyComp))
.toThrowError(new RegExp( .toThrowError(
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`)); new RegExp(`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:2`));
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should use the right source url in template parse errors', fakeAsync(() => { 'should use the right source url in template parse errors', fakeAsync(() => {
@Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')}) @Component({...templateDecorator('<div>\n <div unknown="{{ctxProp}}"></div>')})
class MyComp { class MyComp {
} }
expect(() => compileAndCreateComponent(MyComp)) expect(() => compileAndCreateComponent(MyComp))
.toThrowError(new RegExp( .toThrowError(
`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:7`)); new RegExp(`Template parse errors[\\s\\S]*${ngUrl.replace('$', '\\$')}@1:7`));
})); }));
fixmeIvy('unknown') && it('should create a sourceMap for templates', fakeAsync(() => { fixmeIvy('unknown').it('should create a sourceMap for templates', fakeAsync(() => {
const template = `Hello World!`; const template = `Hello World!`;
@Component({...templateDecorator(template)}) @Component({...templateDecorator(template)})
class MyComp { class MyComp {
} }
compileAndCreateComponent(MyComp); compileAndCreateComponent(MyComp);
const sourceMap = const sourceMap =
getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js'); getSourceMap('ng:///DynamicTestModule/MyComp.ngfactory.js');
expect(sourceMap.sources).toEqual([ expect(sourceMap.sources).toEqual([
'ng:///DynamicTestModule/MyComp.ngfactory.js', ngUrl 'ng:///DynamicTestModule/MyComp.ngfactory.js', ngUrl
]); ]);
expect(sourceMap.sourcesContent).toEqual([' ', template]); expect(sourceMap.sourcesContent).toEqual([' ', template]);
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should report source location for di errors', fakeAsync(() => { 'should report source location for di errors', fakeAsync(() => {
const template = `<div>\n <div someDir></div></div>`; const template = `<div>\n <div someDir></div></div>`;
@Component({...templateDecorator(template)}) @Component({...templateDecorator(template)})
class MyComp { class MyComp {
} }
@Directive({selector: '[someDir]'}) @Directive({selector: '[someDir]'})
class SomeDir { class SomeDir {
constructor() { throw new Error('Test'); } constructor() { throw new Error('Test'); }
} }
TestBed.configureTestingModule({declarations: [SomeDir]}); TestBed.configureTestingModule({declarations: [SomeDir]});
let error: any; let error: any;
try { try {
compileAndCreateComponent(MyComp); compileAndCreateComponent(MyComp);
} catch (e) { } catch (e) {
error = e; error = e;
} }
// The error should be logged from the element // The error should be logged from the element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: ngUrl, source: ngUrl,
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should report di errors with multiple elements and directives', fakeAsync(() => { 'should report di errors with multiple elements and directives', fakeAsync(() => {
const template = `<div someDir></div><div someDir="throw"></div>`; const template = `<div someDir></div><div someDir="throw"></div>`;
@Component({...templateDecorator(template)}) @Component({...templateDecorator(template)})
class MyComp { class MyComp {
} }
@Directive({selector: '[someDir]'}) @Directive({selector: '[someDir]'})
class SomeDir { class SomeDir {
constructor(@Attribute('someDir') someDir: string) { constructor(@Attribute('someDir') someDir: string) {
if (someDir === 'throw') { if (someDir === 'throw') {
throw new Error('Test'); throw new Error('Test');
} }
} }
} }
TestBed.configureTestingModule({declarations: [SomeDir]}); TestBed.configureTestingModule({declarations: [SomeDir]});
let error: any; let error: any;
try { try {
compileAndCreateComponent(MyComp); compileAndCreateComponent(MyComp);
} catch (e) { } catch (e) {
error = e; error = e;
} }
// The error should be logged from the 2nd-element // The error should be logged from the 2nd-element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 1, line: 1,
column: 19, column: 19,
source: ngUrl, source: ngUrl,
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should report source location for binding errors', fakeAsync(() => { 'should report source location for binding errors', fakeAsync(() => {
const template = `<div>\n <span [title]="createError()"></span></div>`; const template = `<div>\n <span [title]="createError()"></span></div>`;
@Component({...templateDecorator(template)}) @Component({...templateDecorator(template)})
class MyComp { class MyComp {
createError() { throw new Error('Test'); } createError() { throw new Error('Test'); }
} }
const comp = compileAndCreateComponent(MyComp); const comp = compileAndCreateComponent(MyComp);
let error: any; let error: any;
try { try {
comp.detectChanges(); comp.detectChanges();
} catch (e) { } catch (e) {
error = e; error = e;
} }
// the stack should point to the binding // the stack should point to the binding
expect(getSourcePositionForStack(error.stack)).toEqual({ expect(getSourcePositionForStack(error.stack)).toEqual({
line: 2, line: 2,
column: 12, column: 12,
source: ngUrl, source: ngUrl,
}); });
// The error should be logged from the element // The error should be logged from the element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: ngUrl, source: ngUrl,
}); });
})); }));
fixmeIvy('unknown') && fixmeIvy('unknown').it(
it('should report source location for event errors', fakeAsync(() => { 'should report source location for event errors', fakeAsync(() => {
const template = `<div>\n <span (click)="createError()"></span></div>`; const template = `<div>\n <span (click)="createError()"></span></div>`;
@Component({...templateDecorator(template)}) @Component({...templateDecorator(template)})
class MyComp { class MyComp {
createError() { throw new Error('Test'); } createError() { throw new Error('Test'); }
} }
const comp = compileAndCreateComponent(MyComp); const comp = compileAndCreateComponent(MyComp);
let error: any; let error: any;
const errorHandler = TestBed.get(ErrorHandler); const errorHandler = TestBed.get(ErrorHandler);
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e); spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT'); comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
expect(error).toBeTruthy(); expect(error).toBeTruthy();
// the stack should point to the binding // the stack should point to the binding
expect(getSourcePositionForStack(error.stack)).toEqual({ expect(getSourcePositionForStack(error.stack)).toEqual({
line: 2, line: 2,
column: 12, column: 12,
source: ngUrl, source: ngUrl,
}); });
// The error should be logged from the element // The error should be logged from the element
expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({ expect(getSourcePositionForStack(getErrorLoggerStack(error))).toEqual({
line: 2, line: 2,
column: 4, column: 4,
source: ngUrl, source: ngUrl,
}); });
})); }));
} }
}); });
} }

View File

@ -138,7 +138,7 @@ import {fixmeIvy} from '@angular/private/testing';
expect(instance.dep instanceof Dep).toBeTruthy(); expect(instance.dep instanceof Dep).toBeTruthy();
}); });
fixmeIvy('unknown') && it('should not inject deps from sibling root elements', () => { fixmeIvy('unknown').it('should not inject deps from sibling root elements', () => {
const rootElNodes = [ const rootElNodes = [
elementDef(0, NodeFlags.None, null, null, 1, 'span'), elementDef(0, NodeFlags.None, null, null, 1, 'span'),
directiveDef(1, NodeFlags.None, null, 0, Dep, []), directiveDef(1, NodeFlags.None, null, 0, Dep, []),
@ -181,7 +181,7 @@ import {fixmeIvy} from '@angular/private/testing';
expect(instance.dep instanceof Dep).toBeTruthy(); expect(instance.dep instanceof Dep).toBeTruthy();
}); });
fixmeIvy('unknown') && it('should throw for missing dependencies', () => { fixmeIvy('unknown').it('should throw for missing dependencies', () => {
expect(() => createAndGetRootNodes(compViewDef([ expect(() => createAndGetRootNodes(compViewDef([
elementDef(0, NodeFlags.None, null, null, 1, 'span'), elementDef(0, NodeFlags.None, null, null, 1, 'span'),
directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep']) directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep'])

View File

@ -160,7 +160,8 @@ function bootstrap(
afterEach(destroyPlatform); afterEach(destroyPlatform);
fixmeIvy('FW-553: TestBed is unaware of async compilation') && // TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
fixmeIvy('FW-553: TestBed is unaware of async compilation').isEnabled &&
it('should throw if bootstrapped Directive is not a Component', it('should throw if bootstrapped Directive is not a Component',
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => { inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
const logger = new MockConsole(); const logger = new MockConsole();
@ -188,7 +189,8 @@ function bootstrap(
}); });
})); }));
fixmeIvy('FW-553: TestBed is unaware of async compilation') && // TODO(misko): can't use `fixmeIvy.it` because the `it` is somehow special here.
fixmeIvy('FW-553: TestBed is unaware of async compilation').isEnabled &&
it('should throw if no provider', it('should throw if no provider',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const logger = new MockConsole(); const logger = new MockConsole();

View File

@ -251,8 +251,8 @@ class CompWithUrlTemplate {
expect(compFixture.componentInstance).toBeAnInstanceOf(CompUsingModuleDirectiveAndPipe); expect(compFixture.componentInstance).toBeAnInstanceOf(CompUsingModuleDirectiveAndPipe);
}); });
fixmeIvy('FW-681: not possible to retrieve host property bindings from TView') && fixmeIvy('FW-681: not possible to retrieve host property bindings from TView')
it('should use set up directives and pipes', () => { .it('should use set up directives and pipes', () => {
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe); const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
const el = compFixture.debugElement; const el = compFixture.debugElement;
@ -289,14 +289,14 @@ class CompWithUrlTemplate {
expect(service.value).toEqual('real value'); expect(service.value).toEqual('real value');
})); }));
fixmeIvy('FW-681: not possible to retrieve host property bindings from TView') && fixmeIvy('FW-681: not possible to retrieve host property bindings from TView')
it('should use set up directives and pipes', withModule(moduleConfig, () => { .it('should use set up directives and pipes', withModule(moduleConfig, () => {
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe); const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
const el = compFixture.debugElement; const el = compFixture.debugElement;
compFixture.detectChanges(); compFixture.detectChanges();
expect(el.children[0].properties['title']).toBe('transformed someValue'); expect(el.children[0].properties['title']).toBe('transformed someValue');
})); }));
it('should use set up library modules', it('should use set up library modules',
withModule(moduleConfig).inject([SomeLibModule], (libModule: SomeLibModule) => { withModule(moduleConfig).inject([SomeLibModule], (libModule: SomeLibModule) => {
@ -310,12 +310,13 @@ class CompWithUrlTemplate {
TestBed.compileComponents(); TestBed.compileComponents();
})); }));
fixmeIvy('FW-553: TestBed is unaware of async compilation') && isBrowser && isBrowser &&
it('should allow to createSync components with templateUrl after explicit async compilation', fixmeIvy('FW-553: TestBed is unaware of async compilation')
() => { .it('should allow to createSync components with templateUrl after explicit async compilation',
const fixture = TestBed.createComponent(CompWithUrlTemplate); () => {
expect(fixture.nativeElement).toHaveText('from external template'); const fixture = TestBed.createComponent(CompWithUrlTemplate);
}); expect(fixture.nativeElement).toHaveText('from external template');
});
}); });
describe('overwriting metadata', () => { describe('overwriting metadata', () => {
@ -371,8 +372,8 @@ class CompWithUrlTemplate {
.overrideDirective( .overrideDirective(
SomeDirective, {set: {selector: '[someDir]', host: {'[title]': 'someProp'}}}); SomeDirective, {set: {selector: '[someDir]', host: {'[title]': 'someProp'}}});
}); });
fixmeIvy('FW-681: not possible to retrieve host property bindings from TView') && fixmeIvy('FW-681: not possible to retrieve host property bindings from TView')
it('should work', () => { .it('should work', () => {
const compFixture = TestBed.createComponent(SomeComponent); const compFixture = TestBed.createComponent(SomeComponent);
compFixture.detectChanges(); compFixture.detectChanges();
expect(compFixture.debugElement.children[0].properties['title']).toEqual('hello'); expect(compFixture.debugElement.children[0].properties['title']).toEqual('hello');
@ -387,8 +388,8 @@ class CompWithUrlTemplate {
.overridePipe(SomePipe, {set: {name: 'somePipe'}}) .overridePipe(SomePipe, {set: {name: 'somePipe'}})
.overridePipe(SomePipe, {add: {pure: false}}); .overridePipe(SomePipe, {add: {pure: false}});
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should work', () => { .it('should work', () => {
const compFixture = TestBed.createComponent(SomeComponent); const compFixture = TestBed.createComponent(SomeComponent);
compFixture.detectChanges(); compFixture.detectChanges();
expect(compFixture.nativeElement).toHaveText('transformed hello'); expect(compFixture.nativeElement).toHaveText('transformed hello');
@ -458,8 +459,8 @@ class CompWithUrlTemplate {
expect(TestBed.get('a')).toBe('mockA: depValue'); expect(TestBed.get('a')).toBe('mockA: depValue');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support SkipSelf', () => { .it('should support SkipSelf', () => {
@NgModule({ @NgModule({
providers: [ providers: [
{provide: 'a', useValue: 'aValue'}, {provide: 'a', useValue: 'aValue'},
@ -501,8 +502,8 @@ class CompWithUrlTemplate {
expect(someModule).toBeAnInstanceOf(SomeModule); expect(someModule).toBeAnInstanceOf(SomeModule);
}); });
obsoleteInIvy(`deprecated method, won't be reimplemented for Render3`) && obsoleteInIvy(`deprecated method, won't be reimplemented for Render3`)
it('should keep imported NgModules lazy with deprecatedOverrideProvider', () => { .it('should keep imported NgModules lazy with deprecatedOverrideProvider', () => {
let someModule: SomeModule|undefined; let someModule: SomeModule|undefined;
@NgModule() @NgModule()
@ -553,8 +554,8 @@ class CompWithUrlTemplate {
}); });
describe('in Components', () => { describe('in Components', () => {
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support useValue', () => { .it('should support useValue', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -571,8 +572,8 @@ class CompWithUrlTemplate {
expect(ctx.debugElement.injector.get('a')).toBe('mockValue'); expect(ctx.debugElement.injector.get('a')).toBe('mockValue');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support useFactory', () => { .it('should support useFactory', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -591,8 +592,8 @@ class CompWithUrlTemplate {
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue'); expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support @Optional without matches', () => { .it('should support @Optional without matches', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -611,8 +612,8 @@ class CompWithUrlTemplate {
expect(ctx.debugElement.injector.get('a')).toBe('mockA: null'); expect(ctx.debugElement.injector.get('a')).toBe('mockA: null');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support Optional with matches', () => { .it('should support Optional with matches', () => {
@Component({ @Component({
template: '', template: '',
providers: [ providers: [
@ -632,8 +633,8 @@ class CompWithUrlTemplate {
expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue'); expect(ctx.debugElement.injector.get('a')).toBe('mockA: depValue');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support SkipSelf', () => { .it('should support SkipSelf', () => {
@Directive({ @Directive({
selector: '[myDir]', selector: '[myDir]',
providers: [ providers: [
@ -662,8 +663,8 @@ class CompWithUrlTemplate {
.toBe('mockA: parentDepValue'); .toBe('mockA: parentDepValue');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should support multiple providers in a template', () => { .it('should support multiple providers in a template', () => {
@Directive({ @Directive({
selector: '[myDir1]', selector: '[myDir1]',
providers: [ providers: [
@ -708,9 +709,8 @@ class CompWithUrlTemplate {
constructor(@Inject('a') a: any, @Inject('b') b: any) {} constructor(@Inject('a') a: any, @Inject('b') b: any) {}
} }
fixmeIvy( fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
'FW-788: Support metadata override in TestBed (for AOT-compiled components)') && .it('should inject providers that were declared before it', () => {
it('should inject providers that were declared before it', () => {
TestBed.overrideProvider( TestBed.overrideProvider(
'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']}); 'b', {useFactory: (a: string) => `mockB: ${a}`, deps: ['a']});
const ctx = TestBed.configureTestingModule({declarations: [MyComp]}) const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
@ -719,9 +719,8 @@ class CompWithUrlTemplate {
expect(ctx.debugElement.injector.get('b')).toBe('mockB: aValue'); expect(ctx.debugElement.injector.get('b')).toBe('mockB: aValue');
}); });
fixmeIvy( fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
'FW-788: Support metadata override in TestBed (for AOT-compiled components)') && .it('should inject providers that were declared after it', () => {
it('should inject providers that were declared after it', () => {
TestBed.overrideProvider( TestBed.overrideProvider(
'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']}); 'a', {useFactory: (b: string) => `mockA: ${b}`, deps: ['b']});
const ctx = TestBed.configureTestingModule({declarations: [MyComp]}) const ctx = TestBed.configureTestingModule({declarations: [MyComp]})
@ -741,8 +740,8 @@ class CompWithUrlTemplate {
}); });
describe('overrideTemplateUsingTestingModule', () => { describe('overrideTemplateUsingTestingModule', () => {
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should compile the template in the context of the testing module', () => { .it('should compile the template in the context of the testing module', () => {
@Component({selector: 'comp', template: 'a'}) @Component({selector: 'comp', template: 'a'})
class MyComponent { class MyComponent {
prop = 'some prop'; prop = 'some prop';
@ -770,8 +769,8 @@ class CompWithUrlTemplate {
expect(testDir !.test).toBe('some prop'); expect(testDir !.test).toBe('some prop');
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should throw if the TestBed is already created', () => { .it('should throw if the TestBed is already created', () => {
@Component({selector: 'comp', template: 'a'}) @Component({selector: 'comp', template: 'a'})
class MyComponent { class MyComponent {
} }
@ -783,8 +782,8 @@ class CompWithUrlTemplate {
/Cannot override template when the test module has already been instantiated/); /Cannot override template when the test module has already been instantiated/);
}); });
fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)') && fixmeIvy('FW-788: Support metadata override in TestBed (for AOT-compiled components)')
it('should reset overrides when the testing module is resetted', () => { .it('should reset overrides when the testing module is resetted', () => {
@Component({selector: 'comp', template: 'a'}) @Component({selector: 'comp', template: 'a'})
class MyComponent { class MyComponent {
} }
@ -809,30 +808,30 @@ class CompWithUrlTemplate {
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]}); {providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
}); });
fixmeIvy('FW-553: TestBed is unaware of async compilation') && fixmeIvy('FW-553: TestBed is unaware of async compilation')
it('should use set up providers', fakeAsync(() => { .it('should use set up providers', fakeAsync(() => {
TestBed.compileComponents(); TestBed.compileComponents();
tick(); tick();
const compFixture = TestBed.createComponent(CompWithUrlTemplate); const compFixture = TestBed.createComponent(CompWithUrlTemplate);
expect(compFixture.nativeElement).toHaveText('Hello world!'); expect(compFixture.nativeElement).toHaveText('Hello world!');
})); }));
}); });
describe('useJit true', () => { describe('useJit true', () => {
beforeEach(() => TestBed.configureCompiler({useJit: true})); beforeEach(() => TestBed.configureCompiler({useJit: true}));
obsoleteInIvy('the Render3 compiler JiT mode is not configurable') && obsoleteInIvy('the Render3 compiler JiT mode is not configurable')
it('should set the value into CompilerConfig', .it('should set the value into CompilerConfig',
inject([CompilerConfig], (config: CompilerConfig) => { inject([CompilerConfig], (config: CompilerConfig) => {
expect(config.useJit).toBe(true); expect(config.useJit).toBe(true);
})); }));
}); });
describe('useJit false', () => { describe('useJit false', () => {
beforeEach(() => TestBed.configureCompiler({useJit: false})); beforeEach(() => TestBed.configureCompiler({useJit: false}));
obsoleteInIvy('the Render3 compiler JiT mode is not configurable') && obsoleteInIvy('the Render3 compiler JiT mode is not configurable')
it('should set the value into CompilerConfig', .it('should set the value into CompilerConfig',
inject([CompilerConfig], (config: CompilerConfig) => { inject([CompilerConfig], (config: CompilerConfig) => {
expect(config.useJit).toBe(false); expect(config.useJit).toBe(false);
})); }));
}); });
}); });
}); });
@ -921,28 +920,28 @@ class CompWithUrlTemplate {
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]}); {providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
}); });
fixmeIvy('FW-553: TestBed is unaware of async compilation') && fixmeIvy('FW-553: TestBed is unaware of async compilation')
it('should report an error for declared components with templateUrl which never call TestBed.compileComponents', .it('should report an error for declared components with templateUrl which never call TestBed.compileComponents',
() => { () => {
const itPromise = patchJasmineIt(); const itPromise = patchJasmineIt();
expect( expect(
() => () => it(
it('should fail', withModule( 'should fail', withModule(
{declarations: [CompWithUrlTemplate]}, {declarations: [CompWithUrlTemplate]},
() => TestBed.createComponent(CompWithUrlTemplate)))) () => TestBed.createComponent(CompWithUrlTemplate))))
.toThrowError( .toThrowError(
`This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` + `This test module uses the component ${stringify(CompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
`Please call "TestBed.compileComponents" before your test.`); `Please call "TestBed.compileComponents" before your test.`);
restoreJasmineIt(); restoreJasmineIt();
}); });
}); });
fixmeIvy(`FW-721: Bindings to unknown properties are not reported as errors`) && fixmeIvy(`FW-721: Bindings to unknown properties are not reported as errors`)
it('should error on unknown bound properties on custom elements by default', () => { .it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'}) @Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty { class ComponentUsingInvalidProperty {
} }

View File

@ -579,28 +579,28 @@ class HiddenModule {
}); });
}))); })));
fixmeIvy('FW-672: SVG xlink:href is sanitized to :xlink:href (extra ":")') && fixmeIvy('FW-672: SVG xlink:href is sanitized to :xlink:href (extra ":")')
it('works with SVG elements', async(() => { .it('works with SVG elements', async(() => {
renderModule(SVGServerModule, {document: doc}).then(output => { renderModule(SVGServerModule, {document: doc}).then(output => {
expect(output).toBe( expect(output).toBe(
'<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' + '<html><head></head><body><app ng-version="0.0.0-PLACEHOLDER">' +
'<svg><use xlink:href="#clear"></use></svg></app></body></html>'); '<svg><use xlink:href="#clear"></use></svg></app></body></html>');
called = true; called = true;
}); });
})); }));
fixmeIvy( fixmeIvy(
`FW-643: Components with animations throw with "Failed to execute 'setAttribute' on 'Element'`) && `FW-643: Components with animations throw with "Failed to execute 'setAttribute' on 'Element'`)
it('works with animation', async(() => { .it('works with animation', async(() => {
renderModule(AnimationServerModule, {document: doc}).then(output => { renderModule(AnimationServerModule, {document: doc}).then(output => {
expect(output).toContain('Works!'); expect(output).toContain('Works!');
expect(output).toContain('ng-trigger-myAnimation'); expect(output).toContain('ng-trigger-myAnimation');
expect(output).toContain('opacity:1;'); expect(output).toContain('opacity:1;');
expect(output).toContain('transform:translate3d(0 , 0 , 0);'); expect(output).toContain('transform:translate3d(0 , 0 , 0);');
expect(output).toContain('font-weight:bold;'); expect(output).toContain('font-weight:bold;');
called = true; called = true;
}); });
})); }));
it('should handle ViewEncapsulation.Native', async(() => { it('should handle ViewEncapsulation.Native', async(() => {
renderModule(NativeExampleModule, {document: doc}).then(output => { renderModule(NativeExampleModule, {document: doc}).then(output => {

View File

@ -95,44 +95,44 @@ let lastCreatedRenderer: Renderer2;
expect(renderEl).toHaveText('Hello World!'); expect(renderEl).toHaveText('Hello World!');
}); });
fixmeIvy('#FW-750 - fixture.debugElement is null') && fixmeIvy('#FW-750 - fixture.debugElement is null')
it('should update any element property/attributes/class/style(s) independent of the compilation on the root element and other elements', .it('should update any element property/attributes/class/style(s) independent of the compilation on the root element and other elements',
() => { () => {
const fixture = const fixture =
TestBed.overrideTemplate(MyComp2, '<input [title]="y" style="position:absolute">') TestBed.overrideTemplate(MyComp2, '<input [title]="y" style="position:absolute">')
.createComponent(MyComp2); .createComponent(MyComp2);
const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => { const checkSetters = (componentRef: ComponentRef<any>, workerEl: any) => {
expect(lastCreatedRenderer).not.toBeNull(); expect(lastCreatedRenderer).not.toBeNull();
const el = getRenderElement(workerEl); const el = getRenderElement(workerEl);
lastCreatedRenderer.setProperty(workerEl, 'tabIndex', 1); lastCreatedRenderer.setProperty(workerEl, 'tabIndex', 1);
expect(el.tabIndex).toEqual(1); expect(el.tabIndex).toEqual(1);
lastCreatedRenderer.addClass(workerEl, 'a'); lastCreatedRenderer.addClass(workerEl, 'a');
expect(getDOM().hasClass(el, 'a')).toBe(true); expect(getDOM().hasClass(el, 'a')).toBe(true);
lastCreatedRenderer.removeClass(workerEl, 'a'); lastCreatedRenderer.removeClass(workerEl, 'a');
expect(getDOM().hasClass(el, 'a')).toBe(false); expect(getDOM().hasClass(el, 'a')).toBe(false);
lastCreatedRenderer.setStyle(workerEl, 'width', '10px'); lastCreatedRenderer.setStyle(workerEl, 'width', '10px');
expect(getDOM().getStyle(el, 'width')).toEqual('10px'); expect(getDOM().getStyle(el, 'width')).toEqual('10px');
lastCreatedRenderer.removeStyle(workerEl, 'width'); lastCreatedRenderer.removeStyle(workerEl, 'width');
expect(getDOM().getStyle(el, 'width')).toEqual(''); expect(getDOM().getStyle(el, 'width')).toEqual('');
lastCreatedRenderer.setAttribute(workerEl, 'someattr', 'someValue'); lastCreatedRenderer.setAttribute(workerEl, 'someattr', 'someValue');
expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue'); expect(getDOM().getAttribute(el, 'someattr')).toEqual('someValue');
}; };
// root element // root element
checkSetters(fixture.componentRef, fixture.nativeElement); checkSetters(fixture.componentRef, fixture.nativeElement);
// nested elements // nested elements
checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement); checkSetters(fixture.componentRef, fixture.debugElement.children[0].nativeElement);
}); });
fixmeIvy('#FW-664 ng-reflect-* is not supported') && fixmeIvy('#FW-664 ng-reflect-* is not supported')
it('should update any template comment property/attributes', () => { .it('should update any template comment property/attributes', () => {
const fixture = const fixture =
TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>') TestBed.overrideTemplate(MyComp2, '<ng-container *ngIf="ctxBoolProp"></ng-container>')
.createComponent(MyComp2); .createComponent(MyComp2);
@ -161,7 +161,7 @@ let lastCreatedRenderer: Renderer2;
}); });
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
fixmeIvy('#FW-750 - fixture.debugElement is null') && it('should listen to events', () => { fixmeIvy('#FW-750 - fixture.debugElement is null').it('should listen to events', () => {
const fixture = TestBed.overrideTemplate(MyComp2, '<input (change)="ctxNumProp = 1">') const fixture = TestBed.overrideTemplate(MyComp2, '<input (change)="ctxNumProp = 1">')
.createComponent(MyComp2); .createComponent(MyComp2);

View File

@ -8,6 +8,14 @@
import {bazelDefineCompileValue} from './bazel_define_compile_value'; import {bazelDefineCompileValue} from './bazel_define_compile_value';
/**
* Set this constant to `true` to run all tests and report which of the tests marked with `fixmeIvy`
* are actually already passing.
*
* This is useful for locating already passing tests. The already passing tests should have their
* `fixmeIvy` removed.
*/
const FIND_PASSING_TESTS = false;
/** /**
* A function to conditionally include a test or a block of tests only when tests run against Ivy. * A function to conditionally include a test or a block of tests only when tests run against Ivy.
@ -33,17 +41,21 @@ export const ivyEnabled = 'aot' === (bazelDefineCompileValue as string);
* when running against Ivy. * when running against Ivy.
* *
* ``` * ```
* fixmeIvy('some reason') && describe(...); * fixmeIvy('some reason').describe(...);
* ``` * ```
* *
* or * or
* *
* ``` * ```
* fixmeIvy('some reason') && it(...); * fixmeIvy('some reason').it(...);
* ``` * ```
*/ */
export function fixmeIvy(reason: string): boolean { export function fixmeIvy(reason: string): JasmineMethods {
return !ivyEnabled; if (FIND_PASSING_TESTS) {
return ivyEnabled ? PASSTHROUGH : IGNORE;
} else {
return ivyEnabled ? IGNORE : PASSTHROUGH;
}
} }
@ -54,17 +66,17 @@ export function fixmeIvy(reason: string): boolean {
* Any tests disabled using this switch should not be user-facing breaking changes. * Any tests disabled using this switch should not be user-facing breaking changes.
* *
* ``` * ```
* obsoleteInIvy('some reason') && describe(...); * obsoleteInIvy('some reason').describe(...);
* ``` * ```
* *
* or * or
* *
* ``` * ```
* obsoleteInIvy('some reason') && it(...); * obsoleteInIvy('some reason').it(...);
* ``` * ```
*/ */
export function obsoleteInIvy(reason: string): boolean { export function obsoleteInIvy(reason: string): JasmineMethods {
return !ivyEnabled; return ivyEnabled ? IGNORE : PASSTHROUGH;
} }
/** /**
@ -75,15 +87,87 @@ export function obsoleteInIvy(reason: string): boolean {
* documented as a breaking change. * documented as a breaking change.
* *
* ``` * ```
* modifiedInIvy('some reason') && describe(...); * modifiedInIvy('some reason').describe(...);
* ``` * ```
* *
* or * or
* *
* ``` * ```
* modifiedInIvy('some reason') && it(...); * modifiedInIvy('some reason').it(...);
* ``` * ```
*/ */
export function modifiedInIvy(reason: string): boolean { export function modifiedInIvy(reason: string): JasmineMethods {
return !ivyEnabled; return ivyEnabled ? IGNORE : PASSTHROUGH;
}
export interface JasmineMethods {
it: typeof it;
fit: typeof fit;
describe: typeof describe;
fdescribe: typeof fdescribe;
fixmeIvy: typeof fixmeIvy;
isEnabled: boolean;
}
const PASSTHROUGH: JasmineMethods = {
it: maybeAppendFindPassingTestsMarker(it),
fit: maybeAppendFindPassingTestsMarker(fit),
describe: maybeAppendFindPassingTestsMarker(describe),
fdescribe: maybeAppendFindPassingTestsMarker(fdescribe),
fixmeIvy: maybeAppendFindPassingTestsMarker(fixmeIvy),
isEnabled: true,
};
const FIND_PASSING_TESTS_MARKER = '__FIND_PASSING_TESTS_MARKER__';
function maybeAppendFindPassingTestsMarker<T extends Function>(fn: T): T {
return FIND_PASSING_TESTS ? function(...args: any[]) {
if (typeof args[0] == 'string') {
args[0] += FIND_PASSING_TESTS_MARKER;
}
return fn.apply(this, args);
} : fn as any;
}
function noop() {}
const IGNORE: JasmineMethods = {
it: noop,
fit: noop,
describe: noop,
fdescribe: noop,
fixmeIvy: (reason) => IGNORE,
isEnabled: false,
};
if (FIND_PASSING_TESTS) {
const env = jasmine.getEnv();
const passingTests: jasmine.CustomReporterResult[] = [];
const stillFailing: jasmine.CustomReporterResult[] = [];
let specCount = 0;
env.clearReporters();
env.addReporter({
specDone: function(result: jasmine.CustomReporterResult) {
specCount++;
if (result.fullName.indexOf(FIND_PASSING_TESTS_MARKER) != -1) {
(result.status == 'passed' ? passingTests : stillFailing).push(result);
}
},
jasmineDone: function(details: jasmine.RunDetails) {
if (passingTests.length) {
passingTests.forEach((result) => {
// tslint:disable-next-line:no-console
console.log('ALREADY PASSING', result.fullName.replace(FIND_PASSING_TESTS_MARKER, ''));
});
// tslint:disable-next-line:no-console
console.log(
`${specCount} specs,`, //
`${passingTests.length} passing specs,`, //
`${stillFailing.length} still failing specs`);
} else {
// tslint:disable-next-line:no-console
console.log('NO PASSING TESTS FOUND.');
}
}
});
} }

View File

@ -467,38 +467,38 @@ describe('Integration', () => {
expect(location.path()).toEqual('/child/simple'); expect(location.path()).toEqual('/child/simple');
}))); })));
fixmeIvy('FW-768: markViewDirty instruction is scheduling a tick') && fixmeIvy('FW-768: markViewDirty instruction is scheduling a tick')
it('should work when an outlet is added/removed', fakeAsync(() => { .it('should work when an outlet is added/removed', fakeAsync(() => {
@Component({ @Component({
selector: 'someRoot', selector: 'someRoot',
template: `[<div *ngIf="cond"><router-outlet></router-outlet></div>]` template: `[<div *ngIf="cond"><router-outlet></router-outlet></div>]`
}) })
class RootCmpWithLink { class RootCmpWithLink {
cond: boolean = true; cond: boolean = true;
} }
TestBed.configureTestingModule({declarations: [RootCmpWithLink]}); TestBed.configureTestingModule({declarations: [RootCmpWithLink]});
const router: Router = TestBed.get(Router); const router: Router = TestBed.get(Router);
const fixture = createRoot(router, RootCmpWithLink); const fixture = createRoot(router, RootCmpWithLink);
router.resetConfig([ router.resetConfig([
{path: 'simple', component: SimpleCmp}, {path: 'simple', component: SimpleCmp},
{path: 'blank', component: BlankCmp}, {path: 'blank', component: BlankCmp},
]); ]);
router.navigateByUrl('/simple'); router.navigateByUrl('/simple');
advance(fixture); advance(fixture);
expect(fixture.nativeElement).toHaveText('[simple]'); expect(fixture.nativeElement).toHaveText('[simple]');
fixture.componentInstance.cond = false; fixture.componentInstance.cond = false;
advance(fixture); advance(fixture);
expect(fixture.nativeElement).toHaveText('[]'); expect(fixture.nativeElement).toHaveText('[]');
fixture.componentInstance.cond = true; fixture.componentInstance.cond = true;
advance(fixture); advance(fixture);
expect(fixture.nativeElement).toHaveText('[simple]'); expect(fixture.nativeElement).toHaveText('[simple]');
})); }));
it('should update location when navigating', fakeAsync(() => { it('should update location when navigating', fakeAsync(() => {
@Component({template: `record`}) @Component({template: `record`})
@ -2595,46 +2595,46 @@ describe('Integration', () => {
expect(canceledStatus).toEqual(false); expect(canceledStatus).toEqual(false);
}))); })));
fixmeIvy('FW-766: One router test is wrong') && fixmeIvy('FW-766: One router test is wrong')
it('works with componentless routes', .it('works with componentless routes',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => { fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([ router.resetConfig([
{ {
path: 'grandparent', path: 'grandparent',
canDeactivate: ['RecordingDeactivate'], canDeactivate: ['RecordingDeactivate'],
children: [{ children: [{
path: 'parent', path: 'parent',
canDeactivate: ['RecordingDeactivate'], canDeactivate: ['RecordingDeactivate'],
children: [{ children: [{
path: 'child', path: 'child',
canDeactivate: ['RecordingDeactivate'], canDeactivate: ['RecordingDeactivate'],
children: [{ children: [{
path: 'simple', path: 'simple',
component: SimpleCmp, component: SimpleCmp,
canDeactivate: ['RecordingDeactivate'] canDeactivate: ['RecordingDeactivate']
}] }]
}] }]
}] }]
}, },
{path: 'simple', component: SimpleCmp} {path: 'simple', component: SimpleCmp}
]); ]);
router.navigateByUrl('/grandparent/parent/child/simple'); router.navigateByUrl('/grandparent/parent/child/simple');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/grandparent/parent/child/simple'); expect(location.path()).toEqual('/grandparent/parent/child/simple');
router.navigateByUrl('/simple'); router.navigateByUrl('/simple');
advance(fixture); advance(fixture);
const child = fixture.debugElement.children[1].componentInstance; const child = fixture.debugElement.children[1].componentInstance;
expect(log.map((a: any) => a.path)).toEqual([ expect(log.map((a: any) => a.path)).toEqual([
'simple', 'child', 'parent', 'grandparent' 'simple', 'child', 'parent', 'grandparent'
]); ]);
expect(log.map((a: any) => a.component)).toEqual([child, null, null, null]); expect(log.map((a: any) => a.component)).toEqual([child, null, null, null]);
}))); })));
it('works with aux routes', it('works with aux routes',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => { fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
@ -3524,94 +3524,94 @@ describe('Integration', () => {
expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]'); expect(fixture.nativeElement).toHaveText('lazy-loaded-parent [lazy-loaded-child]');
}))); })));
fixmeIvy('FW-646: Directive providers don\'t support primitive types as DI tokens') && fixmeIvy('FW-646: Directive providers don\'t support primitive types as DI tokens')
it('should have 2 injector trees: module and element', .it('should have 2 injector trees: module and element',
fakeAsync(inject( fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
@Component({ @Component({
selector: 'lazy', selector: 'lazy',
template: 'parent[<router-outlet></router-outlet>]', template: 'parent[<router-outlet></router-outlet>]',
viewProviders: [ viewProviders: [
{provide: 'shadow', useValue: 'from parent component'}, {provide: 'shadow', useValue: 'from parent component'},
], ],
}) })
class Parent { class Parent {
} }
@Component({selector: 'lazy', template: 'child'}) @Component({selector: 'lazy', template: 'child'})
class Child { class Child {
} }
@NgModule({ @NgModule({
declarations: [Parent], declarations: [Parent],
imports: [RouterModule.forChild([{ imports: [RouterModule.forChild([{
path: 'parent', path: 'parent',
component: Parent, component: Parent,
children: [ children: [
{path: 'child', loadChildren: 'child'}, {path: 'child', loadChildren: 'child'},
] ]
}])], }])],
providers: [ providers: [
{provide: 'moduleName', useValue: 'parent'}, {provide: 'moduleName', useValue: 'parent'},
{provide: 'fromParent', useValue: 'from parent'}, {provide: 'fromParent', useValue: 'from parent'},
], ],
}) })
class ParentModule { class ParentModule {
} }
@NgModule({ @NgModule({
declarations: [Child], declarations: [Child],
imports: [RouterModule.forChild([{path: '', component: Child}])], imports: [RouterModule.forChild([{path: '', component: Child}])],
providers: [ providers: [
{provide: 'moduleName', useValue: 'child'}, {provide: 'moduleName', useValue: 'child'},
{provide: 'fromChild', useValue: 'from child'}, {provide: 'fromChild', useValue: 'from child'},
{provide: 'shadow', useValue: 'from child module'}, {provide: 'shadow', useValue: 'from child module'},
], ],
}) })
class ChildModule { class ChildModule {
} }
loader.stubbedModules = { loader.stubbedModules = {
parent: ParentModule, parent: ParentModule,
child: ChildModule, child: ChildModule,
}; };
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'parent'}]); router.resetConfig([{path: 'lazy', loadChildren: 'parent'}]);
router.navigateByUrl('/lazy/parent/child'); router.navigateByUrl('/lazy/parent/child');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/lazy/parent/child'); expect(location.path()).toEqual('/lazy/parent/child');
expect(fixture.nativeElement).toHaveText('parent[child]'); expect(fixture.nativeElement).toHaveText('parent[child]');
const pInj = fixture.debugElement.query(By.directive(Parent)).injector !; const pInj = fixture.debugElement.query(By.directive(Parent)).injector !;
const cInj = fixture.debugElement.query(By.directive(Child)).injector !; const cInj = fixture.debugElement.query(By.directive(Child)).injector !;
expect(pInj.get('moduleName')).toEqual('parent'); expect(pInj.get('moduleName')).toEqual('parent');
expect(pInj.get('fromParent')).toEqual('from parent'); expect(pInj.get('fromParent')).toEqual('from parent');
expect(pInj.get(Parent)).toBeAnInstanceOf(Parent); expect(pInj.get(Parent)).toBeAnInstanceOf(Parent);
expect(pInj.get('fromChild', null)).toEqual(null); expect(pInj.get('fromChild', null)).toEqual(null);
expect(pInj.get(Child, null)).toEqual(null); expect(pInj.get(Child, null)).toEqual(null);
expect(cInj.get('moduleName')).toEqual('child'); expect(cInj.get('moduleName')).toEqual('child');
expect(cInj.get('fromParent')).toEqual('from parent'); expect(cInj.get('fromParent')).toEqual('from parent');
expect(cInj.get('fromChild')).toEqual('from child'); expect(cInj.get('fromChild')).toEqual('from child');
expect(cInj.get(Parent)).toBeAnInstanceOf(Parent); expect(cInj.get(Parent)).toBeAnInstanceOf(Parent);
expect(cInj.get(Child)).toBeAnInstanceOf(Child); expect(cInj.get(Child)).toBeAnInstanceOf(Child);
// The child module can not shadow the parent component // The child module can not shadow the parent component
expect(cInj.get('shadow')).toEqual('from parent component'); expect(cInj.get('shadow')).toEqual('from parent component');
const pmInj = pInj.get(NgModuleRef).injector; const pmInj = pInj.get(NgModuleRef).injector;
const cmInj = cInj.get(NgModuleRef).injector; const cmInj = cInj.get(NgModuleRef).injector;
expect(pmInj.get('moduleName')).toEqual('parent'); expect(pmInj.get('moduleName')).toEqual('parent');
expect(cmInj.get('moduleName')).toEqual('child'); expect(cmInj.get('moduleName')).toEqual('child');
expect(pmInj.get(Parent, '-')).toEqual('-'); expect(pmInj.get(Parent, '-')).toEqual('-');
expect(cmInj.get(Parent, '-')).toEqual('-'); expect(cmInj.get(Parent, '-')).toEqual('-');
expect(pmInj.get(Child, '-')).toEqual('-'); expect(pmInj.get(Child, '-')).toEqual('-');
expect(cmInj.get(Child, '-')).toEqual('-'); expect(cmInj.get(Child, '-')).toEqual('-');
}))); })));
// https://github.com/angular/angular/issues/12889 // https://github.com/angular/angular/issues/12889
it('should create a single instance of lazy-loaded modules', it('should create a single instance of lazy-loaded modules',
@ -3653,56 +3653,56 @@ describe('Integration', () => {
// https://github.com/angular/angular/issues/13870 // https://github.com/angular/angular/issues/13870
fixmeIvy( fixmeIvy(
'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components') && 'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components')
it('should create a single instance of guards for lazy-loaded modules', .it('should create a single instance of guards for lazy-loaded modules',
fakeAsync(inject( fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
@Injectable() @Injectable()
class Service { class Service {
} }
@Injectable() @Injectable()
class Resolver implements Resolve<Service> { class Resolver implements Resolve<Service> {
constructor(public service: Service) {} constructor(public service: Service) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.service; return this.service;
} }
} }
@Component({selector: 'lazy', template: 'lazy'}) @Component({selector: 'lazy', template: 'lazy'})
class LazyLoadedComponent { class LazyLoadedComponent {
resolvedService: Service; resolvedService: Service;
constructor(public injectedService: Service, route: ActivatedRoute) { constructor(public injectedService: Service, route: ActivatedRoute) {
this.resolvedService = route.snapshot.data['service']; this.resolvedService = route.snapshot.data['service'];
} }
} }
@NgModule({ @NgModule({
declarations: [LazyLoadedComponent], declarations: [LazyLoadedComponent],
providers: [Service, Resolver], providers: [Service, Resolver],
imports: [ imports: [
RouterModule.forChild([{ RouterModule.forChild([{
path: 'loaded', path: 'loaded',
component: LazyLoadedComponent, component: LazyLoadedComponent,
resolve: {'service': Resolver}, resolve: {'service': Resolver},
}]), }]),
] ]
}) })
class LoadedModule { class LoadedModule {
} }
loader.stubbedModules = {expected: LoadedModule}; loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]); router.resetConfig([{path: 'lazy', loadChildren: 'expected'}]);
router.navigateByUrl('/lazy/loaded'); router.navigateByUrl('/lazy/loaded');
advance(fixture); advance(fixture);
expect(fixture.nativeElement).toHaveText('lazy'); expect(fixture.nativeElement).toHaveText('lazy');
const lzc = fixture.debugElement.query(By.directive(LazyLoadedComponent)) const lzc = fixture.debugElement.query(By.directive(LazyLoadedComponent))
.componentInstance; .componentInstance;
expect(lzc.injectedService).toBe(lzc.resolvedService); expect(lzc.injectedService).toBe(lzc.resolvedService);
}))); })));
it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded', it('should emit RouteConfigLoadStart and RouteConfigLoadEnd event when route is lazy loaded',
@ -3954,27 +3954,27 @@ describe('Integration', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components') && 'FW-767: Lazy loaded modules are not used when resolving dependencies in one of their components')
it('should use the injector of the lazily-loaded configuration', .it('should use the injector of the lazily-loaded configuration',
fakeAsync(inject( fakeAsync(inject(
[Router, Location, NgModuleFactoryLoader], [Router, Location, NgModuleFactoryLoader],
(router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => { (router: Router, location: Location, loader: SpyNgModuleFactoryLoader) => {
loader.stubbedModules = {expected: LoadedModule}; loader.stubbedModules = {expected: LoadedModule};
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);
router.resetConfig([{ router.resetConfig([{
path: 'eager-parent', path: 'eager-parent',
component: EagerParentComponent, component: EagerParentComponent,
children: [{path: 'lazy', loadChildren: 'expected'}] children: [{path: 'lazy', loadChildren: 'expected'}]
}]); }]);
router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child'); router.navigateByUrl('/eager-parent/lazy/lazy-parent/lazy-child');
advance(fixture); advance(fixture);
expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child'); expect(location.path()).toEqual('/eager-parent/lazy/lazy-parent/lazy-child');
expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child'); expect(fixture.nativeElement).toHaveText('eager-parent lazy-parent lazy-child');
}))); })));
}); });
it('works when given a callback', it('works when given a callback',
@ -4439,82 +4439,82 @@ describe('Integration', () => {
expect(simpleCmp1).not.toBe(simpleCmp2); expect(simpleCmp1).not.toBe(simpleCmp2);
}))); })));
fixmeIvy('FW-768: markViewDirty instruction is scheduling a tick') && fixmeIvy('FW-768: markViewDirty instruction is scheduling a tick')
it('should not mount the component of the previously reused route when the outlet was not instantiated at the time of route activation', .it('should not mount the component of the previously reused route when the outlet was not instantiated at the time of route activation',
fakeAsync(() => { fakeAsync(() => {
@Component({ @Component({
selector: 'root-cmp', selector: 'root-cmp',
template: template:
'<div *ngIf="isToolpanelShowing"><router-outlet name="toolpanel"></router-outlet></div>' '<div *ngIf="isToolpanelShowing"><router-outlet name="toolpanel"></router-outlet></div>'
}) })
class RootCmpWithCondOutlet implements OnDestroy { class RootCmpWithCondOutlet implements OnDestroy {
private subscription: Subscription; private subscription: Subscription;
public isToolpanelShowing: boolean = false; public isToolpanelShowing: boolean = false;
constructor(router: Router) { constructor(router: Router) {
this.subscription = this.subscription =
router.events.pipe(filter(event => event instanceof NavigationEnd)) router.events.pipe(filter(event => event instanceof NavigationEnd))
.subscribe( .subscribe(
() => this.isToolpanelShowing = () => this.isToolpanelShowing =
!!router.parseUrl(router.url).root.children['toolpanel']); !!router.parseUrl(router.url).root.children['toolpanel']);
} }
public ngOnDestroy(): void { this.subscription.unsubscribe(); } public ngOnDestroy(): void { this.subscription.unsubscribe(); }
} }
@Component({selector: 'tool-1-cmp', template: 'Tool 1 showing'}) @Component({selector: 'tool-1-cmp', template: 'Tool 1 showing'})
class Tool1Component { class Tool1Component {
} }
@Component({selector: 'tool-2-cmp', template: 'Tool 2 showing'}) @Component({selector: 'tool-2-cmp', template: 'Tool 2 showing'})
class Tool2Component { class Tool2Component {
} }
@NgModule({ @NgModule({
declarations: [RootCmpWithCondOutlet, Tool1Component, Tool2Component], declarations: [RootCmpWithCondOutlet, Tool1Component, Tool2Component],
imports: [ imports: [
CommonModule, CommonModule,
RouterTestingModule.withRoutes([ RouterTestingModule.withRoutes([
{path: 'a', outlet: 'toolpanel', component: Tool1Component}, {path: 'a', outlet: 'toolpanel', component: Tool1Component},
{path: 'b', outlet: 'toolpanel', component: Tool2Component}, {path: 'b', outlet: 'toolpanel', component: Tool2Component},
]), ]),
], ],
}) })
class TestModule { class TestModule {
} }
TestBed.configureTestingModule({imports: [TestModule]}); TestBed.configureTestingModule({imports: [TestModule]});
const router: Router = TestBed.get(Router); const router: Router = TestBed.get(Router);
router.routeReuseStrategy = new AttachDetachReuseStrategy(); router.routeReuseStrategy = new AttachDetachReuseStrategy();
const fixture = createRoot(router, RootCmpWithCondOutlet); const fixture = createRoot(router, RootCmpWithCondOutlet);
// Activate 'tool-1' // Activate 'tool-1'
router.navigate([{outlets: {toolpanel: 'a'}}]); router.navigate([{outlets: {toolpanel: 'a'}}]);
advance(fixture); advance(fixture);
expect(fixture).toContainComponent(Tool1Component, '(a)'); expect(fixture).toContainComponent(Tool1Component, '(a)');
// Deactivate 'tool-1' // Deactivate 'tool-1'
router.navigate([{outlets: {toolpanel: null}}]); router.navigate([{outlets: {toolpanel: null}}]);
advance(fixture); advance(fixture);
expect(fixture).not.toContainComponent(Tool1Component, '(b)'); expect(fixture).not.toContainComponent(Tool1Component, '(b)');
// Activate 'tool-1' // Activate 'tool-1'
router.navigate([{outlets: {toolpanel: 'a'}}]); router.navigate([{outlets: {toolpanel: 'a'}}]);
advance(fixture); advance(fixture);
expect(fixture).toContainComponent(Tool1Component, '(c)'); expect(fixture).toContainComponent(Tool1Component, '(c)');
// Deactivate 'tool-1' // Deactivate 'tool-1'
router.navigate([{outlets: {toolpanel: null}}]); router.navigate([{outlets: {toolpanel: null}}]);
advance(fixture); advance(fixture);
expect(fixture).not.toContainComponent(Tool1Component, '(d)'); expect(fixture).not.toContainComponent(Tool1Component, '(d)');
// Activate 'tool-2' // Activate 'tool-2'
router.navigate([{outlets: {toolpanel: 'b'}}]); router.navigate([{outlets: {toolpanel: 'b'}}]);
advance(fixture); advance(fixture);
expect(fixture).toContainComponent(Tool2Component, '(e)'); expect(fixture).toContainComponent(Tool2Component, '(e)');
})); }));
}); });
}); });

View File

@ -62,64 +62,64 @@ describe('RouterPreloader', () => {
fixmeIvy( fixmeIvy(
'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules') && 'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules')
it('should work', .it('should work',
fakeAsync(inject( fakeAsync(inject(
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef], [NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef],
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router, (loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
testModule: NgModuleRef<any>) => { testModule: NgModuleRef<any>) => {
const events: Array<RouteConfigLoadStart|RouteConfigLoadEnd> = []; const events: Array<RouteConfigLoadStart|RouteConfigLoadEnd> = [];
@NgModule({ @NgModule({
declarations: [LazyLoadedCmp], declarations: [LazyLoadedCmp],
imports: [RouterModule.forChild( imports: [RouterModule.forChild(
[{path: 'LoadedModule2', component: LazyLoadedCmp}])] [{path: 'LoadedModule2', component: LazyLoadedCmp}])]
}) })
class LoadedModule2 { class LoadedModule2 {
} }
@NgModule({ @NgModule({
imports: [RouterModule.forChild( imports: [RouterModule.forChild(
[{path: 'LoadedModule1', loadChildren: 'expected2'}])] [{path: 'LoadedModule1', loadChildren: 'expected2'}])]
}) })
class LoadedModule1 { class LoadedModule1 {
} }
router.events.subscribe(e => { router.events.subscribe(e => {
if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) { if (e instanceof RouteConfigLoadEnd || e instanceof RouteConfigLoadStart) {
events.push(e); events.push(e);
} }
}); });
loader.stubbedModules = { loader.stubbedModules = {
expected: LoadedModule1, expected: LoadedModule1,
expected2: LoadedModule2, expected2: LoadedModule2,
}; };
preloader.preload().subscribe(() => {}); preloader.preload().subscribe(() => {});
tick(); tick();
const c = router.config; const c = router.config;
expect(c[0].loadChildren).toEqual('expected'); expect(c[0].loadChildren).toEqual('expected');
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !; const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
const module: any = loadedConfig.module; const module: any = loadedConfig.module;
expect(loadedConfig.routes[0].path).toEqual('LoadedModule1'); expect(loadedConfig.routes[0].path).toEqual('LoadedModule1');
expect(module._parent).toBe(testModule); expect(module._parent).toBe(testModule);
const loadedConfig2: LoadedRouterConfig = const loadedConfig2: LoadedRouterConfig =
(loadedConfig.routes[0] as any)._loadedConfig !; (loadedConfig.routes[0] as any)._loadedConfig !;
const module2: any = loadedConfig2.module; const module2: any = loadedConfig2.module;
expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2'); expect(loadedConfig2.routes[0].path).toEqual('LoadedModule2');
expect(module2._parent).toBe(module); expect(module2._parent).toBe(module);
expect(events.map(e => e.toString())).toEqual([ expect(events.map(e => e.toString())).toEqual([
'RouteConfigLoadStart(path: lazy)', 'RouteConfigLoadStart(path: lazy)',
'RouteConfigLoadEnd(path: lazy)', 'RouteConfigLoadEnd(path: lazy)',
'RouteConfigLoadStart(path: LoadedModule1)', 'RouteConfigLoadStart(path: LoadedModule1)',
'RouteConfigLoadEnd(path: LoadedModule1)', 'RouteConfigLoadEnd(path: LoadedModule1)',
]); ]);
}))); })));
}); });
describe('should support modules that have already been loaded', () => { describe('should support modules that have already been loaded', () => {
@ -131,59 +131,59 @@ describe('RouterPreloader', () => {
}); });
fixmeIvy( fixmeIvy(
'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules') && 'FW-765: NgModuleRef hierarchy is differently constructed when the router preloads modules')
it('should work', .it('should work',
fakeAsync(inject( fakeAsync(inject(
[NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler], [NgModuleFactoryLoader, RouterPreloader, Router, NgModuleRef, Compiler],
(loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router, (loader: SpyNgModuleFactoryLoader, preloader: RouterPreloader, router: Router,
testModule: NgModuleRef<any>, compiler: Compiler) => { testModule: NgModuleRef<any>, compiler: Compiler) => {
@NgModule() @NgModule()
class LoadedModule2 { class LoadedModule2 {
} }
const module2 = compiler.compileModuleSync(LoadedModule2).create(null); const module2 = compiler.compileModuleSync(LoadedModule2).create(null);
@NgModule({ @NgModule({
imports: [RouterModule.forChild([ imports: [RouterModule.forChild([
<Route>{ <Route>{
path: 'LoadedModule2', path: 'LoadedModule2',
loadChildren: 'no', loadChildren: 'no',
_loadedConfig: { _loadedConfig: {
routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}], routes: [{path: 'LoadedModule3', loadChildren: 'expected3'}],
module: module2, module: module2,
} }
}, },
])] ])]
}) })
class LoadedModule1 { class LoadedModule1 {
} }
@NgModule({imports: [RouterModule.forChild([])]}) @NgModule({imports: [RouterModule.forChild([])]})
class LoadedModule3 { class LoadedModule3 {
} }
loader.stubbedModules = { loader.stubbedModules = {
expected: LoadedModule1, expected: LoadedModule1,
expected3: LoadedModule3, expected3: LoadedModule3,
}; };
preloader.preload().subscribe(() => {}); preloader.preload().subscribe(() => {});
tick(); tick();
const c = router.config; const c = router.config;
const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !; const loadedConfig: LoadedRouterConfig = (c[0] as any)._loadedConfig !;
const module: any = loadedConfig.module; const module: any = loadedConfig.module;
expect(module._parent).toBe(testModule); expect(module._parent).toBe(testModule);
const loadedConfig2: LoadedRouterConfig = const loadedConfig2: LoadedRouterConfig =
(loadedConfig.routes[0] as any)._loadedConfig !; (loadedConfig.routes[0] as any)._loadedConfig !;
const loadedConfig3: LoadedRouterConfig = const loadedConfig3: LoadedRouterConfig =
(loadedConfig2.routes[0] as any)._loadedConfig !; (loadedConfig2.routes[0] as any)._loadedConfig !;
const module3: any = loadedConfig3.module; const module3: any = loadedConfig3.module;
expect(module3._parent).toBe(module2); expect(module3._parent).toBe(module2);
}))); })));
}); });
describe('should ignore errors', () => { describe('should ignore errors', () => {

File diff suppressed because it is too large Load Diff

View File

@ -77,59 +77,59 @@ withEachNg1Version(() => {
})); }));
fixmeIvy( fixmeIvy(
'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger') && 'FW-712: Rendering is being run on next "animation frame" rather than "Zone.microTaskEmpty" trigger')
it('should propagate changes to a downgraded component inside the ngZone', async(() => { .it('should propagate changes to a downgraded component inside the ngZone', async(() => {
const element = html('<my-app></my-app>'); const element = html('<my-app></my-app>');
let appComponent: AppComponent; let appComponent: AppComponent;
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'}) @Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
class AppComponent { class AppComponent {
value?: number; value?: number;
constructor() { appComponent = this; } constructor() { appComponent = this; }
} }
@Component({ @Component({
selector: 'my-child', selector: 'my-child',
template: '<div>{{ valueFromPromise }}</div>', template: '<div>{{ valueFromPromise }}</div>',
}) })
class ChildComponent { class ChildComponent {
valueFromPromise?: number; valueFromPromise?: number;
@Input() @Input()
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); } set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
constructor(private zone: NgZone) {} constructor(private zone: NgZone) {}
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['value'].isFirstChange()) return; if (changes['value'].isFirstChange()) return;
// HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer // HACK(ivy): Using setTimeout allows this test to pass but hides the ivy renderer
// timing BC. // timing BC.
// setTimeout(() => expect(element.textContent).toEqual('5'), 0); // setTimeout(() => expect(element.textContent).toEqual('5'), 0);
this.zone.onMicrotaskEmpty.subscribe( this.zone.onMicrotaskEmpty.subscribe(
() => { expect(element.textContent).toEqual('5'); }); () => { expect(element.textContent).toEqual('5'); });
// Create a micro-task to update the value to be rendered asynchronously. // Create a micro-task to update the value to be rendered asynchronously.
Promise.resolve().then( Promise.resolve().then(
() => this.valueFromPromise = changes['value'].currentValue); () => this.valueFromPromise = changes['value'].currentValue);
} }
} }
@NgModule({ @NgModule({
declarations: [AppComponent, ChildComponent], declarations: [AppComponent, ChildComponent],
entryComponents: [AppComponent], entryComponents: [AppComponent],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'myApp', downgradeComponent({component: AppComponent})); 'myApp', downgradeComponent({component: AppComponent}));
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
appComponent.value = 5; appComponent.value = 5;
}); });
})); }));
// This test demonstrates https://github.com/angular/angular/issues/6385 // This test demonstrates https://github.com/angular/angular/issues/6385
// which was invalidly fixed by https://github.com/angular/angular/pull/6386 // which was invalidly fixed by https://github.com/angular/angular/pull/6386

View File

@ -22,82 +22,82 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform()); afterEach(() => destroyPlatform());
fixmeIvy('FW-714: ng1 projected content is not being rendered') && fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should instantiate ng2 in ng1 template and project content', async(() => { .it('should instantiate ng2 in ng1 template and project content', async(() => {
// the ng2 component that will be used in ng1 (downgraded) // the ng2 component that will be used in ng1 (downgraded)
@Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`}) @Component({selector: 'ng2', template: `{{ prop }}(<ng-content></ng-content>)`})
class Ng2Component { class Ng2Component {
prop = 'NG2'; prop = 'NG2';
ngContent = 'ng2-content'; ngContent = 'ng2-content';
} }
// our upgrade module to host the component to downgrade // our upgrade module to host the component to downgrade
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component] entryComponents: [Ng2Component]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// the ng1 app module that will consume the downgraded component // the ng1 app module that will consume the downgraded component
const ng1Module = angular const ng1Module = angular
.module('ng1', []) .module('ng1', [])
// create an ng1 facade of the ng2 component // create an ng1 facade of the ng2 component
.directive('ng2', downgradeComponent({component: Ng2Component})) .directive('ng2', downgradeComponent({component: Ng2Component}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope['prop'] = 'NG1'; $rootScope['prop'] = 'NG1';
$rootScope['ngContent'] = 'ng1-content'; $rootScope['ngContent'] = 'ng1-content';
}); });
const element = const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>'); html('<div>{{ \'ng1[\' }}<ng2>~{{ ngContent }}~</ng2>{{ \']\' }}</div>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]'); expect(document.body.textContent).toEqual('ng1[NG2(~ng1-content~)]');
}); });
})); }));
fixmeIvy('FW-714: ng1 projected content is not being rendered') && fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should correctly project structural directives', async(() => { .it('should correctly project structural directives', async(() => {
@Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'}) @Component({selector: 'ng2', template: 'ng2-{{ itemId }}(<ng-content></ng-content>)'})
class Ng2Component { class Ng2Component {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() itemId !: string; @Input() itemId !: string;
} }
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component] entryComponents: [Ng2Component]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = const ng1Module =
angular.module('ng1', []) angular.module('ng1', [])
.directive('ng2', downgradeComponent({component: Ng2Component})) .directive('ng2', downgradeComponent({component: Ng2Component}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope['items'] = [ $rootScope['items'] = [
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
{id: 'c', subitems: [7, 8, 9]} {id: 'c', subitems: [7, 8, 9]}
]; ];
}); });
const element = html(` const element = html(`
<ng2 ng-repeat="item in items" [item-id]="item.id"> <ng2 ng-repeat="item in items" [item-id]="item.id">
<div ng-repeat="subitem in item.subitems">{{ subitem }}</div> <div ng-repeat="subitem in item.subitems">{{ subitem }}</div>
</ng2> </ng2>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )'); .toBe('ng2-a( 123 )ng2-b( 456 )ng2-c( 789 )');
}); });
})); }));
it('should instantiate ng1 in ng2 template and project content', async(() => { it('should instantiate ng1 in ng2 template and project content', async(() => {
@ -145,39 +145,39 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-714: ng1 projected content is not being rendered') && fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should support multi-slot projection', async(() => { .it('should support multi-slot projection', async(() => {
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: '2a(<ng-content select=".ng1a"></ng-content>)' + template: '2a(<ng-content select=".ng1a"></ng-content>)' +
'2b(<ng-content select=".ng1b"></ng-content>)' '2b(<ng-content select=".ng1b"></ng-content>)'
}) })
class Ng2Component { class Ng2Component {
constructor() {} constructor() {}
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component}));
// The ng-if on one of the projected children is here to make sure // The ng-if on one of the projected children is here to make sure
// the correct slot is targeted even with structural directives in play. // the correct slot is targeted even with structural directives in play.
const element = html( const element = html(
'<ng2><div ng-if="true" class="ng1a">1a</div><div' + '<ng2><div ng-if="true" class="ng1a">1a</div><div' +
' class="ng1b">1b</div></ng2>'); ' class="ng1b">1b</div></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('2a(1a)2b(1b)'); expect(document.body.textContent).toEqual('2a(1a)2b(1b)');
}); });
})); }));
}); });
}); });

View File

@ -22,106 +22,106 @@ withEachNg1Version(() => {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform()); afterEach(() => destroyPlatform());
fixmeIvy('FW-716: Error: [$rootScope:inprog] $digest already in progress') && fixmeIvy('FW-716: Error: [$rootScope:inprog] $digest already in progress')
it('should bind properties, events', async(() => { .it('should bind properties, events', async(() => {
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['name'] = 'world'; $rootScope['name'] = 'world';
$rootScope['dataA'] = 'A'; $rootScope['dataA'] = 'A';
$rootScope['dataB'] = 'B'; $rootScope['dataB'] = 'B';
$rootScope['modelA'] = 'initModelA'; $rootScope['modelA'] = 'initModelA';
$rootScope['modelB'] = 'initModelB'; $rootScope['modelB'] = 'initModelB';
$rootScope['eventA'] = '?'; $rootScope['eventA'] = '?';
$rootScope['eventB'] = '?'; $rootScope['eventB'] = '?';
}); });
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'], inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
outputs: [ outputs: [
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange', 'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
'twoWayBEmitter: twoWayBChange' 'twoWayBEmitter: twoWayBChange'
], ],
template: 'ignore: {{ignore}}; ' + template: 'ignore: {{ignore}}; ' +
'literal: {{literal}}; interpolate: {{interpolate}}; ' + 'literal: {{literal}}; interpolate: {{interpolate}}; ' +
'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' + 'oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; ' +
'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})' 'twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{ngOnChangesCount}})'
}) })
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
ngOnChangesCount = 0; ngOnChangesCount = 0;
ignore = '-'; ignore = '-';
literal = '?'; literal = '?';
interpolate = '?'; interpolate = '?';
oneWayA = '?'; oneWayA = '?';
oneWayB = '?'; oneWayB = '?';
twoWayA = '?'; twoWayA = '?';
twoWayB = '?'; twoWayB = '?';
eventA = new EventEmitter(); eventA = new EventEmitter();
eventB = new EventEmitter(); eventB = new EventEmitter();
twoWayAEmitter = new EventEmitter(); twoWayAEmitter = new EventEmitter();
twoWayBEmitter = new EventEmitter(); twoWayBEmitter = new EventEmitter();
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
const assert = (prop: string, value: any) => { const assert = (prop: string, value: any) => {
const propVal = (this as any)[prop]; const propVal = (this as any)[prop];
if (propVal != value) { if (propVal != value) {
throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`); throw new Error(`Expected: '${prop}' to be '${value}' but was '${propVal}'`);
} }
}; };
const assertChange = (prop: string, value: any) => { const assertChange = (prop: string, value: any) => {
assert(prop, value); assert(prop, value);
if (!changes[prop]) { if (!changes[prop]) {
throw new Error(`Changes record for '${prop}' not found.`); throw new Error(`Changes record for '${prop}' not found.`);
} }
const actualValue = changes[prop].currentValue; const actualValue = changes[prop].currentValue;
if (actualValue != value) { if (actualValue != value) {
throw new Error( throw new Error(
`Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`); `Expected changes record for'${prop}' to be '${value}' but was '${actualValue}'`);
} }
}; };
switch (this.ngOnChangesCount++) { switch (this.ngOnChangesCount++) {
case 0: case 0:
assert('ignore', '-'); assert('ignore', '-');
assertChange('literal', 'Text'); assertChange('literal', 'Text');
assertChange('interpolate', 'Hello world'); assertChange('interpolate', 'Hello world');
assertChange('oneWayA', 'A'); assertChange('oneWayA', 'A');
assertChange('oneWayB', 'B'); assertChange('oneWayB', 'B');
assertChange('twoWayA', 'initModelA'); assertChange('twoWayA', 'initModelA');
assertChange('twoWayB', 'initModelB'); assertChange('twoWayB', 'initModelB');
this.twoWayAEmitter.emit('newA'); this.twoWayAEmitter.emit('newA');
this.twoWayBEmitter.emit('newB'); this.twoWayBEmitter.emit('newB');
this.eventA.emit('aFired'); this.eventA.emit('aFired');
this.eventB.emit('bFired'); this.eventB.emit('bFired');
break; break;
case 1: case 1:
assertChange('twoWayA', 'newA'); assertChange('twoWayA', 'newA');
assertChange('twoWayB', 'newB'); assertChange('twoWayB', 'newB');
break; break;
case 2: case 2:
assertChange('interpolate', 'Hello everyone'); assertChange('interpolate', 'Hello everyone');
break; break;
default: default:
throw new Error('Called too many times! ' + JSON.stringify(changes)); throw new Error('Called too many times! ' + JSON.stringify(changes));
} }
} }
} }
ng1Module.directive('ng2', downgradeComponent({ ng1Module.directive('ng2', downgradeComponent({
component: Ng2Component, component: Ng2Component,
})); }));
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const element = html(` const element = html(`
<div> <div>
<ng2 literal="Text" interpolate="Hello {{name}}" <ng2 literal="Text" interpolate="Hello {{name}}"
bind-one-way-a="dataA" [one-way-b]="dataB" bind-one-way-a="dataA" [one-way-b]="dataB"
@ -130,23 +130,23 @@ withEachNg1Version(() => {
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}}; | modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
</div>`); </div>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'ignore: -; ' + 'ignore: -; ' +
'literal: Text; interpolate: Hello world; ' + 'literal: Text; interpolate: Hello world; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (2) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
$apply(upgrade, 'name = "everyone"'); $apply(upgrade, 'name = "everyone"');
expect(multiTrim(document.body.textContent)) expect(multiTrim(document.body.textContent))
.toEqual( .toEqual(
'ignore: -; ' + 'ignore: -; ' +
'literal: Text; interpolate: Hello everyone; ' + 'literal: Text; interpolate: Hello everyone; ' +
'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' + 'oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | ' +
'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;'); 'modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;');
}); });
})); }));
it('should bind properties to onpush components', async(() => { it('should bind properties to onpush components', async(() => {
const ng1Module = angular.module('ng1', []).run( const ng1Module = angular.module('ng1', []).run(
@ -189,58 +189,58 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
it('should support two-way binding and event listener', async(() => { .it('should support two-way binding and event listener', async(() => {
const listenerSpy = jasmine.createSpy('$rootScope.listener'); const listenerSpy = jasmine.createSpy('$rootScope.listener');
const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => {
$rootScope['value'] = 'world'; $rootScope['value'] = 'world';
$rootScope['listener'] = listenerSpy; $rootScope['listener'] = listenerSpy;
}); });
@Component({selector: 'ng2', template: `model: {{model}};`}) @Component({selector: 'ng2', template: `model: {{model}};`})
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
ngOnChangesCount = 0; ngOnChangesCount = 0;
@Input() model = '?'; @Input() model = '?';
@Output() modelChange = new EventEmitter(); @Output() modelChange = new EventEmitter();
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
switch (this.ngOnChangesCount++) { switch (this.ngOnChangesCount++) {
case 0: case 0:
expect(changes.model.currentValue).toBe('world'); expect(changes.model.currentValue).toBe('world');
this.modelChange.emit('newC'); this.modelChange.emit('newC');
break; break;
case 1: case 1:
expect(changes.model.currentValue).toBe('newC'); expect(changes.model.currentValue).toBe('newC');
break; break;
default: default:
throw new Error('Called too many times! ' + JSON.stringify(changes)); throw new Error('Called too many times! ' + JSON.stringify(changes));
} }
} }
} }
ng1Module.directive('ng2', downgradeComponent({component: Ng2Component})); ng1Module.directive('ng2', downgradeComponent({component: Ng2Component}));
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const element = html(` const element = html(`
<div> <div>
<ng2 [(model)]="value" (model-change)="listener($event)"></ng2> <ng2 [(model)]="value" (model-change)="listener($event)"></ng2>
| value: {{value}} | value: {{value}}
</div> </div>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC');
expect(listenerSpy).toHaveBeenCalledWith('newC'); expect(listenerSpy).toHaveBeenCalledWith('newC');
}); });
})); }));
it('should run change-detection on every digest (by default)', async(() => { it('should run change-detection on every digest (by default)', async(() => {
let ng2Component: Ng2Component; let ng2Component: Ng2Component;
@ -404,66 +404,66 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
it('should initialize inputs in time for `ngOnChanges`', async(() => { .it('should initialize inputs in time for `ngOnChanges`', async(() => {
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
ngOnChangesCount: {{ ngOnChangesCount }} | ngOnChangesCount: {{ ngOnChangesCount }} |
firstChangesCount: {{ firstChangesCount }} | firstChangesCount: {{ firstChangesCount }} |
initialValue: {{ initialValue }}` initialValue: {{ initialValue }}`
}) })
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
ngOnChangesCount = 0; ngOnChangesCount = 0;
firstChangesCount = 0; firstChangesCount = 0;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
initialValue !: string; initialValue !: string;
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() foo !: string; @Input() foo !: string;
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
this.ngOnChangesCount++; this.ngOnChangesCount++;
if (this.ngOnChangesCount === 1) { if (this.ngOnChangesCount === 1) {
this.initialValue = this.foo; this.initialValue = this.foo;
} }
if (changes['foo'] && changes['foo'].isFirstChange()) { if (changes['foo'] && changes['foo'].isFirstChange()) {
this.firstChangesCount++; this.firstChangesCount++;
} }
} }
} }
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component] entryComponents: [Ng2Component]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component}));
const element = html(` const element = html(`
<ng2 [foo]="'foo'"></ng2> <ng2 [foo]="'foo'"></ng2>
<ng2 foo="bar"></ng2> <ng2 foo="bar"></ng2>
<ng2 [foo]="'baz'" ng-if="true"></ng2> <ng2 [foo]="'baz'" ng-if="true"></ng2>
<ng2 foo="qux" ng-if="true"></ng2> <ng2 foo="qux" ng-if="true"></ng2>
`); `);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const nodes = element.querySelectorAll('ng2'); const nodes = element.querySelectorAll('ng2');
const expectedTextWith = (value: string) => const expectedTextWith = (value: string) =>
`ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`; `ngOnChangesCount: 1 | firstChangesCount: 1 | initialValue: ${value}`;
expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo')); expect(multiTrim(nodes[0].textContent)).toBe(expectedTextWith('foo'));
expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar')); expect(multiTrim(nodes[1].textContent)).toBe(expectedTextWith('bar'));
expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz')); expect(multiTrim(nodes[2].textContent)).toBe(expectedTextWith('baz'));
expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux')); expect(multiTrim(nodes[3].textContent)).toBe(expectedTextWith('qux'));
}); });
})); }));
it('should bind to ng-model', async(() => { it('should bind to ng-model', async(() => {
const ng1Module = angular.module('ng1', []).run( const ng1Module = angular.module('ng1', []).run(
@ -709,88 +709,88 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-714: ng1 projected content is not being rendered') && fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should respect hierarchical dependency injection for ng2', async(() => { .it('should respect hierarchical dependency injection for ng2', async(() => {
@Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'}) @Component({selector: 'parent', template: 'parent(<ng-content></ng-content>)'})
class ParentComponent { class ParentComponent {
} }
@Component({selector: 'child', template: 'child'}) @Component({selector: 'child', template: 'child'})
class ChildComponent { class ChildComponent {
constructor(parent: ParentComponent) {} constructor(parent: ParentComponent) {}
} }
@NgModule({ @NgModule({
declarations: [ParentComponent, ChildComponent], declarations: [ParentComponent, ChildComponent],
entryComponents: [ParentComponent, ChildComponent], entryComponents: [ParentComponent, ChildComponent],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = const ng1Module =
angular.module('ng1', []) angular.module('ng1', [])
.directive('parent', downgradeComponent({component: ParentComponent})) .directive('parent', downgradeComponent({component: ParentComponent}))
.directive('child', downgradeComponent({component: ChildComponent})); .directive('child', downgradeComponent({component: ChildComponent}));
const element = html('<parent><child></child></parent>'); const element = html('<parent><child></child></parent>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(document.body.textContent)).toBe('parent(child)'); expect(multiTrim(document.body.textContent)).toBe('parent(child)');
}); });
})); }));
fixmeIvy( fixmeIvy(
'FW-717: Injector on lazy loaded components are not the same as their NgModule\'s injector') && 'FW-717: Injector on lazy loaded components are not the same as their NgModule\'s injector')
it('should work with ng2 lazy loaded components', async(() => { .it('should work with ng2 lazy loaded components', async(() => {
let componentInjector: Injector; let componentInjector: Injector;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component { class Ng2Component {
constructor(injector: Injector) { componentInjector = injector; } constructor(injector: Injector) { componentInjector = injector; }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
@Component({template: ''}) @Component({template: ''})
class LazyLoadedComponent { class LazyLoadedComponent {
constructor(public module: NgModuleRef<any>) {} constructor(public module: NgModuleRef<any>) {}
} }
@NgModule({ @NgModule({
declarations: [LazyLoadedComponent], declarations: [LazyLoadedComponent],
entryComponents: [LazyLoadedComponent], entryComponents: [LazyLoadedComponent],
}) })
class LazyLoadedModule { class LazyLoadedModule {
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component}));
const element = html('<ng2></ng2>'); const element = html('<ng2></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
const modInjector = upgrade.injector; const modInjector = upgrade.injector;
// Emulate the router lazy loading a module and creating a component // Emulate the router lazy loading a module and creating a component
const compiler = modInjector.get(Compiler); const compiler = modInjector.get(Compiler);
const modFactory = compiler.compileModuleSync(LazyLoadedModule); const modFactory = compiler.compileModuleSync(LazyLoadedModule);
const childMod = modFactory.create(modInjector); const childMod = modFactory.create(modInjector);
const cmpFactory = const cmpFactory = childMod.componentFactoryResolver.resolveComponentFactory(
childMod.componentFactoryResolver.resolveComponentFactory(LazyLoadedComponent) !; LazyLoadedComponent) !;
const lazyCmp = cmpFactory.create(componentInjector); const lazyCmp = cmpFactory.create(componentInjector);
expect(lazyCmp.instance.module.injector === childMod.injector).toBe(true); expect(lazyCmp.instance.module.injector === childMod.injector).toBe(true);
}); });
})); }));
it('should throw if `downgradedModule` is specified', async(() => { it('should throw if `downgradedModule` is specified', async(() => {
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})

View File

@ -132,64 +132,64 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-718: upgraded service not being initialized correctly on the injector') && fixmeIvy('FW-718: upgraded service not being initialized correctly on the injector')
it('should support using an upgraded service', async(() => { .it('should support using an upgraded service', async(() => {
class Ng2Service { class Ng2Service {
constructor(@Inject('ng1Value') private ng1Value: string) {} constructor(@Inject('ng1Value') private ng1Value: string) {}
getValue = () => `${this.ng1Value}-bar`; getValue = () => `${this.ng1Value}-bar`;
} }
@Component({selector: 'ng2', template: '{{ value }}'}) @Component({selector: 'ng2', template: '{{ value }}'})
class Ng2Component { class Ng2Component {
value: string; value: string;
constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); } constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
providers: [ providers: [
Ng2Service, Ng2Service,
{ {
provide: 'ng1Value', provide: 'ng1Value',
useFactory: (i: angular.IInjectorService) => i.get('ng1Value'), useFactory: (i: angular.IInjectorService) => i.get('ng1Value'),
deps: ['$injector'], deps: ['$injector'],
}, },
], ],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.value('ng1Value', 'foo'); .value('ng1Value', 'foo');
const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>'); const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
expect(element.textContent).toBe(''); expect(element.textContent).toBe('');
expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
$rootScope.$apply('loadNg2 = true'); $rootScope.$apply('loadNg2 = true');
expect(element.textContent).toBe(''); expect(element.textContent).toBe('');
expect(() => $injector.get(INJECTOR_KEY)).toThrowError(); expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
// Wait for the module to be bootstrapped. // Wait for the module to be bootstrapped.
setTimeout(() => { setTimeout(() => {
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow(); expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
// Wait for `$evalAsync()` to propagate inputs. // Wait for `$evalAsync()` to propagate inputs.
setTimeout(() => expect(element.textContent).toBe('foo-bar')); setTimeout(() => expect(element.textContent).toBe('foo-bar'));
}); });
})); }));
it('should create components inside the Angular zone', async(() => { it('should create components inside the Angular zone', async(() => {
@Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'}) @Component({selector: 'ng2', template: 'In the zone: {{ inTheZone }}'})
@ -261,66 +261,66 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
it('should propagate input changes inside the Angular zone', async(() => { .it('should propagate input changes inside the Angular zone', async(() => {
let ng2Component: Ng2Component; let ng2Component: Ng2Component;
@Component({selector: 'ng2', template: ''}) @Component({selector: 'ng2', template: ''})
class Ng2Component implements OnChanges { class Ng2Component implements OnChanges {
@Input() attrInput = 'foo'; @Input() attrInput = 'foo';
@Input() propInput = 'foo'; @Input() propInput = 'foo';
constructor() { ng2Component = this; } constructor() { ng2Component = this; }
ngOnChanges() {} ngOnChanges() {}
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope.attrVal = 'bar'; $rootScope.attrVal = 'bar';
$rootScope.propVal = 'bar'; $rootScope.propVal = 'bar';
}); });
const element = const element =
html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>'); html('<ng2 attr-input="{{ attrVal }}" [prop-input]="propVal"></ng2>');
const $injector = angular.bootstrap(element, [ng1Module.name]); const $injector = angular.bootstrap(element, [ng1Module.name]);
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService; const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
setTimeout(() => { // Wait for the module to be bootstrapped. setTimeout(() => { // Wait for the module to be bootstrapped.
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true); const expectToBeInNgZone = () => expect(NgZone.isInAngularZone()).toBe(true);
const changesSpy = const changesSpy =
spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone); spyOn(ng2Component, 'ngOnChanges').and.callFake(expectToBeInNgZone);
expect(ng2Component.attrInput).toBe('bar'); expect(ng2Component.attrInput).toBe('bar');
expect(ng2Component.propInput).toBe('bar'); expect(ng2Component.propInput).toBe('bar');
$rootScope.$apply('attrVal = "baz"'); $rootScope.$apply('attrVal = "baz"');
expect(ng2Component.attrInput).toBe('baz'); expect(ng2Component.attrInput).toBe('baz');
expect(ng2Component.propInput).toBe('bar'); expect(ng2Component.propInput).toBe('bar');
expect(changesSpy).toHaveBeenCalledTimes(1); expect(changesSpy).toHaveBeenCalledTimes(1);
$rootScope.$apply('propVal = "qux"'); $rootScope.$apply('propVal = "qux"');
expect(ng2Component.attrInput).toBe('baz'); expect(ng2Component.attrInput).toBe('baz');
expect(ng2Component.propInput).toBe('qux'); expect(ng2Component.propInput).toBe('qux');
expect(changesSpy).toHaveBeenCalledTimes(2); expect(changesSpy).toHaveBeenCalledTimes(2);
}); });
}); });
})); }));
it('should wire up the component for change detection', async(() => { it('should wire up the component for change detection', async(() => {
@Component( @Component(
@ -364,166 +364,168 @@ withEachNg1Version(() => {
}); });
})); }));
fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly') && fixmeIvy('FW-715: ngOnChanges being called a second time unexpectedly')
fixmeIvy('FW-714: ng1 projected content is not being rendered') && .fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should run the lifecycle hooks in the correct order', async(() => { .it('should run the lifecycle hooks in the correct order', async(() => {
const logs: string[] = []; const logs: string[] = [];
let rootScope: angular.IRootScopeService; let rootScope: angular.IRootScopeService;
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
{{ value }} {{ value }}
<button (click)="value = 'qux'"></button> <button (click)="value = 'qux'"></button>
<ng-content></ng-content> <ng-content></ng-content>
` `
}) })
class Ng2Component implements AfterContentChecked, class Ng2Component implements AfterContentChecked,
AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges, OnDestroy, AfterContentInit, AfterViewChecked, AfterViewInit, DoCheck, OnChanges,
OnInit { OnDestroy, OnInit {
@Input() value = 'foo'; @Input() value = 'foo';
ngAfterContentChecked() { this.log('AfterContentChecked'); } ngAfterContentChecked() { this.log('AfterContentChecked'); }
ngAfterContentInit() { this.log('AfterContentInit'); } ngAfterContentInit() { this.log('AfterContentInit'); }
ngAfterViewChecked() { this.log('AfterViewChecked'); } ngAfterViewChecked() { this.log('AfterViewChecked'); }
ngAfterViewInit() { this.log('AfterViewInit'); } ngAfterViewInit() { this.log('AfterViewInit'); }
ngDoCheck() { this.log('DoCheck'); } ngDoCheck() { this.log('DoCheck'); }
ngOnChanges() { this.log('OnChanges'); } ngOnChanges() { this.log('OnChanges'); }
ngOnDestroy() { this.log('OnDestroy'); } ngOnDestroy() { this.log('OnDestroy'); }
ngOnInit() { this.log('OnInit'); } ngOnInit() { this.log('OnInit'); }
private log(hook: string) { logs.push(`${hook}(${this.value})`); } private log(hook: string) { logs.push(`${hook}(${this.value})`); }
} }
@NgModule({ @NgModule({
declarations: [Ng2Component], declarations: [Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule], imports: [BrowserModule],
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const bootstrapFn = (extraProviders: StaticProvider[]) => const bootstrapFn = (extraProviders: StaticProvider[]) =>
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module); platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn); const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
const ng1Module = const ng1Module =
angular.module('ng1', [lazyModuleName]) angular.module('ng1', [lazyModuleName])
.directive( .directive(
'ng2', downgradeComponent({component: Ng2Component, propagateDigest})) 'ng2', downgradeComponent({component: Ng2Component, propagateDigest}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
rootScope = $rootScope; rootScope = $rootScope;
rootScope.value = 'bar'; rootScope.value = 'bar';
}); });
const element = const element =
html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>'); html('<div><ng2 value="{{ value }}" ng-if="!hideNg2">Content</ng2></div>');
angular.bootstrap(element, [ng1Module.name]); angular.bootstrap(element, [ng1Module.name]);
setTimeout(() => { // Wait for the module to be bootstrapped. setTimeout(() => { // Wait for the module to be bootstrapped.
setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs. setTimeout(() => { // Wait for `$evalAsync()` to propagate inputs.
const button = element.querySelector('button') !; const button = element.querySelector('button') !;
// Once initialized. // Once initialized.
expect(multiTrim(element.textContent)).toBe('bar Content'); expect(multiTrim(element.textContent)).toBe('bar Content');
expect(logs).toEqual([ expect(logs).toEqual([
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. // `ngOnChanges()` call triggered directly through the `inputChanges`
'OnChanges(bar)', // $watcher.
// Initial CD triggered directly through the `detectChanges()` or 'OnChanges(bar)',
// `inputChanges` // Initial CD triggered directly through the `detectChanges()` or
// $watcher (for `propagateDigest` true/false respectively). // `inputChanges`
'OnInit(bar)', // $watcher (for `propagateDigest` true/false respectively).
'DoCheck(bar)', 'OnInit(bar)',
'AfterContentInit(bar)', 'DoCheck(bar)',
'AfterContentChecked(bar)', 'AfterContentInit(bar)',
'AfterViewInit(bar)', 'AfterContentChecked(bar)',
'AfterViewChecked(bar)', 'AfterViewInit(bar)',
...(propagateDigest ? 'AfterViewChecked(bar)',
[ ...(propagateDigest ?
// CD triggered directly through the `detectChanges()` $watcher (2nd [
// $digest). // CD triggered directly through the `detectChanges()` $watcher (2nd
'DoCheck(bar)', // $digest).
'AfterContentChecked(bar)', 'DoCheck(bar)',
'AfterViewChecked(bar)', 'AfterContentChecked(bar)',
] : 'AfterViewChecked(bar)',
[]), ] :
// CD triggered due to entering/leaving the NgZone (in `downgradeFn()`). []),
'DoCheck(bar)', // CD triggered due to entering/leaving the NgZone (in `downgradeFn()`).
'AfterContentChecked(bar)', 'DoCheck(bar)',
'AfterViewChecked(bar)', 'AfterContentChecked(bar)',
]); 'AfterViewChecked(bar)',
logs.length = 0; ]);
logs.length = 0;
// Change inputs and run `$digest`. // Change inputs and run `$digest`.
rootScope.$apply('value = "baz"'); rootScope.$apply('value = "baz"');
expect(multiTrim(element.textContent)).toBe('baz Content'); expect(multiTrim(element.textContent)).toBe('baz Content');
expect(logs).toEqual([ expect(logs).toEqual([
// `ngOnChanges()` call triggered directly through the `inputChanges` $watcher. // `ngOnChanges()` call triggered directly through the `inputChanges`
'OnChanges(baz)', // $watcher.
// `propagateDigest: true` (3 CD runs): 'OnChanges(baz)',
// - CD triggered due to entering/leaving the NgZone (in `inputChanges` // `propagateDigest: true` (3 CD runs):
// $watcher). // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
// - CD triggered directly through the `detectChanges()` $watcher. // $watcher).
// - CD triggered due to entering/leaving the NgZone (in `detectChanges` // - CD triggered directly through the `detectChanges()` $watcher.
// $watcher). // - CD triggered due to entering/leaving the NgZone (in `detectChanges`
// `propagateDigest: false` (2 CD runs): // $watcher).
// - CD triggered directly through the `inputChanges` $watcher. // `propagateDigest: false` (2 CD runs):
// - CD triggered due to entering/leaving the NgZone (in `inputChanges` // - CD triggered directly through the `inputChanges` $watcher.
// $watcher). // - CD triggered due to entering/leaving the NgZone (in `inputChanges`
'DoCheck(baz)', // $watcher).
'AfterContentChecked(baz)', 'DoCheck(baz)',
'AfterViewChecked(baz)', 'AfterContentChecked(baz)',
'DoCheck(baz)', 'AfterViewChecked(baz)',
'AfterContentChecked(baz)', 'DoCheck(baz)',
'AfterViewChecked(baz)', 'AfterContentChecked(baz)',
...(propagateDigest ? 'AfterViewChecked(baz)',
[ ...(propagateDigest ?
'DoCheck(baz)', [
'AfterContentChecked(baz)', 'DoCheck(baz)',
'AfterViewChecked(baz)', 'AfterContentChecked(baz)',
] : 'AfterViewChecked(baz)',
[]), ] :
]); []),
logs.length = 0; ]);
logs.length = 0;
// Run `$digest` (without changing inputs). // Run `$digest` (without changing inputs).
rootScope.$digest(); rootScope.$digest();
expect(multiTrim(element.textContent)).toBe('baz Content'); expect(multiTrim(element.textContent)).toBe('baz Content');
expect(logs).toEqual( expect(logs).toEqual(
propagateDigest ? propagateDigest ?
[ [
// CD triggered directly through the `detectChanges()` $watcher. // CD triggered directly through the `detectChanges()` $watcher.
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
// CD triggered due to entering/leaving the NgZone (in the above // CD triggered due to entering/leaving the NgZone (in the above
// $watcher). // $watcher).
'DoCheck(baz)', 'DoCheck(baz)',
'AfterContentChecked(baz)', 'AfterContentChecked(baz)',
'AfterViewChecked(baz)', 'AfterViewChecked(baz)',
] : ] :
[]); []);
logs.length = 0; logs.length = 0;
// Trigger change detection (without changing inputs). // Trigger change detection (without changing inputs).
button.click(); button.click();
expect(multiTrim(element.textContent)).toBe('qux Content'); expect(multiTrim(element.textContent)).toBe('qux Content');
expect(logs).toEqual([ expect(logs).toEqual([
'DoCheck(qux)', 'DoCheck(qux)',
'AfterContentChecked(qux)', 'AfterContentChecked(qux)',
'AfterViewChecked(qux)', 'AfterViewChecked(qux)',
]); ]);
logs.length = 0; logs.length = 0;
// Destroy the component. // Destroy the component.
rootScope.$apply('hideNg2 = true'); rootScope.$apply('hideNg2 = true');
expect(logs).toEqual([ expect(logs).toEqual([
'OnDestroy(qux)', 'OnDestroy(qux)',
]); ]);
logs.length = 0; logs.length = 0;
}); });
}); });
})); }));
it('should detach hostViews from the ApplicationRef once destroyed', async(() => { it('should detach hostViews from the ApplicationRef once destroyed', async(() => {
let ng2Component: Ng2Component; let ng2Component: Ng2Component;

View File

@ -24,70 +24,70 @@ withEachNg1Version(() => {
it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1)); it('should have AngularJS loaded', () => expect(angular.version.major).toBe(1));
fixmeIvy('FW-714: ng1 projected content is not being rendered') && fixmeIvy('FW-714: ng1 projected content is not being rendered')
it('should verify UpgradeAdapter example', async(() => { .it('should verify UpgradeAdapter example', async(() => {
// This is wrapping (upgrading) an AngularJS component to be used in an Angular // This is wrapping (upgrading) an AngularJS component to be used in an Angular
// component // component
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1Component extends UpgradeComponent { class Ng1Component extends UpgradeComponent {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() title !: string; @Input() title !: string;
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// This is an Angular component that will be downgraded // This is an Angular component that will be downgraded
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)' template: 'ng2[<ng1 [title]="nameProp">transclude</ng1>](<ng-content></ng-content>)'
}) })
class Ng2Component { class Ng2Component {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input('name') nameProp !: string; @Input('name') nameProp !: string;
} }
// This module represents the Angular pieces of the application // This module represents the Angular pieces of the application
@NgModule({ @NgModule({
declarations: [Ng1Component, Ng2Component], declarations: [Ng1Component, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from ngDoBootstrap() { /* this is a placeholder to stop the bootstrapper from
complaining */ complaining */
} }
} }
// This module represents the AngularJS pieces of the application // This module represents the AngularJS pieces of the application
const ng1Module = const ng1Module =
angular angular
.module('myExample', []) .module('myExample', [])
// This is an AngularJS component that will be upgraded // This is an AngularJS component that will be upgraded
.directive( .directive(
'ng1', 'ng1',
() => { () => {
return { return {
scope: {title: '='}, scope: {title: '='},
transclude: true, transclude: true,
template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
}; };
}) })
// This is wrapping (downgrading) an Angular component to be used in // This is wrapping (downgrading) an Angular component to be used in
// AngularJS // AngularJS
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive('ng2', downgradeComponent({component: Ng2Component}));
// This is the (AngularJS) application bootstrap element // This is the (AngularJS) application bootstrap element
// Notice that it is actually a downgraded Angular component // Notice that it is actually a downgraded Angular component
const element = html('<ng2 name="World">project</ng2>'); const element = html('<ng2 name="World">project</ng2>');
// Let's use a helper function to make this simpler // Let's use a helper function to make this simpler
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
expect(multiTrim(element.textContent)) expect(multiTrim(element.textContent))
.toBe('ng2[ng1[Hello World!](transclude)](project)'); .toBe('ng2[ng1[Hello World!](transclude)](project)');
}); });
})); }));
}); });
}); });

View File

@ -2135,76 +2135,76 @@ withEachNg1Version(() => {
describe('transclusion', () => { describe('transclusion', () => {
fixmeIvy( fixmeIvy(
`Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`) && `Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`)
it('should support single-slot transclusion', async(() => { .it('should support single-slot transclusion', async(() => {
let ng2ComponentAInstance: Ng2ComponentA; let ng2ComponentAInstance: Ng2ComponentA;
let ng2ComponentBInstance: Ng2ComponentB; let ng2ComponentBInstance: Ng2ComponentB;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: 'ng1(<div ng-transclude></div>)', template: 'ng1(<div ng-transclude></div>)',
transclude: true transclude: true
}; };
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1ComponentFacade extends UpgradeComponent { class Ng1ComponentFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2A', selector: 'ng2A',
template: 'ng2A(<ng1>{{ value }} | <ng2B *ngIf="showB"></ng2B></ng1>)' template: 'ng2A(<ng1>{{ value }} | <ng2B *ngIf="showB"></ng2B></ng1>)'
}) })
class Ng2ComponentA { class Ng2ComponentA {
value = 'foo'; value = 'foo';
showB = false; showB = false;
constructor() { ng2ComponentAInstance = this; } constructor() { ng2ComponentAInstance = this; }
} }
@Component({selector: 'ng2B', template: 'ng2B({{ value }})'}) @Component({selector: 'ng2B', template: 'ng2B({{ value }})'})
class Ng2ComponentB { class Ng2ComponentB {
value = 'bar'; value = 'bar';
constructor() { ng2ComponentBInstance = this; } constructor() { ng2ComponentBInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module =
angular.module('ng1Module', []) angular.module('ng1Module', [])
.component('ng1', ng1Component) .component('ng1', ng1Component)
.directive('ng2A', downgradeComponent({component: Ng2ComponentA})); .directive('ng2A', downgradeComponent({component: Ng2ComponentA}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB], declarations: [Ng1ComponentFacade, Ng2ComponentA, Ng2ComponentB],
entryComponents: [Ng2ComponentA] entryComponents: [Ng2ComponentA]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html(`<ng2-a></ng2-a>`); const element = html(`<ng2-a></ng2-a>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))'); expect(multiTrim(element.textContent)).toBe('ng2A(ng1(foo | ))');
ng2ComponentAInstance.value = 'baz'; ng2ComponentAInstance.value = 'baz';
ng2ComponentAInstance.showB = true; ng2ComponentAInstance.showB = true;
$digest(adapter); $digest(adapter);
expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))'); expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(bar)))');
ng2ComponentBInstance.value = 'qux'; ng2ComponentBInstance.value = 'qux';
$digest(adapter); $digest(adapter);
expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))'); expect(multiTrim(element.textContent)).toBe('ng2A(ng1(baz | ng2B(qux)))');
}); });
})); }));
it('should support single-slot transclusion with fallback content', async(() => { it('should support single-slot transclusion with fallback content', async(() => {
let ng1ControllerInstances: any[] = []; let ng1ControllerInstances: any[] = [];
@ -2532,29 +2532,29 @@ withEachNg1Version(() => {
})); }));
fixmeIvy( fixmeIvy(
`Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`) && `Error: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`)
it('should support structural directives in transcluded content', async(() => { .it('should support structural directives in transcluded content', async(() => {
let ng2ComponentInstance: Ng2Component; let ng2ComponentInstance: Ng2Component;
// Define `ng1Component` // Define `ng1Component`
const ng1Component: angular.IComponent = { const ng1Component: angular.IComponent = {
template: template:
'ng1(x(<div ng-transclude="slotX"></div>) | default(<div ng-transclude=""></div>))', 'ng1(x(<div ng-transclude="slotX"></div>) | default(<div ng-transclude=""></div>))',
transclude: {slotX: 'contentX'} transclude: {slotX: 'contentX'}
}; };
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1'}) @Directive({selector: 'ng1'})
class Ng1ComponentFacade extends UpgradeComponent { class Ng1ComponentFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1', elementRef, injector); super('ng1', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: ` template: `
ng2( ng2(
<ng1> <ng1>
<content-x><div *ngIf="show">{{ x }}1</div></content-x> <content-x><div *ngIf="show">{{ x }}1</div></content-x>
@ -2563,53 +2563,53 @@ withEachNg1Version(() => {
<div *ngIf="show">{{ y }}2</div> <div *ngIf="show">{{ y }}2</div>
</ng1> </ng1>
)` )`
}) })
class Ng2Component { class Ng2Component {
x = 'foo'; x = 'foo';
y = 'bar'; y = 'bar';
show = true; show = true;
constructor() { ng2ComponentInstance = this; } constructor() { ng2ComponentInstance = this; }
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module =
angular.module('ng1Module', []) angular.module('ng1Module', [])
.component('ng1', ng1Component) .component('ng1', ng1Component)
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive('ng2', downgradeComponent({component: Ng2Component}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
imports: [BrowserModule, UpgradeModule], imports: [BrowserModule, UpgradeModule],
declarations: [Ng1ComponentFacade, Ng2Component], declarations: [Ng1ComponentFacade, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html(`<ng2></ng2>`); const element = html(`<ng2></ng2>`);
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
expect(multiTrim(element.textContent, true)) expect(multiTrim(element.textContent, true))
.toBe('ng2(ng1(x(foo1)|default(bar2)))'); .toBe('ng2(ng1(x(foo1)|default(bar2)))');
ng2ComponentInstance.x = 'baz'; ng2ComponentInstance.x = 'baz';
ng2ComponentInstance.y = 'qux'; ng2ComponentInstance.y = 'qux';
ng2ComponentInstance.show = false; ng2ComponentInstance.show = false;
$digest(adapter); $digest(adapter);
expect(multiTrim(element.textContent, true)) expect(multiTrim(element.textContent, true))
.toBe('ng2(ng1(x(baz2)|default(qux1)))'); .toBe('ng2(ng1(x(baz2)|default(qux1)))');
ng2ComponentInstance.show = true; ng2ComponentInstance.show = true;
$digest(adapter); $digest(adapter);
expect(multiTrim(element.textContent, true)) expect(multiTrim(element.textContent, true))
.toBe('ng2(ng1(x(baz1)|default(qux2)))'); .toBe('ng2(ng1(x(baz1)|default(qux2)))');
}); });
})); }));
}); });
describe('lifecycle hooks', () => { describe('lifecycle hooks', () => {
@ -3336,103 +3336,105 @@ withEachNg1Version(() => {
})); }));
fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed') && fixmeIvy('FW-713: ngDestroy not being called when downgraded ng2 component is destroyed')
it('should call `$onDestroy()` on controller', async(() => { .it('should call `$onDestroy()` on controller', async(() => {
const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA'); const controllerOnDestroyA = jasmine.createSpy('controllerOnDestroyA');
const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB'); const controllerOnDestroyB = jasmine.createSpy('controllerOnDestroyB');
// Define `ng1Directive` // Define `ng1Directive`
const ng1DirectiveA: angular.IDirective = { const ng1DirectiveA: angular.IDirective = {
template: 'ng1A', template: 'ng1A',
scope: {}, scope: {},
bindToController: false, bindToController: false,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: class {$onDestroy() { controllerOnDestroyA(); }} controller: class {$onDestroy() { controllerOnDestroyA(); }}
}; };
const ng1DirectiveB: angular.IDirective = { const ng1DirectiveB: angular.IDirective = {
template: 'ng1B', template: 'ng1B',
scope: {}, scope: {},
bindToController: true, bindToController: true,
controllerAs: '$ctrl', controllerAs: '$ctrl',
controller: controller: class {
class {constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }} constructor() { (this as any)['$onDestroy'] = controllerOnDestroyB; }
}; }
};
// Define `Ng1ComponentFacade` // Define `Ng1ComponentFacade`
@Directive({selector: 'ng1A'}) @Directive({selector: 'ng1A'})
class Ng1ComponentAFacade extends UpgradeComponent { class Ng1ComponentAFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1A', elementRef, injector); super('ng1A', elementRef, injector);
} }
} }
@Directive({selector: 'ng1B'}) @Directive({selector: 'ng1B'})
class Ng1ComponentBFacade extends UpgradeComponent { class Ng1ComponentBFacade extends UpgradeComponent {
constructor(elementRef: ElementRef, injector: Injector) { constructor(elementRef: ElementRef, injector: Injector) {
super('ng1B', elementRef, injector); super('ng1B', elementRef, injector);
} }
} }
// Define `Ng2Component` // Define `Ng2Component`
@Component({ @Component({
selector: 'ng2', selector: 'ng2',
template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>' template: '<div *ngIf="show"><ng1A></ng1A> | <ng1B></ng1B></div>'
}) })
class Ng2Component { class Ng2Component {
// TODO(issue/24571): remove '!'. // TODO(issue/24571): remove '!'.
@Input() show !: boolean; @Input() show !: boolean;
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = const ng1Module =
angular.module('ng1Module', []) angular.module('ng1Module', [])
.directive('ng1A', () => ng1DirectiveA) .directive('ng1A', () => ng1DirectiveA)
.directive('ng1B', () => ng1DirectiveB) .directive('ng1B', () => ng1DirectiveB)
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive('ng2', downgradeComponent({component: Ng2Component}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component], declarations: [Ng1ComponentAFacade, Ng1ComponentBFacade, Ng2Component],
entryComponents: [Ng2Component], entryComponents: [Ng2Component],
imports: [BrowserModule, UpgradeModule] imports: [BrowserModule, UpgradeModule]
}) })
class Ng2Module { class Ng2Module {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
// Bootstrap // Bootstrap
const element = html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>'); const element =
html('<ng2 [show]="!destroyFromNg2" ng-if="!destroyFromNg1"></ng2>');
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(adapter => {
const $rootScope = const $rootScope =
adapter.$injector.get('$rootScope') as angular.IRootScopeService; adapter.$injector.get('$rootScope') as angular.IRootScopeService;
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg1 = true'); $rootScope.$apply('destroyFromNg1 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
controllerOnDestroyA.calls.reset(); controllerOnDestroyA.calls.reset();
controllerOnDestroyB.calls.reset(); controllerOnDestroyB.calls.reset();
$rootScope.$apply('destroyFromNg1 = false'); $rootScope.$apply('destroyFromNg1 = false');
expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B'); expect(multiTrim(document.body.textContent)).toBe('ng1A | ng1B');
expect(controllerOnDestroyA).not.toHaveBeenCalled(); expect(controllerOnDestroyA).not.toHaveBeenCalled();
expect(controllerOnDestroyB).not.toHaveBeenCalled(); expect(controllerOnDestroyB).not.toHaveBeenCalled();
$rootScope.$apply('destroyFromNg2 = true'); $rootScope.$apply('destroyFromNg2 = true');
expect(multiTrim(document.body.textContent)).toBe(''); expect(multiTrim(document.body.textContent)).toBe('');
expect(controllerOnDestroyA).toHaveBeenCalled(); expect(controllerOnDestroyA).toHaveBeenCalled();
expect(controllerOnDestroyB).toHaveBeenCalled(); expect(controllerOnDestroyB).toHaveBeenCalled();
}); });
})); }));
it('should not call `$onDestroy()` on scope', async(() => { it('should not call `$onDestroy()` on scope', async(() => {
const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy'); const scopeOnDestroy = jasmine.createSpy('scopeOnDestroy');