diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index f5a0f82c1e..450fe9afcd 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -20,10 +20,10 @@ import {Type} from '../type'; import {assertGreaterThan, assertLessThan, assertNotNull} from './assert'; import {addToViewTree, assertPreviousIsParent, createLContainer, createLNodeObject, createTNode, createTView, getDirectiveInstance, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; -import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList} from './interfaces/definition'; +import {ComponentTemplate, DirectiveDef} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; import {AttributeMarker, LContainerNode, LElementNode, LNode, LViewNode, TNodeFlags, TNodeType} from './interfaces/node'; -import {QueryReadType} from './interfaces/query'; +import {LQueries, QueryReadType} from './interfaces/query'; import {Renderer3} from './interfaces/renderer'; import {LView, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; @@ -576,6 +576,11 @@ export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainer const lContainerNode: LContainerNode = createLNodeObject( TNodeType.Container, vcRefHost.view, hostParent, undefined, lContainer, null); + + if (vcRefHost.queries) { + lContainerNode.queries = vcRefHost.queries.container(); + } + const hostTNode = vcRefHost.tNode; if (!hostTNode.dynamicContainerNode) { hostTNode.dynamicContainerNode = @@ -701,7 +706,7 @@ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef ngDevMode && assertNotNull(hostTNode.tViews, 'TView must be allocated'); di.templateRef = new TemplateRef( getOrCreateElementRef(di), hostTNode.tViews as TView, hostNode.data.template !, - getRenderer(), hostTView.directiveRegistry, hostTView.pipeRegistry); + getRenderer(), hostNode.queries); } return di.templateRef; } @@ -712,13 +717,13 @@ class TemplateRef implements viewEngine_TemplateRef { constructor( elementRef: viewEngine_ElementRef, private _tView: TView, private _template: ComponentTemplate, private _renderer: Renderer3, - private _directives: DirectiveDefList|null, private _pipes: PipeDefList|null) { + private _queries: LQueries|null) { this.elementRef = elementRef; } createEmbeddedView(context: T): viewEngine_EmbeddedViewRef { const viewNode = renderEmbeddedTemplate( - null, this._tView, this._template, context, this._renderer, this._directives, this._pipes); + null, this._tView, this._template, context, this._renderer, this._queries); return addDestroyable(new EmbeddedViewRef(viewNode, this._template, context)); } } diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index cc178c2eb0..ba586dfad2 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -493,8 +493,7 @@ export function renderTemplate( */ export function renderEmbeddedTemplate( viewNode: LViewNode | null, tView: TView, template: ComponentTemplate, context: T, - renderer: Renderer3, directives?: DirectiveDefList | null, - pipes?: PipeDefList | null): LViewNode { + renderer: Renderer3, queries?: LQueries | null): LViewNode { const _isParent = isParent; const _previousOrParentNode = previousOrParentNode; let oldView: LView; @@ -507,6 +506,10 @@ export function renderEmbeddedTemplate( const lView = createLView( -1, renderer, tView, template, context, LViewFlags.CheckAlways, getCurrentSanitizer()); + if (queries) { + lView.queries = queries.createView(); + } + viewNode = createLNode(null, TNodeType.View, null, null, null, lView); rf = RenderFlags.Create; } @@ -1643,8 +1646,9 @@ export function embeddedViewStart(viewBlockId: number): RenderFlags { const newView = createLView( viewBlockId, renderer, getOrCreateEmbeddedTView(viewBlockId, container), null, null, LViewFlags.CheckAlways, getCurrentSanitizer()); + if (lContainer.queries) { - newView.queries = lContainer.queries.enterView(lContainer.nextIndex !); + newView.queries = lContainer.queries.createView(); } enterView( diff --git a/packages/core/src/render3/interfaces/query.ts b/packages/core/src/render3/interfaces/query.ts index 2ab94de136..57822a0815 100644 --- a/packages/core/src/render3/interfaces/query.ts +++ b/packages/core/src/render3/interfaces/query.ts @@ -28,19 +28,25 @@ export interface LQueries { addNode(node: LNode): void; /** - * Notify `LQueries` that a `LNode` has been created and needs to be added to query results - * if matching query predicate. + * Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need + * to prepare room for views that might be inserted into this container. */ container(): LQueries|null; /** - * Notify `LQueries` that a new view was created and is being entered in the creation mode. - * This allow queries to prepare space for matching nodes from views. + * Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room + * and collect nodes that match query predicate. */ - enterView(newViewIndex: number): LQueries|null; + createView(): LQueries|null; /** - * Notify `LQueries` that an `LViewNode` has been removed from `LContainerNode`. As a result all + * Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all + * the matching nodes from this view should be added to container's queries. + */ + insertView(newViewIndex: number): void; + + /** + * Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all * the matching nodes from this view should be removed from container's queries. */ removeView(removeIndex: number): void; diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index 1000197b31..95633efbea 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -328,6 +328,12 @@ export function insertView( viewNode.data.next = null; } + // Notify query that a new view has been added + const lView = viewNode.data; + if (lView.queries) { + lView.queries.insertView(index); + } + // If the container's renderParent is null, we know that it is a root node of its own parent view // and we should wait until that parent processes its nodes (otherwise, we will insert this view's // nodes twice - once now and once when its parent inserts its views). @@ -367,8 +373,13 @@ export function removeView(container: LContainerNode, removeIndex: number): LVie views.splice(removeIndex, 1); destroyViewTree(viewNode.data); addRemoveViewFromContainer(container, viewNode, false); + // Notify query that view has been removed - container.data.queries && container.data.queries.removeView(removeIndex); + const removedLview = viewNode.data; + if (removedLview.queries) { + removedLview.queries.removeView(removeIndex); + } + return viewNode; } diff --git a/packages/core/src/render3/query.ts b/packages/core/src/render3/query.ts index 4502b47b9c..21a69f8ff3 100644 --- a/packages/core/src/render3/query.ts +++ b/packages/core/src/render3/query.ts @@ -76,6 +76,12 @@ export interface LQuery { * This is what builds up the `QueryList._valuesTree`. */ values: any[]; + + /** + * A pointer to an array that stores collected values from views. This is necessary so we know a + * container into which to insert nodes collected from views. + */ + containerValues: any[]|null; } export class LQueries_ implements LQueries { @@ -118,8 +124,13 @@ export class LQueries_ implements LQueries { while (query) { const containerValues: any[] = []; // prepare room for views query.values.push(containerValues); - const clonedQuery: LQuery = - {next: null, list: query.list, predicate: query.predicate, values: containerValues}; + const clonedQuery: LQuery = { + next: null, + list: query.list, + predicate: query.predicate, + values: containerValues, + containerValues: null + }; clonedQuery.next = result; result = clonedQuery; query = query.next; @@ -128,15 +139,18 @@ export class LQueries_ implements LQueries { return result ? new LQueries_(result) : null; } - enterView(index: number): LQueries|null { + createView(): LQueries|null { let result: LQuery|null = null; let query = this.deep; while (query) { - const viewValues: any[] = []; // prepare room for view nodes - query.values.splice(index, 0, viewValues); - const clonedQuery: LQuery = - {next: null, list: query.list, predicate: query.predicate, values: viewValues}; + const clonedQuery: LQuery = { + next: null, + list: query.list, + predicate: query.predicate, + values: [], + containerValues: query.values + }; clonedQuery.next = result; result = clonedQuery; query = query.next; @@ -145,6 +159,17 @@ export class LQueries_ implements LQueries { return result ? new LQueries_(result) : null; } + insertView(index: number): void { + let query = this.deep; + while (query) { + ngDevMode && + assertNotNull( + query.containerValues, 'View queries need to have a pointer to container values.'); + query.containerValues !.splice(index, 0, query.values); + query = query.next; + } + } + addNode(node: LNode): void { add(this.shallow, node); add(this.deep, node); @@ -153,7 +178,10 @@ export class LQueries_ implements LQueries { removeView(index: number): void { let query = this.deep; while (query) { - const removed = query.values.splice(index, 1); + ngDevMode && + assertNotNull( + query.containerValues, 'View queries need to have a pointer to container values.'); + const removed = query.containerValues !.splice(index, 1); // mark a query as dirty only when removed view had matching modes ngDevMode && assertEqual(removed.length, 1, 'removed.length'); @@ -279,7 +307,8 @@ function createQuery( next: previous, list: queryList, predicate: createPredicate(predicate, read), - values: (queryList as any as QueryList_)._valuesTree + values: (queryList as any as QueryList_)._valuesTree, + containerValues: null }; } diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index e31fbd02d7..9309ec5022 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -5,14 +5,17 @@ * 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 {NgForOfContext} from '@angular/common'; + import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF} from '../../src/render3/di'; import {QueryList, defineComponent, detectChanges} from '../../src/render3/index'; -import {container, containerRefreshEnd, containerRefreshStart, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; import {query, queryRefresh} from '../../src/render3/query'; -import {createComponent, createDirective, renderComponent} from './render_util'; - +import {NgForOf, NgIf} from './common_with_def'; +import {ComponentFixture, createComponent, createDirective, renderComponent} from './render_util'; /** @@ -685,307 +688,406 @@ describe('query', () => { describe('view boundaries', () => { - it('should report results in embedded views', () => { - let firstEl; - /** - * - *
- *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } + describe('ViewContainerRef', () => { + + it('should report results in views inserted / removed by ngIf', () => { + + /** + * + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1, (rf1: RenderFlags, ctx1: any) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); } - embeddedViewEnd(); - } + }, null, ['ngIf', '']); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngIf', bind(ctx.value)); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }, [NgIf]); + + const fixture = new ComponentFixture(Cmpt); + const qList = fixture.component.query; + expect(qList.length).toBe(0); + + fixture.component.value = true; + fixture.update(); + expect(qList.length).toBe(1); + + fixture.component.value = false; + fixture.update(); + expect(qList.length).toBe(0); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); + it('should report results in views inserted / removed by ngFor', () => { - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toBe(firstEl); + /** + * + *
+ *
+ * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1, (rf1: RenderFlags, row: NgForOfContext) => { + if (rf1 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + if (rf1 & RenderFlags.Update) { + elementProperty(0, 'id', bind(row.$implicit)); + } + }, null, ['ngForOf', '']); + } + if (rf & RenderFlags.Update) { + elementProperty(1, 'ngForOf', bind(ctx.value)); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }, [NgForOf]); + + const fixture = new ComponentFixture(Cmpt); + const qList = fixture.component.query; + expect(qList.length).toBe(0); + + fixture.component.value = ['a', 'b', 'c']; + fixture.update(); + fixture + .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 + expect(qList.length).toBe(3); + + fixture.component.value.splice(1, 1); // remove "b" + fixture.update(); + fixture + .update(); // invoking CD twice due to https://github.com/angular/angular/issues/23707 + expect(qList.length).toBe(2); + + // make sure that a proper element was removed from query results + expect(qList.first.nativeElement.id).toBe('a'); + expect(qList.last.nativeElement.id).toBe('c'); + + }); - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(qList.length).toBe(0); }); - it('should add results from embedded views in the correct order - views and elements mix', - () => { - let firstEl, lastEl, viewEl; - /** - * - * - *
- *
- * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - firstEl = elementStart(1, 'span', null, ['foo', '']); - elementEnd(); - container(3); - lastEl = elementStart(4, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(3); - { - if (ctx.exp) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - viewEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + describe('JS blocks', () => { + + it('should report results in embedded views', () => { + let firstEl; + /** + * % if (exp) { + *
+ * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toBe(firstEl); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(qList.length).toBe(0); + }); + + it('should add results from embedded views in the correct order - views and elements mix', + () => { + let firstEl, lastEl, viewEl; + /** + * + * % if (exp) { + *
+ * % } + * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + firstEl = elementStart(1, 'span', null, ['foo', '']); + elementEnd(); + container(3); + lastEl = elementStart(4, 'span', null, ['foo', '']); + elementEnd(); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(3); + { + if (ctx.exp) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + viewEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); + + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(3); + expect(qList.toArray()[0].nativeElement).toBe(firstEl); + expect(qList.toArray()[1].nativeElement).toBe(viewEl); + expect(qList.toArray()[2].nativeElement).toBe(lastEl); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(3); - expect(qList.toArray()[0].nativeElement).toBe(firstEl); - expect(qList.toArray()[1].nativeElement).toBe(viewEl); - expect(qList.toArray()[2].nativeElement).toBe(lastEl); - - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should add results from embedded views in the correct order - views side by side', () => { - let firstEl, lastEl; - /** - * - *
- *
- * - * - * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } - if (ctx.exp2) { - let rf1 = embeddedViewStart(1); - { - if (rf1 & RenderFlags.Create) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + it('should add results from embedded views in the correct order - views side by side', () => { + let firstEl, lastEl; + /** + * % if (exp1) { + *
+ * % } if (exp2) { + * + * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } - }); - - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); - - cmptInstance.exp2 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.last.nativeElement).toBe(lastEl); - - cmptInstance.exp1 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should add results from embedded views in the correct order - nested views', () => { - let firstEl, lastEl; - /** - * - *
- * - * - * - *
- * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - container(1); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(1); - { - if (ctx.exp1) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - firstEl = elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - container(2); - } - if (rf0 & RenderFlags.Update) { - containerRefreshStart(2); - { - if (ctx.exp2) { - let rf2 = embeddedViewStart(0); - { - if (rf2) { - lastEl = elementStart(0, 'span', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); } - containerRefreshEnd(); } + embeddedViewEnd(); + } + if (ctx.exp2) { + let rf1 = embeddedViewStart(1); + { + if (rf1 & RenderFlags.Create) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); } - embeddedViewEnd(); } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); - } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp2 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.last.nativeElement).toBe(lastEl); + + cmptInstance.exp1 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const qList = (cmptInstance.query as any); - expect(qList.length).toBe(0); - - cmptInstance.exp1 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(1); - expect(qList.first.nativeElement).toBe(firstEl); - - cmptInstance.exp2 = true; - detectChanges(cmptInstance); - expect(qList.length).toBe(2); - expect(qList.first.nativeElement).toBe(firstEl); - expect(qList.last.nativeElement).toBe(lastEl); - }); - - it('should support combination of deep and shallow queries', () => { - /** - * - *
- *
- * - * class Cmpt { - * @ViewChildren('foo') query; - * } - */ - const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { - let tmp: any; - if (rf & RenderFlags.Create) { - query(0, ['foo'], true, QUERY_READ_FROM_NODE); - query(1, ['foo'], false, QUERY_READ_FROM_NODE); - container(2); - elementStart(3, 'span', null, ['foo', '']); - elementEnd(); - } - if (rf & RenderFlags.Update) { - containerRefreshStart(2); - { - if (ctx.exp) { - let rf0 = embeddedViewStart(0); - { - if (rf0 & RenderFlags.Create) { - elementStart(0, 'div', null, ['foo', '']); - elementEnd(); - } - } - embeddedViewEnd(); - } + it('should add results from embedded views in the correct order - nested views', () => { + let firstEl, lastEl; + /** + * % if (exp1) { + *
+ * % if (exp2) { + * + * } + * % } + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + container(1); } - containerRefreshEnd(); - queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); - queryRefresh(tmp = load>(1)) && (ctx.shallow = tmp as QueryList); - } + if (rf & RenderFlags.Update) { + containerRefreshStart(1); + { + if (ctx.exp1) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + firstEl = elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + container(2); + } + if (rf0 & RenderFlags.Update) { + containerRefreshStart(2); + { + if (ctx.exp2) { + let rf2 = embeddedViewStart(0); + { + if (rf2) { + lastEl = elementStart(0, 'span', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const qList = (cmptInstance.query as any); + expect(qList.length).toBe(0); + + cmptInstance.exp1 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(1); + expect(qList.first.nativeElement).toBe(firstEl); + + cmptInstance.exp2 = true; + detectChanges(cmptInstance); + expect(qList.length).toBe(2); + expect(qList.first.nativeElement).toBe(firstEl); + expect(qList.last.nativeElement).toBe(lastEl); }); - const cmptInstance = renderComponent(Cmpt); - const deep = (cmptInstance.deep as any); - const shallow = (cmptInstance.shallow as any); - expect(deep.length).toBe(1); - expect(shallow.length).toBe(1); + it('should support combination of deep and shallow queries', () => { + /** + * + *
+ *
+ * + * class Cmpt { + * @ViewChildren('foo') query; + * } + */ + const Cmpt = createComponent('cmpt', function(rf: RenderFlags, ctx: any) { + let tmp: any; + if (rf & RenderFlags.Create) { + query(0, ['foo'], true, QUERY_READ_FROM_NODE); + query(1, ['foo'], false, QUERY_READ_FROM_NODE); + container(2); + elementStart(3, 'span', null, ['foo', '']); + elementEnd(); + } + if (rf & RenderFlags.Update) { + containerRefreshStart(2); + { + if (ctx.exp) { + let rf0 = embeddedViewStart(0); + { + if (rf0 & RenderFlags.Create) { + elementStart(0, 'div', null, ['foo', '']); + elementEnd(); + } + } + embeddedViewEnd(); + } + } + containerRefreshEnd(); + queryRefresh(tmp = load>(0)) && (ctx.deep = tmp as QueryList); + queryRefresh(tmp = load>(1)) && (ctx.shallow = tmp as QueryList); + } + }); + + const cmptInstance = renderComponent(Cmpt); + const deep = (cmptInstance.deep as any); + const shallow = (cmptInstance.shallow as any); + expect(deep.length).toBe(1); + expect(shallow.length).toBe(1); - cmptInstance.exp = true; - detectChanges(cmptInstance); - expect(deep.length).toBe(2); - expect(shallow.length).toBe(1); + cmptInstance.exp = true; + detectChanges(cmptInstance); + expect(deep.length).toBe(2); + expect(shallow.length).toBe(1); + + cmptInstance.exp = false; + detectChanges(cmptInstance); + expect(deep.length).toBe(1); + expect(shallow.length).toBe(1); + }); - cmptInstance.exp = false; - detectChanges(cmptInstance); - expect(deep.length).toBe(1); - expect(shallow.length).toBe(1); }); });