diff --git a/packages/core/test/acceptance/template_ref_spec.ts b/packages/core/test/acceptance/template_ref_spec.ts index 981cc43bac..7184479bad 100644 --- a/packages/core/test/acceptance/template_ref_spec.ts +++ b/packages/core/test/acceptance/template_ref_spec.ts @@ -9,6 +9,7 @@ import {Component, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {expect} from '@angular/platform-browser/testing/src/matchers'; +import {onlyInIvy} from '@angular/private/testing'; describe('TemplateRef', () => { describe('rootNodes', () => { @@ -52,6 +53,117 @@ describe('TemplateRef', () => { expect(rootNodeTextContent).toEqual(['Header', 'Item one', 'Item two']); }); - }); + it('should return root render nodes for an embedded view instance', () => { + @Component({ + template: ` + +
+ some text + +
+ ` + }) + class App { + @ViewChild('templateRef') + templateRef !: TemplateRef; + } + + TestBed.configureTestingModule({ + declarations: [App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const embeddedView = fixture.componentInstance.templateRef.createEmbeddedView({}); + expect(embeddedView.rootNodes.length).toBe(3); + }); + + /** + * This is different as compared to the view engine implementation which returns a comment node + * in this case: + * https://stackblitz.com/edit/angular-uiqry6?file=src/app/app.component.ts + * + * Returning a comment node for a template ref with no nodes is wrong is fixed in Ivy. + */ + onlyInIvy('Fixed: Ivy no longer adds a comment node in this case.') + .it('should return an empty array for embedded view with no nodes', () => { + @Component({ + template: ` + + ` + }) + class App { + @ViewChild('templateRef') + templateRef !: TemplateRef; + } + + TestBed.configureTestingModule({ + declarations: [App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const embeddedView = fixture.componentInstance.templateRef.createEmbeddedView({}); + expect(embeddedView.rootNodes.length).toBe(0); + }); + + it('should not descend into containers when retrieving root nodes', () => { + /** + * NOTE: In VE, if `SUFFIX` text node below is _not_ present, VE will add an + * additional `` comment, thus being slightly different than Ivy. + * (resulting in 1 root node in Ivy and 2 in VE). + */ + @Component({ + template: ` + textSUFFIX + ` + }) + class App { + @ViewChild('templateRef') + templateRef !: TemplateRef; + } + + TestBed.configureTestingModule({ + declarations: [App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const embeddedView = fixture.componentInstance.templateRef.createEmbeddedView({}); + expect(embeddedView.rootNodes.length).toBe(2); + expect(embeddedView.rootNodes[0].nodeType).toBe(Node.COMMENT_NODE); + expect(embeddedView.rootNodes[1].nodeType).toBe(Node.TEXT_NODE); + }); + + /** + * Contrary to containers () we _do_ descend into element containers + * () + */ + it('should descend into element containers when retrieving root nodes', () => { + @Component({ + template: ` + + text + + ` + }) + class App { + @ViewChild('templateRef') + templateRef !: TemplateRef; + } + + TestBed.configureTestingModule({ + declarations: [App], + }); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const embeddedView = fixture.componentInstance.templateRef.createEmbeddedView({}); + + expect(embeddedView.rootNodes.length).toBe(2); + expect(embeddedView.rootNodes[0].nodeType).toBe(Node.COMMENT_NODE); + expect(embeddedView.rootNodes[1].nodeType).toBe(Node.TEXT_NODE); + }); + }); }); diff --git a/packages/core/test/render3/template_ref_spec.ts b/packages/core/test/render3/template_ref_spec.ts deleted file mode 100644 index d5059f366e..0000000000 --- a/packages/core/test/render3/template_ref_spec.ts +++ /dev/null @@ -1,178 +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 {TemplateRef} from '@angular/core'; - -import {AttributeMarker, RenderFlags, ΔdefineDirective} from '../../src/render3/index'; -import {Δbind, ΔdirectiveInject, Δelement, ΔelementContainerEnd, ΔelementContainerStart, ΔelementProperty, Δtemplate, Δtext} from '../../src/render3/instructions/all'; - -import {NgIf} from './common_with_def'; -import {ComponentFixture, createComponent, getDirectiveOnNode} from './render_util'; - -describe('TemplateRef', () => { - - describe('rootNodes', () => { - - class DirectiveWithTplRef { - static ngDirectiveDef = ΔdefineDirective({ - type: DirectiveWithTplRef, - selectors: [['', 'tplRef', '']], - factory: () => new DirectiveWithTplRef(ΔdirectiveInject(TemplateRef as any)) - }); - - // injecting a ViewContainerRef to create a dynamic container in which embedded views will be - // created - constructor(public tplRef: TemplateRef<{}>) {} - } - - it('should return root render nodes for an embedded view instance', () => { - let directiveWithTplRef: DirectiveWithTplRef; - - function embeddedTemplate(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δelement(0, 'div'); - Δtext(1, 'some text'); - Δelement(2, 'span'); - } - } - - /* - -
- some text - -
- */ - const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtemplate(0, embeddedTemplate, 3, 0, 'ng-template', ['tplRef', '']); - directiveWithTplRef = getDirectiveOnNode(0, 0); - } - }, 1, 0, [DirectiveWithTplRef]); - - - const fixture = new ComponentFixture(AppComponent); - expect(directiveWithTplRef !).toBeDefined(); - - const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({}); - expect(viewRef.rootNodes.length).toBe(3); - }); - - /** - * This is different as compared to the view engine implementation which returns a comment node - * in this case: - * https://stackblitz.com/edit/angular-uiqry6?file=src/app/app.component.ts - * - * Returning a comment node for a template ref with no nodes is wrong and should be fixed in - * ivy. - */ - it('should return an empty array for embedded view with no nodes', () => { - let directiveWithTplRef: DirectiveWithTplRef; - - /* - - */ - const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtemplate(0, () => {}, 0, 0, 'ng-template', ['tplRef', '']); - directiveWithTplRef = getDirectiveOnNode(0, 0); - } - }, 1, 0, [DirectiveWithTplRef]); - - - const fixture = new ComponentFixture(AppComponent); - expect(directiveWithTplRef !).toBeDefined(); - - const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({}); - expect(viewRef.rootNodes.length).toBe(0); - }); - - /** - * This is somehow surprising but the current view engine don't descend into containers when - * getting root nodes of an embedded view: - * https://stackblitz.com/edit/angular-z8zev7?file=src/app/app.component.ts - */ - it('should not descend into containers when retrieving root nodes', () => { - let directiveWithTplRef: DirectiveWithTplRef; - - function ngIfTemplate(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtext(0, 'text'); - } - } - - function embeddedTemplate(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtemplate(0, ngIfTemplate, 1, 0, 'ng-template', [AttributeMarker.Bindings, 'ngIf']); - } - if (rf & RenderFlags.Update) { - ΔelementProperty(0, 'ngIf', Δbind(ctx.showing)); - } - } - - /* - text - */ - const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtemplate(0, embeddedTemplate, 1, 1, 'ng-template', ['tplRef', '']); - directiveWithTplRef = getDirectiveOnNode(0, 0); - } - }, 1, 0, [DirectiveWithTplRef, NgIf]); - - - const fixture = new ComponentFixture(AppComponent); - expect(directiveWithTplRef !).toBeDefined(); - - const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({}); - - // assert that we've got a comment node (only!) corresponding to - expect(viewRef.rootNodes.length).toBe(1); - expect(viewRef.rootNodes[0].nodeType).toBe(8); - }); - - - /** - * Contrary to containers () we _do_ descend into element containers - * ( { - let directiveWithTplRef: DirectiveWithTplRef; - - function embeddedTemplate(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - ΔelementContainerStart(0); - { Δtext(1, 'text'); } - ΔelementContainerEnd(); - } - } - - /* - text - */ - const AppComponent = createComponent('app-cmp', function(rf: RenderFlags, ctx: any) { - if (rf & RenderFlags.Create) { - Δtemplate(0, embeddedTemplate, 2, 0, 'ng-template', ['tplRef', '']); - directiveWithTplRef = getDirectiveOnNode(0, 0); - } - }, 1, 0, [DirectiveWithTplRef]); - - - const fixture = new ComponentFixture(AppComponent); - expect(directiveWithTplRef !).toBeDefined(); - - const viewRef = directiveWithTplRef !.tplRef.createEmbeddedView({}); - - expect(viewRef.rootNodes.length).toBe(2); - expect(viewRef.rootNodes[0].nodeType) - .toBe(8); // a comment node (only!) corresponding to - expect(viewRef.rootNodes[1].nodeType).toBe(3); // a text node - }); - }); -});