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');
+ });
+
+ });
+ });
+
});