From 3f7e823b805622993189032aee79862cc271598b Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 13 May 2019 14:56:50 -0700 Subject: [PATCH] test(ivy): add exhaustive inheritance tests (#30442) - Adds inheritance tests for many combinations of directive, component and bare class inheritance - Adds tests that are failing in ivy, but should work, marks them with `xit` and a TODO. - Removes render3 unit tests that cover the same things. PR Close #30442 --- .../inherit_definition_feature_spec.ts | 4502 ++++++++++++++++- .../inherit_definition_feature_spec.ts | 639 --- 2 files changed, 4396 insertions(+), 745 deletions(-) delete mode 100644 packages/core/test/render3/inherit_definition_feature_spec.ts diff --git a/packages/core/test/acceptance/inherit_definition_feature_spec.ts b/packages/core/test/acceptance/inherit_definition_feature_spec.ts index cc053dea37..353588802a 100644 --- a/packages/core/test/acceptance/inherit_definition_feature_spec.ts +++ b/packages/core/test/acceptance/inherit_definition_feature_spec.ts @@ -6,155 +6,4445 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, Input, OnChanges, Type} from '@angular/core'; +import {Component, ContentChildren, Directive, EventEmitter, HostBinding, Input, OnChanges, Output, QueryList, ViewChildren} from '@angular/core'; import {TestBed} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {onlyInIvy} from '@angular/private/testing'; -describe('ngOnChanges', () => { - it('should be inherited when super is a directive', () => { - const log: string[] = []; +describe('inheritance', () => { + onlyInIvy('View Engine does not provide this check') + .it('should throw when trying to inherit a component from a directive', () => { + @Component({ + selector: 'my-comp', + template: '
', + }) + class MyComponent { + } - @Directive({selector: '[superDir]'}) - class SuperDirective implements OnChanges { - @Input() someInput = ''; + @Directive({ + selector: '[my-dir]', + }) + class MyDirective extends MyComponent { + } - ngOnChanges() { log.push('on changes!'); } + @Component({ + template: `
`, + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, MyDirective], + }); + + expect(() => { + TestBed.createComponent(App); + }).toThrowError('Directives cannot inherit Components'); + }); + + describe('ngOnChanges', () => { + @Component({selector: 'app-comp', template: ``}) + class AppComp { } - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperDirective { - } + it('should be inherited when super is a directive', () => { + const log: string[] = []; - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
'})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + @Directive({selector: '[superDir]'}) + class SuperDirective implements OnChanges { + @Input() someInput = ''; - expect(log).toEqual(['on changes!']); + ngOnChanges() { log.push('on changes!'); } + } + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperDirective { + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); + + it('should be inherited when super is a simple class', () => { + const log: string[] = []; + + class SuperClass { + ngOnChanges() { log.push('on changes!'); } + } + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperClass { + @Input() someInput = ''; + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); + + it('should be inherited when super is a directive and grand-super is a directive', () => { + const log: string[] = []; + + @Directive({selector: '[grandSuperDir]'}) + class GrandSuperDirective implements OnChanges { + @Input() someInput = ''; + + ngOnChanges() { log.push('on changes!'); } + } + + @Directive({selector: '[superDir]'}) + class SuperDirective extends GrandSuperDirective { + } + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperDirective { + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); + + it('should be inherited when super is a directive and grand-super is a simple class', () => { + const log: string[] = []; + + class GrandSuperClass { + ngOnChanges() { log.push('on changes!'); } + } + + @Directive({selector: '[superDir]'}) + class SuperDirective extends GrandSuperClass { + @Input() someInput = ''; + } + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperDirective { + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); + + it('should be inherited when super is a simple class and grand-super is a directive', () => { + const log: string[] = []; + + @Directive({selector: '[grandSuperDir]'}) + class GrandSuperDirective implements OnChanges { + @Input() someInput = ''; + + ngOnChanges() { log.push('on changes!'); } + } + + class SuperClass extends GrandSuperDirective {} + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperClass { + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); + + it('should be inherited when super is a simple class and grand-super is a simple class', () => { + const log: string[] = []; + + class GrandSuperClass { + ngOnChanges() { log.push('on changes!'); } + } + + class SuperClass extends GrandSuperClass {} + + @Directive({selector: '[subDir]'}) + class SubDirective extends SuperClass { + @Input() someInput = ''; + } + + TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); + TestBed.overrideComponent( + AppComp, {set: new Component({template: '
'})}); + const fixture = TestBed.createComponent(AppComp); + fixture.detectChanges(); + + expect(log).toEqual(['on changes!']); + }); }); - it('should be inherited when super is a simple class', () => { - const log: string[] = []; + describe('of bare super class by a directive', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; - class SuperClass { - ngOnChanges() { log.push('on changes!'); } - } + class SuperDirective { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperClass { - @Input() someInput = ''; - } + beforeEach(() => fired.length = 0); - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
'})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + it('ngOnInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { fired.push('sub init'); } + } - expect(log).toEqual(['on changes!']); + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + class SuperDirective { + @Input() + foo = ''; + + @Input() + bar = ''; + + @Input() + baz = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: `

`}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir = + fixture.debugElement.query(By.directive(SubDirective)).injector.get(SubDirective); + + expect(subDir.foo).toBe('a'); + expect(subDir.bar).toBe('b'); + expect(subDir.baz).toBe('c'); + expect(subDir.qux).toBe('d'); + }); + + + it('should inherit outputs', () => { + class SuperDirective { + @Output() + foo = new EventEmitter(); + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` +
+ ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + class SuperDirective { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(SubDirective)); + + expect(queryResult.nativeElement.tagName).toBe('P'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + class SuperDirective { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(SubDirective)); + + expect(queryResult.nativeElement.title).toBe('test!!!'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` +
    +
  • one
  • +
  • two
  • +
+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, ChildDir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); }); - it('should be inherited when super is a directive and grand-super is a directive', () => { - const log: string[] = []; + describe('of a directive by another directive', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; - @Directive({selector: '[grandSuperDir]'}) - class GrandSuperDirective implements OnChanges { - @Input() someInput = ''; + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } - ngOnChanges() { log.push('on changes!'); } - } + beforeEach(() => fired.length = 0); - @Directive({selector: '[superDir]'}) - class SuperDirective extends GrandSuperDirective { - } + it('ngOnInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { fired.push('sub init'); } + } - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperDirective { - } + @Component({ + template: `

`, + }) + class App { + showing = true; + } - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
'})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); - expect(log).toEqual(['on changes!']); + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Directive({selector: '[super-dir]'}) + class SuperDirective { + @Input() + foo = ''; + + @Input() + bar = ''; + + @Input() + baz = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: `

`}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir = + fixture.debugElement.query(By.directive(SubDirective)).injector.get(SubDirective); + + expect(subDir.foo).toBe('a'); + expect(subDir.bar).toBe('b'); + expect(subDir.baz).toBe('c'); + expect(subDir.qux).toBe('d'); + }); + + + it('should inherit outputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @Output() + foo = new EventEmitter(); + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` +
+ ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(SubDirective)); + + expect(queryResult.nativeElement.tagName).toBe('P'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(SubDirective)); + + expect(queryResult.nativeElement.title).toBe('test!!!'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` +
    +
  • one
  • +
  • two
  • +
+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, ChildDir, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); }); - it('should be inherited when super is a directive and grand-super is a simple class', () => { - const log: string[] = []; + describe('of a directive by a bare class then by another directive', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; - class GrandSuperClass { - ngOnChanges() { log.push('on changes!'); } - } + @Directive({ + selector: '[super-dir]', + }) + class SuperSuperDirective { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } - @Directive({selector: '[superDir]'}) - class SuperDirective extends GrandSuperClass { - @Input() someInput = ''; - } + class SuperDirective extends SuperSuperDirective {} - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperDirective { - } + beforeEach(() => fired.length = 0); - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
'})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + it('ngOnInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { fired.push('sub init'); } + } - expect(log).toEqual(['on changes!']); + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Directive({ + selector: '[subDir]', + }) + class SubDirective extends SuperDirective { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: `

`, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [SubDirective, App, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Directive({selector: '[super-dir]'}) + class SuperSuperDirective { + @Input() + foo = ''; + + @Input() + baz = ''; + } + + class SuperDirective extends SuperSuperDirective { + @Input() + bar = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({ + selector: 'my-app', + template: `

`, + }) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir = + fixture.debugElement.query(By.directive(SubDirective)).injector.get(SubDirective); + + expect(subDir.foo).toBe('a'); + expect(subDir.bar).toBe('b'); + expect(subDir.baz).toBe('c'); + expect(subDir.qux).toBe('d'); + }); + + it('should inherit outputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperSuperDirective { + @Output() + foo = new EventEmitter(); + } + + class SuperDirective extends SuperSuperDirective { + @Output() + bar = new EventEmitter(); + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngOnInit() { + this.foo.emit('test1'); + this.bar.emit('test2'); + } + } + + @Component({ + template: ` +
+ ` + }) + class App { + foo = ''; + + bar = ''; + + handleFoo(event: string) { this.foo = event; } + + handleBar(event: string) { this.bar = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test1'); + expect(app.bar).toBe('test2'); + }); + + it('should compose host bindings for styles', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperSuperDirective { + @HostBinding('style.color') + color = 'red'; + } + + class SuperDirective extends SuperSuperDirective { + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(SubDirective)); + + expect(queryResult.nativeElement.tagName).toBe('P'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperSuperDirective { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + class SuperDirective extends SuperSuperDirective { + @HostBinding('accessKey') + get boundAltKey() { return this.superAccessKey + '???'; } + + @Input() + superAccessKey = ''; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + } + @Component({ + template: ` +

test

+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, SuperDirective, SuperSuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const p: HTMLParagraphElement = + fixture.debugElement.query(By.directive(SubDirective)).nativeElement; + + expect(p.title).toBe('test1!!!'); + expect(p.accessKey).toBe('test2???'); + }); + + it('should inherit ContentChildren queries', () => { + let foundChildDir1s: QueryList; + let foundChildDir2s: QueryList; + + @Directive({selector: '[child-dir-one]'}) + class ChildDir1 { + } + + @Directive({selector: '[child-dir-two]'}) + class ChildDir2 { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperSuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir1) + childDir1s !: QueryList; + } + + class SuperDirective extends SuperSuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir1) + childDir2s !: QueryList; + } + + @Directive({ + selector: '[sub-dir]', + }) + class SubDirective extends SuperDirective { + ngAfterViewInit() { + foundChildDir1s = this.childDir1s; + foundChildDir2s = this.childDir2s; + } + } + + @Component({ + template: ` +
    +
  • one
  • +
  • two
  • +
  • three
  • +
+ ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SubDirective, ChildDir1, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundChildDir1s !.length).toBe(2); + expect(foundChildDir2s !.length).toBe(2); + }); }); - it('should be inherited when super is a simple class and grand-super is a directive', () => { - const log: string[] = []; + describe('of bare super class by a component', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; - @Directive({selector: '[grandSuperDir]'}) - class GrandSuperDirective implements OnChanges { - @Input() someInput = ''; + class SuperComponent { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } - ngOnChanges() { log.push('on changes!'); } - } + beforeEach(() => fired.length = 0); - class SuperClass extends GrandSuperDirective {} + it('ngOnInit', () => { + @Component({selector: 'my-comp', template: `

test

`}) + class MyComponent extends SuperComponent { + ngOnInit() { fired.push('sub init'); } + } - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperClass { - } + @Component({ + template: ``, + }) + class App { + showing = true; + } - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
'})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); - expect(log).toEqual(['on changes!']); + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Directive({ + selector: 'my-comp', + }) + class MyComponent extends SuperComponent { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + class SuperComponent { + @Input() + foo = ''; + + @Input() + bar = ''; + + @Input() + baz = ''; + } + + @Component({selector: 'my-comp', template: `

test

`}) + class MyComponent extends SuperComponent { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: ``}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir: MyComponent = + fixture.debugElement.query(By.directive(MyComponent)).componentInstance; + + expect(subDir.foo).toEqual('a'); + expect(subDir.bar).toEqual('b'); + expect(subDir.baz).toEqual('c'); + expect(subDir.qux).toEqual('d'); + }); + + + it('should inherit outputs', () => { + class SuperComponent { + @Output() + foo = new EventEmitter(); + } + + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` + + ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + class SuperComponent { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + } + + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + class SuperComponent { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + @Component({ + selector: 'my-comp', + template: `

test

`, + }) + class MyComponent extends SuperComponent { + } + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.title).toBe('test!!!'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + class SuperComponent { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({selector: 'my-comp', template: `
`}) + class MyComponent extends SuperComponent { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + +
  • one
  • +
  • two
  • +
    + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); }); - it('should be inherited when super is a simple class and grand-super is a simple class', () => { - const log: string[] = []; + describe('of a directive inherited by a component', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; - class GrandSuperClass { - ngOnChanges() { log.push('on changes!'); } - } + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } - class SuperClass extends GrandSuperClass {} + beforeEach(() => fired.length = 0); - @Directive({selector: '[subDir]'}) - class SubDirective extends SuperClass { - @Input() someInput = ''; - } + it('ngOnInit', () => { + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends SuperDirective { + ngOnInit() { fired.push('sub init'); } + } - TestBed.configureTestingModule({declarations: [AppComp, SubDirective]}); - TestBed.overrideComponent( - AppComp, {set: new Component({template: '
    '})}); - const fixture = TestBed.createComponent(AppComp); - fixture.detectChanges(); + @Component({ + template: ``, + }) + class App { + showing = true; + } - expect(log).toEqual(['on changes!']); + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @Input() + foo = ''; + + @Input() + bar = ''; + + @Input() + baz = ''; + } + + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends SuperDirective { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: ``}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir: MyComponent = + fixture.debugElement.query(By.directive(MyComponent)).componentInstance; + + expect(subDir.foo).toEqual('a'); + expect(subDir.bar).toEqual('b'); + expect(subDir.baz).toEqual('c'); + expect(subDir.qux).toEqual('d'); + }); + + it('should inherit outputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @Output() + foo = new EventEmitter(); + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` + + ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + } + + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperDirective { + } + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.title).toBe('test!!!'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({selector: 'my-comp', template: `
    `}) + class MyComponent extends SuperDirective { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + +
  • one
  • +
  • two
  • +
    + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperDirective, ChildDir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); + + it('should inherit ViewChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ViewChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({ + selector: 'my-comp', + template: ` +
      +
    • {{item}}
    • +
    + `, + }) + class MyComponent extends SuperDirective { + items = [1, 2, 3, 4, 5]; + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(5); + }); + }); + + describe('of a directive inherited by a bare class and then by a component', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } + + class BareClass extends SuperDirective {} + + beforeEach(() => fired.length = 0); + + it('ngOnInit', () => { + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends BareClass { + ngOnInit() { fired.push('sub init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Directive({ + selector: 'my-comp', + }) + class MyComponent extends BareClass { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @Input() + foo = ''; + + @Input() + baz = ''; + } + + class BareClass extends SuperDirective { + @Input() + bar = ''; + } + + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends BareClass { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: ``}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, BareClass, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir: MyComponent = + fixture.debugElement.query(By.directive(MyComponent)).componentInstance; + + expect(subDir.foo).toEqual('a'); + expect(subDir.bar).toEqual('b'); + expect(subDir.baz).toEqual('c'); + expect(subDir.qux).toEqual('d'); + }); + + + it('should inherit outputs', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @Output() + foo = new EventEmitter(); + } + + class BareClass extends SuperDirective {} + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` + + ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + class BareClass extends SuperDirective {} + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + } + + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + class BareClass extends SuperDirective { + @HostBinding('accessKey') + get boundAccessKey() { return this.superAccessKey + '???'; } + + @Input() + superAccessKey = ''; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends BareClass { + } + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, SuperDirective, BareClass, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.title).toBe('test1!!!'); + expect(queryResult.nativeElement.accessKey).toBe('test2???'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + class BareClass extends SuperDirective {} + + @Component({ + selector: 'my-comp', + template: `
    `, + }) + class MyComponent extends BareClass { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + +
  • one
  • +
  • two
  • +
    + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); + + it('should inherit ViewChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Directive({ + selector: '[super-dir]', + }) + class SuperDirective { + // TODO(issue/24571): remove '!'. + @ViewChildren(ChildDir) + customDirs !: QueryList; + } + + class BareClass extends SuperDirective {} + + @Component({ + selector: 'my-comp', + template: ` +
      +
    • {{item}}
    • +
    + `, + }) + class MyComponent extends BareClass { + items = [1, 2, 3, 4, 5]; + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir, SuperDirective], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(5); + }); + }); + + describe('of a component inherited by a component', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } + + beforeEach(() => fired.length = 0); + + it('ngOnInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnInit() { fired.push('sub init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + @Input() + foo = ''; + + @Input() + bar = ''; + + @Input() + baz = ''; + } + + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends SuperComponent { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: ``}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const subDir: MyComponent = + fixture.debugElement.query(By.directive(MyComponent)).componentInstance; + + expect(subDir.foo).toEqual('a'); + expect(subDir.bar).toEqual('b'); + expect(subDir.baz).toEqual('c'); + expect(subDir.qux).toEqual('d'); + }); + + it('should inherit outputs', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + @Output() + foo = new EventEmitter(); + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnInit() { this.foo.emit('test'); } + } + + @Component({ + template: ` + + ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test'); + }); + + it('should compose host bindings for styles', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + @HostBinding('style.color') + color = 'red'; + + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + } + + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + } + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.title).toBe('test!!!'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({ + selector: 'my-comp', + template: `
    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + +
  • one
  • +
  • two
  • +
    + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent, ChildDir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); + + it('should inherit ViewChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + // TODO(issue/24571): remove '!'. + @ViewChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({ + selector: 'my-comp', + template: ` +
      +
    • {{item}}
    • +
    + `, + }) + class MyComponent extends SuperComponent { + items = [1, 2, 3, 4, 5]; + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(5); + }); + }); + + describe('of a component inherited by a bare class then by a component', () => { + describe('lifecycle hooks', () => { + const fired: string[] = []; + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperSuperComponent { + ngOnInit() { fired.push('super init'); } + ngOnDestroy() { fired.push('super destroy'); } + ngAfterContentInit() { fired.push('super after content init'); } + ngAfterContentChecked() { fired.push('super after content checked'); } + ngAfterViewInit() { fired.push('super after view init'); } + ngAfterViewChecked() { fired.push('super after view checked'); } + ngDoCheck() { fired.push('super do check'); } + } + + class SuperComponent extends SuperSuperComponent {} + + beforeEach(() => fired.length = 0); + + it('ngOnInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnInit() { fired.push('sub init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngDoCheck', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngDoCheck() { fired.push('sub do check'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'sub do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterContentInit() { fired.push('sub after content init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'sub after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterContentChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterContentChecked() { fired.push('sub after content checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'sub after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewInit', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewInit() { fired.push('sub after view init'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'sub after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngAfterViewChecked', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewChecked() { fired.push('sub after view checked'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'sub after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super destroy', + ]); + }); + + it('ngOnDestroy', () => { + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnDestroy() { fired.push('sub destroy'); } + } + + @Component({ + template: ``, + }) + class App { + showing = true; + } + + TestBed.configureTestingModule({ + declarations: [MyComponent, App, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fired).toEqual([ + 'super init', + 'super do check', + 'super after content init', + 'super after content checked', + 'super after view init', + 'super after view checked', + ]); + + fired.length = 0; + fixture.componentInstance.showing = false; + fixture.detectChanges(); + + expect(fired).toEqual([ + 'sub destroy', + ]); + }); + }); + + it('should inherit inputs', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperSuperComponent { + @Input() + foo = ''; + + @Input() + baz = ''; + } + + class BareClass extends SuperSuperComponent { + @Input() + bar = ''; + } + + @Component({selector: 'my-comp', template: `

    test

    `}) + class MyComponent extends BareClass { + @Input() + baz = ''; + + @Input() + qux = ''; + } + + @Component({template: ``}) + class App { + a = 'a'; + b = 'b'; + c = 'c'; + d = 'd'; + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperSuperComponent, BareClass], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const myComp: MyComponent = + fixture.debugElement.query(By.directive(MyComponent)).componentInstance; + + expect(myComp.foo).toEqual('a'); + expect(myComp.bar).toEqual('b'); + expect(myComp.baz).toEqual('c'); + expect(myComp.qux).toEqual('d'); + }); + + it('should inherit outputs', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperSuperComponent { + @Output() + foo = new EventEmitter(); + } + + class SuperComponent extends SuperSuperComponent { + @Output() + bar = new EventEmitter(); + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + ngOnInit() { + this.foo.emit('test1'); + this.bar.emit('test2'); + } + } + + @Component({ + template: ` + + ` + }) + class App { + foo = ''; + + handleFoo(event: string) { this.foo = event; } + + bar = ''; + + handleBar(event: string) { this.bar = event; } + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent, SuperSuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const app = fixture.componentInstance; + + expect(app.foo).toBe('test1'); + expect(app.bar).toBe('test2'); + }); + + it('should compose host bindings for styles', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperSuperComponent { + @HostBinding('style.color') + color = 'red'; + } + + class SuperComponent extends SuperSuperComponent { + @HostBinding('style.backgroundColor') + bg = 'black'; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + } + + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent, SuperSuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.style.color).toBe('red'); + expect(queryResult.nativeElement.style.backgroundColor).toBe('black'); + }); + + it('should compose host bindings (non-style related)', () => { + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperSuperComponent { + @HostBinding('title') + get boundTitle() { return this.superTitle + '!!!'; } + + @Input() + superTitle = ''; + } + + class SuperComponent extends SuperSuperComponent { + @HostBinding('accessKey') + get boundAccessKey() { return this.superAccessKey + '???'; } + + @Input() + superAccessKey = ''; + } + + @Component({ + selector: 'my-comp', + template: `

    test

    `, + }) + class MyComponent extends SuperComponent { + } + @Component({ + template: ` + test + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent, SuperSuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const queryResult = fixture.debugElement.query(By.directive(MyComponent)); + + expect(queryResult.nativeElement.tagName).toBe('MY-COMP'); + expect(queryResult.nativeElement.title).toBe('test1!!!'); + expect(queryResult.nativeElement.accessKey).toBe('test2???'); + }); + + it('should inherit ContentChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + // TODO(issue/24571): remove '!'. + @ContentChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({ + selector: 'my-comp', + template: `
    `, + }) + class MyComponent extends SuperComponent { + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + +
  • one
  • +
  • two
  • +
    + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, SuperComponent, ChildDir], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(2); + }); + + it('should inherit ViewChildren queries', () => { + let foundQueryList: QueryList; + + @Directive({selector: '[child-dir]'}) + class ChildDir { + } + + @Component({ + selector: 'super-comp', + template: `

    super

    `, + }) + class SuperComponent { + // TODO(issue/24571): remove '!'. + @ViewChildren(ChildDir) + customDirs !: QueryList; + } + + @Component({ + selector: 'my-comp', + template: ` +
      +
    • {{item}}
    • +
    + `, + }) + class MyComponent extends SuperComponent { + items = [1, 2, 3, 4, 5]; + ngAfterViewInit() { foundQueryList = this.customDirs; } + } + + @Component({ + template: ` + + ` + }) + class App { + } + + TestBed.configureTestingModule({ + declarations: [App, MyComponent, ChildDir, SuperComponent], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(foundQueryList !.length).toBe(5); + }); }); }); - -@Component({selector: 'app-comp', template: ``}) -class AppComp { -} diff --git a/packages/core/test/render3/inherit_definition_feature_spec.ts b/packages/core/test/render3/inherit_definition_feature_spec.ts deleted file mode 100644 index 4d78e7eb7e..0000000000 --- a/packages/core/test/render3/inherit_definition_feature_spec.ts +++ /dev/null @@ -1,639 +0,0 @@ -/** - * @license - * Copyright Google Inc. All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import {ElementRef, Inject, InjectionToken, QueryList, ɵAttributeMarker as AttributeMarker} from '../../src/core'; -import {ΔallocHostVars, Δbind, ComponentDef, ΔcontentQuery, ΔdefineBase, ΔdefineComponent, ΔdefineDirective, DirectiveDef, ΔdirectiveInject, Δelement, ΔelementEnd, ΔelementProperty, ΔelementStart, ΔInheritDefinitionFeature, Δload, ΔloadContentQuery, ΔloadViewQuery, ΔNgOnChangesFeature, ΔProvidersFeature, ΔqueryRefresh, RenderFlags, ΔviewQuery,} from '../../src/render3/index'; - -import {ComponentFixture, createComponent, getDirectiveOnNode} from './render_util'; - -describe('InheritDefinitionFeature', () => { - it('should inherit lifecycle hooks', () => { - class SuperDirective { - ngOnInit() {} - ngOnDestroy() {} - ngAfterContentInit() {} - ngAfterContentChecked() {} - ngAfterViewInit() {} - ngAfterViewChecked() {} - ngDoCheck() {} - } - - class SubDirective extends SuperDirective { - ngAfterViewInit() {} - ngAfterViewChecked() {} - ngDoCheck() {} - - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'subDir', '']], - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - const finalDef = SubDirective.ngDirectiveDef as DirectiveDef; - - - expect(finalDef.onInit).toBe(SuperDirective.prototype.ngOnInit); - expect(finalDef.onDestroy).toBe(SuperDirective.prototype.ngOnDestroy); - expect(finalDef.afterContentChecked).toBe(SuperDirective.prototype.ngAfterContentChecked); - expect(finalDef.afterContentInit).toBe(SuperDirective.prototype.ngAfterContentInit); - expect(finalDef.afterViewChecked).toBe(SubDirective.prototype.ngAfterViewChecked); - expect(finalDef.afterViewInit).toBe(SubDirective.prototype.ngAfterViewInit); - expect(finalDef.doCheck).toBe(SubDirective.prototype.ngDoCheck); - }); - - it('should inherit inputs', () => { - class SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - inputs: { - superFoo: ['foo', 'declaredFoo'], - superBar: 'bar', - superBaz: 'baz', - }, - type: SuperDirective, - selectors: [['', 'superDir', '']], - factory: () => new SuperDirective(), - }); - } - - class SubDirective extends SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - inputs: { - subBaz: 'baz', - subQux: 'qux', - }, - selectors: [['', 'subDir', '']], - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - const subDef = SubDirective.ngDirectiveDef as DirectiveDef; - - expect(subDef.inputs).toEqual({ - foo: 'superFoo', - bar: 'superBar', - baz: 'subBaz', - qux: 'subQux', - }); - expect(subDef.declaredInputs).toEqual({ - foo: 'declaredFoo', - bar: 'bar', - baz: 'baz', - qux: 'qux', - }); - }); - - it('should inherit outputs', () => { - class SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - outputs: { - superFoo: 'foo', - superBar: 'bar', - superBaz: 'baz', - }, - type: SuperDirective, - selectors: [['', 'superDir', '']], - factory: () => new SuperDirective(), - }); - } - - class SubDirective extends SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - outputs: { - subBaz: 'baz', - subQux: 'qux', - }, - selectors: [['', 'subDir', '']], - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - const subDef = SubDirective.ngDirectiveDef as DirectiveDef; - - expect(subDef.outputs).toEqual({ - foo: 'superFoo', - bar: 'superBar', - baz: 'subBaz', - qux: 'subQux', - }); - }); - - it('should detect EMPTY inputs and outputs', () => { - class SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - inputs: { - testIn: 'testIn', - }, - outputs: { - testOut: 'testOut', - }, - type: SuperDirective, - selectors: [['', 'superDir', '']], - factory: () => new SuperDirective(), - }); - } - - class SubDirective extends SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'subDir', '']], - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - const subDef = SubDirective.ngDirectiveDef as DirectiveDef; - - expect(subDef.inputs).toEqual({ - testIn: 'testIn', - }); - expect(subDef.outputs).toEqual({ - testOut: 'testOut', - }); - }); - - it('should inherit inputs from ngBaseDefs along the way', () => { - - class Class5 { - input5 = 'data, so data'; - - static ngBaseDef = ΔdefineBase({ - inputs: { - input5: 'input5', - }, - }); - } - - class Class4 extends Class5 { - input4 = 'hehe'; - - static ngDirectiveDef = ΔdefineDirective({ - inputs: { - input4: 'input4', - }, - type: Class4, - selectors: [['', 'superDir', '']], - factory: () => new Class4(), - features: [ΔInheritDefinitionFeature], - }); - } - - class Class3 extends Class4 {} - - class Class2 extends Class3 { - input3 = 'wee'; - - static ngBaseDef = ΔdefineBase({ - inputs: { - input3: ['alias3', 'input3'], - } - }) as any; - } - - class Class1 extends Class2 { - input1 = 'test'; - input2 = 'whatever'; - - static ngDirectiveDef = ΔdefineDirective({ - type: Class1, - inputs: { - input1: 'input1', - input2: 'input2', - }, - selectors: [['', 'subDir', '']], - factory: () => new Class1(), - features: [ΔInheritDefinitionFeature], - }); - } - - const subDef = Class1.ngDirectiveDef as DirectiveDef; - - expect(subDef.inputs).toEqual({ - input1: 'input1', - input2: 'input2', - alias3: 'input3', - input4: 'input4', - input5: 'input5', - }); - expect(subDef.declaredInputs).toEqual({ - input1: 'input1', - input2: 'input2', - alias3: 'input3', - input4: 'input4', - input5: 'input5', - }); - }); - - it('should inherit outputs from ngBaseDefs along the way', () => { - - class Class5 { - output5 = 'data, so data'; - - static ngBaseDef = ΔdefineBase({ - outputs: { - output5: 'alias5', - }, - }); - } - - class Class4 extends Class5 { - output4 = 'hehe'; - - static ngDirectiveDef = ΔdefineDirective({ - outputs: { - output4: 'alias4', - }, - type: Class4, - selectors: [['', 'superDir', '']], - factory: () => new Class4(), - features: [ΔInheritDefinitionFeature], - }); - } - - class Class3 extends Class4 {} - - class Class2 extends Class3 { - output3 = 'wee'; - - static ngBaseDef = ΔdefineBase({ - outputs: { - output3: 'alias3', - } - }) as any; - } - - class Class1 extends Class2 { - output1 = 'test'; - output2 = 'whatever'; - - static ngDirectiveDef = ΔdefineDirective({ - type: Class1, - outputs: { - output1: 'alias1', - output2: 'alias2', - }, - selectors: [['', 'subDir', '']], - factory: () => new Class1(), - features: [ΔInheritDefinitionFeature], - }); - } - - const subDef = Class1.ngDirectiveDef as DirectiveDef; - - expect(subDef.outputs).toEqual({ - alias1: 'output1', - alias2: 'output2', - alias3: 'output3', - alias4: 'output4', - alias5: 'output5', - }); - }); - - it('should compose hostBindings', () => { - let subDir !: SubDirective; - - class SuperDirective { - id = 'my-id'; - - static ngDirectiveDef = ΔdefineDirective({ - type: SuperDirective, - selectors: [['', 'superDir', '']], - hostBindings: (rf: RenderFlags, ctx: SuperDirective, elementIndex: number) => { - if (rf & RenderFlags.Create) { - ΔallocHostVars(1); - } - if (rf & RenderFlags.Update) { - ΔelementProperty(elementIndex, 'id', Δbind(ctx.id)); - } - }, - factory: () => new SuperDirective(), - }); - } - - class SubDirective extends SuperDirective { - title = 'my-title'; - - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'subDir', '']], - hostBindings: (rf: RenderFlags, ctx: SubDirective, elementIndex: number) => { - if (rf & RenderFlags.Create) { - ΔallocHostVars(1); - } - if (rf & RenderFlags.Update) { - ΔelementProperty(elementIndex, 'title', Δbind(ctx.title)); - } - }, - factory: () => subDir = new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - Δelement(0, 'div', ['subDir', '']); - } - }, 1, 0, [SubDirective]); - - const fixture = new ComponentFixture(App); - const divEl = fixture.hostElement.querySelector('div') as HTMLElement; - - expect(divEl.id).toEqual('my-id'); - expect(divEl.title).toEqual('my-title'); - - subDir.title = 'new-title'; - fixture.update(); - expect(divEl.id).toEqual('my-id'); - expect(divEl.title).toEqual('new-title'); - - subDir.id = 'new-id'; - fixture.update(); - expect(divEl.id).toEqual('new-id'); - expect(divEl.title).toEqual('new-title'); - }); - - describe('view query', () => { - - const SomeComp = createComponent('some-comp', (rf: RenderFlags, ctx: any) => {}); - - /* - * class SuperComponent { - * @ViewChildren('super') superQuery; - * } - */ - class SuperComponent { - superQuery?: QueryList; - static ngComponentDef = ΔdefineComponent({ - type: SuperComponent, - template: () => {}, - consts: 0, - vars: 0, - selectors: [['super-comp']], - viewQuery: (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ΔviewQuery(['super'], false, null); - } - if (rf & RenderFlags.Update) { - let tmp: any; - ΔqueryRefresh(tmp = ΔloadViewQuery>()) && - (ctx.superQuery = tmp as QueryList); - } - }, - factory: () => new SuperComponent(), - }); - } - - /** - *
    - *
    - * - * class SubComponent extends SuperComponent { - * @ViewChildren('sub') subQuery; - * } - */ - class SubComponent extends SuperComponent { - subQuery?: QueryList; - static ngComponentDef = ΔdefineComponent({ - type: SubComponent, - template: (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - Δelement(0, 'div', ['id', 'sub'], ['sub', '']); - Δelement(2, 'div', ['id', 'super'], ['super', '']); - Δelement(4, 'some-comp'); - } - }, - consts: 5, - vars: 0, - selectors: [['sub-comp']], - viewQuery: (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - ΔviewQuery(['sub'], false, null); - } - if (rf & RenderFlags.Update) { - let tmp: any; - ΔqueryRefresh(tmp = ΔloadViewQuery>()) && - (ctx.subQuery = tmp as QueryList); - } - }, - factory: () => new SubComponent(), - features: [ΔInheritDefinitionFeature], - directives: [SomeComp] - }); - } - - - it('should compose viewQuery (basic mechanics check)', () => { - const log: Array<[string, RenderFlags, any]> = []; - - class SuperComponent { - static ngComponentDef = ΔdefineComponent({ - type: SuperComponent, - template: () => {}, - consts: 0, - vars: 0, - selectors: [['', 'superDir', '']], - viewQuery: (rf: RenderFlags, ctx: T) => { - log.push(['super', rf, ctx]); - }, - factory: () => new SuperComponent(), - }); - } - - class SubComponent extends SuperComponent { - static ngComponentDef = ΔdefineComponent({ - type: SubComponent, - template: () => {}, - consts: 0, - vars: 0, - selectors: [['', 'subDir', '']], - viewQuery: (rf: RenderFlags, ctx: SubComponent) => { - log.push(['sub', rf, ctx]); - }, - factory: () => new SubComponent(), - features: [ΔInheritDefinitionFeature] - }); - } - - const subDef = SubComponent.ngComponentDef as ComponentDef; - - const context = {foo: 'bar'}; - - subDef.viewQuery !(RenderFlags.Create, context); - - expect(log).toEqual( - [['super', RenderFlags.Create, context], ['sub', RenderFlags.Create, context]]); - }); - - - - it('should compose viewQuery (query logic check)', () => { - const fixture = new ComponentFixture(SubComponent); - - const check = (key: string): void => { - const qList = (fixture.component as any)[`${key}Query`] as QueryList; - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toEqual(fixture.hostElement.querySelector(`#${key}`)); - expect(qList.first.nativeElement.id).toEqual(key); - }; - - check('sub'); - check('super'); - }); - - it('should work with multiple viewQuery comps', () => { - let subCompOne !: SubComponent; - let subCompTwo !: SubComponent; - - const App = createComponent('app', (rf: RenderFlags, ctx: any) => { - if (rf & RenderFlags.Create) { - Δelement(0, 'sub-comp'); - Δelement(1, 'sub-comp'); - } - subCompOne = getDirectiveOnNode(0); - subCompTwo = getDirectiveOnNode(1); - }, 2, 0, [SubComponent, SuperComponent]); - - const fixture = new ComponentFixture(App); - - const check = (comp: SubComponent): void => { - const qList = comp.subQuery as QueryList; - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toEqual(fixture.hostElement.querySelector('#sub')); - expect(qList.first.nativeElement.id).toEqual('sub'); - }; - - check(subCompOne); - check(subCompTwo); - }); - - }); - - - it('should compose contentQueries (basic mechanics check)', () => { - const log: string[] = []; - - class SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - type: SuperDirective, - selectors: [['', 'superDir', '']], - contentQueries: () => { log.push('super'); }, - factory: () => new SuperDirective(), - }); - } - - class SubDirective extends SuperDirective { - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'subDir', '']], - contentQueries: () => { log.push('sub'); }, - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - }); - } - - const subDef = SubDirective.ngDirectiveDef as DirectiveDef; - - subDef.contentQueries !(RenderFlags.Create, {}, 0); - - expect(log).toEqual(['super', 'sub']); - }); - - it('should compose contentQueries (verify query sets)', () => { - let dirInstance: SubDirective; - class SuperDirective { - // @ContentChildren('foo') - foos !: QueryList; - - static ngDirectiveDef = ΔdefineDirective({ - type: SuperDirective, - selectors: [['', 'super-dir', '']], - factory: () => new SuperDirective(), - contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { - if (rf & RenderFlags.Create) { - ΔcontentQuery(dirIndex, ['foo'], true, null); - } - if (rf & RenderFlags.Update) { - let tmp: any; - ΔqueryRefresh(tmp = ΔloadContentQuery()) && (ctx.foos = tmp); - } - } - }); - } - - class SubDirective extends SuperDirective { - // @ContentChildren('bar') - bars !: QueryList; - - static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'sub-dir', '']], - factory: () => dirInstance = new SubDirective(), - contentQueries: (rf: RenderFlags, ctx: any, dirIndex: number) => { - if (rf & RenderFlags.Create) { - ΔcontentQuery(dirIndex, ['bar'], true, null); - } - if (rf & RenderFlags.Update) { - let tmp: any; - ΔqueryRefresh(tmp = ΔloadContentQuery()) && (ctx.bars = tmp); - } - }, - features: [ΔInheritDefinitionFeature] - }); - } - - /** - *
    - * - * - *
    - */ - const AppComponent = createComponent('app-component', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ΔelementStart(0, 'div', [AttributeMarker.Bindings, 'sub-dir']); - { - Δelement(1, 'span', null, ['foo', '']); - Δelement(3, 'span', null, ['bar', '']); - } - ΔelementEnd(); - } - }, 5, 0, [SubDirective]); - - const fixture = new ComponentFixture(AppComponent); - expect(dirInstance !.foos.length).toBe(1); - expect(dirInstance !.bars.length).toBe(1); - }); - - it('should throw if inheriting a component from a directive', () => { - class SuperComponent { - static ngComponentDef = ΔdefineComponent({ - type: SuperComponent, - template: () => {}, - selectors: [['', 'superDir', '']], - consts: 0, - vars: 0, - factory: () => new SuperComponent() - }); - } - - expect(() => { - class SubDirective extends SuperComponent{static ngDirectiveDef = ΔdefineDirective({ - type: SubDirective, - selectors: [['', 'subDir', '']], - factory: () => new SubDirective(), - features: [ΔInheritDefinitionFeature] - });} - }).toThrowError('Directives cannot inherit Components'); - }); - -});