diff --git a/packages/core/test/acceptance/view_insertion_spec.ts b/packages/core/test/acceptance/view_insertion_spec.ts index b02373d27b..dd50834e7e 100644 --- a/packages/core/test/acceptance/view_insertion_spec.ts +++ b/packages/core/test/acceptance/view_insertion_spec.ts @@ -7,7 +7,7 @@ */ import {CommonModule} from '@angular/common'; -import {ChangeDetectorRef, Component, EmbeddedViewRef, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; +import {ChangeDetectorRef, Component, ComponentFactoryResolver, Directive, EmbeddedViewRef, Injector, NgModule, TemplateRef, ViewChild, ViewContainerRef, ViewRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -249,4 +249,246 @@ describe('view insertion', () => { expect(fixture.debugElement.queryAll(By.css('div.dynamic')).length).toBe(4); }); }); + + describe('before another view', () => { + @Directive({selector: '[viewInserting]', exportAs: 'vi'}) + class ViewInsertingDir { + constructor(private _vcRef: ViewContainerRef) {} + + insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) { + this._vcRef.insert(beforeView, 0); + this._vcRef.createEmbeddedView(insertTpl, {}, 0); + } + } + + describe('before embedded view', () => { + @Component({ + selector: 'test-cmpt', + template: ` + insert + |before + +
+ ` + }) + class TestCmpt { + @ViewChild('before', {static: true}) beforeTpl !: TemplateRef<{}>; + @ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>; + @ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir; + + minutes = 10; + + insert() { + const beforeView = this.beforeTpl.createEmbeddedView({}); + // change-detect the "before view" to create all child views + beforeView.detectChanges(); + this.viewInsertingDir.insert(beforeView, this.insertTpl); + } + } + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [TestCmpt, ViewInsertingDir], + imports: [CommonModule], + }); + }); + + function createAndInsertViews(beforeTpl: string): any { + TestBed.overrideTemplate(TestCmpt, ` + insert + ${beforeTpl} + +
+ `); + const fixture = TestBed.createComponent(TestCmpt); + fixture.detectChanges(); + + fixture.componentInstance.insert(); + fixture.detectChanges(); + + return fixture.nativeElement; + } + + + it('should insert before a view with the text node as the first root node', + () => { expect(createAndInsertViews('|before').textContent).toBe('insert|before'); }); + + it('should insert before a view with the element as the first root node', () => { + expect(createAndInsertViews('|before').textContent).toBe('insert|before'); + }); + + it('should insert before a view with the ng-container as the first root node', () => { + expect(createAndInsertViews(` + + |before + + `).textContent) + .toBe('insert|before'); + }); + + it('should insert before a view with ICU container inside a ng-container as the first root node', + () => { + expect( + createAndInsertViews( + `{minutes, plural, =0 {just now} =1 {one minute ago} other {|before}}`) + .textContent) + .toBe('insert|before'); + }); + + it('should insert before a view with a container as the first root node', () => { + expect(createAndInsertViews(`|before`).textContent) + .toBe('insert|before'); + + }); + + it('should insert before a view with an empty container as the first root node', () => { + expect(createAndInsertViews(``).textContent) + .toBe('insert'); + + }); + + it('should insert before a view with an empty projection as the first root node', () => { + expect(createAndInsertViews(`|before`).textContent) + .toBe('insert|before'); + }); + + it('should insert before a view with complex node structure', () => { + expect(createAndInsertViews(` + + + + |before + + + + `).textContent) + .toBe('insert|before'); + }); + + }); + + describe('before embedded view with projection', () => { + + @Component({ + selector: 'with-content', + template: ` + insert + +
+ ` + }) + class WithContentCmpt { + @ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>; + @ViewChild('before', {static: true}) beforeTpl !: TemplateRef<{}>; + @ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir; + + insert() { + const beforeView = this.beforeTpl.createEmbeddedView({}); + // change-detect the "before view" to create all child views + beforeView.detectChanges(); + this.viewInsertingDir.insert(beforeView, this.insertTpl); + } + } + + @Component({selector: 'test-cmpt', template: ''}) + class TestCmpt { + @ViewChild('wc', {static: true}) withContentCmpt !: WithContentCmpt; + } + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ViewInsertingDir, WithContentCmpt, TestCmpt], + imports: [CommonModule], + }); + }); + + it('should insert before a view with projected text nodes', () => { + TestBed.overrideTemplate(TestCmpt, `|before`); + const fixture = TestBed.createComponent(TestCmpt); + fixture.detectChanges(); + + fixture.componentInstance.withContentCmpt.insert(); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('insert|before'); + }); + + it('should insert before a view with projected container', () => { + TestBed.overrideTemplate( + TestCmpt, + `|before`); + + const fixture = TestBed.createComponent(TestCmpt); + fixture.detectChanges(); + + fixture.componentInstance.withContentCmpt.insert(); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('insert|before'); + }); + + }); + + describe('before component view', () => { + @Directive({selector: '[viewInserting]', exportAs: 'vi'}) + class ViewInsertingDir { + constructor(private _vcRef: ViewContainerRef) {} + + insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) { + this._vcRef.insert(beforeView, 0); + this._vcRef.createEmbeddedView(insertTpl, {}, 0); + } + } + + @Component({selector: 'dynamic-cmpt', template: '|before'}) + class DynamicComponent { + } + + it('should insert in front a dynamic component view', () => { + @Component({ + selector: 'test-cmpt', + template: ` + insert +
+ ` + }) + class TestCmpt { + @ViewChild('insert', {static: true}) insertTpl !: TemplateRef<{}>; + @ViewChild('vi', {static: true}) viewInsertingDir !: ViewInsertingDir; + + constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {} + + insert() { + // create a dynamic component view to act as an "insert before" view + const componentFactory = this._cfr.resolveComponentFactory(DynamicComponent); + const beforeView = componentFactory.create(this._injector).hostView; + // change-detect the "before view" to create all child views + beforeView.detectChanges(); + this.viewInsertingDir.insert(beforeView, this.insertTpl); + } + } + + + @NgModule({ + declarations: [TestCmpt, ViewInsertingDir, DynamicComponent], + imports: [CommonModule], + entryComponents: [DynamicComponent] + }) + class TestModule { + } + + TestBed.configureTestingModule({imports: [TestModule]}); + + const fixture = TestBed.createComponent(TestCmpt); + fixture.detectChanges(); + + fixture.componentInstance.insert(); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent).toBe('insert|before'); + }); + + }); + }); + });