it('should update content queries if embedded views are added or removed', () => { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), aServiceProvider(), ])), ])); Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a.length).toBe(0); const childView = Services.createEmbeddedView(view, view.def.nodes[3]); attachEmbeddedView(asElementData(view, 3), 0, childView); Services.checkAndUpdateView(view); expect(qs.a.length).toBe(1); detachEmbeddedView(asElementData(view, 3), 0); Services.checkAndUpdateView(view); expect(qs.a.length).toBe(0); });
it('should attach and detach embedded views', () => { const {view: parentView, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 2, 'div'), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child0'}) ])), anchorDef(NodeFlags.None, null, null, 0, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 0, 'span', {'name': 'child1'}) ])) ])); const childView0 = createEmbeddedView(parentView, parentView.def.nodes[1]); const childView1 = createEmbeddedView(parentView, parentView.def.nodes[2]); const rootChildren = getDOM().childNodes(rootNodes[0]); attachEmbeddedView(asElementData(parentView, 1), 0, childView0); attachEmbeddedView(asElementData(parentView, 1), 1, childView1); // 2 anchors + 2 elements expect(rootChildren.length).toBe(4); expect(getDOM().getAttribute(rootChildren[1], 'name')).toBe('child0'); expect(getDOM().getAttribute(rootChildren[2], 'name')).toBe('child1'); detachEmbeddedView(asElementData(parentView, 1), 1); detachEmbeddedView(asElementData(parentView, 1), 0); expect(getDOM().childNodes(rootNodes[0]).length).toBe(2); });
it('should update view queries if embedded views are added or removed', () => { const {view} = createAndGetRootNodes(compViewDef([ ...compViewQueryProviders( 0, [ anchorDef(NodeFlags.EmbeddedViews, null, null, 0, null, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), aServiceProvider(), ])), ]), ])); Services.checkAndUpdateView(view); const comp: QueryService = asProviderData(view, 1).instance; expect(comp.a.length).toBe(0); const compView = asElementData(view, 0).componentView; const childView = Services.createEmbeddedView(compView, compView.def.nodes[1]); attachEmbeddedView(view, asElementData(compView, 1), 0, childView); Services.checkAndUpdateView(view); expect(comp.a.length).toBe(1); detachEmbeddedView(asElementData(compView, 1), 0); Services.checkAndUpdateView(view); expect(comp.a.length).toBe(0); });
it('should include projected nodes when attaching / detaching embedded views', () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef(0, [textDef(2, 0, ['a'])], [ elementDef(0, NodeFlags.None, null, null, 1, 'div'), anchorDef(NodeFlags.EmbeddedViews, null, 0, 0, null, compViewDefFactory([ ngContentDef(null, 0), // The anchor would be added by the compiler after the ngContent anchorDef(NodeFlags.None, null, null, 0), ])), ]))); const componentView = asElementData(view, 0).componentView; const rf = componentView.root.rendererFactory; const view0 = createEmbeddedView(componentView, componentView.def.nodes[1]); attachEmbeddedView(view, asElementData(componentView, 1), 0, view0); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(3); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[1]) .toBe(asTextData(view, 2).renderText); rf.begin !(); detachEmbeddedView(asElementData(componentView, 1), 0); rf.end !(); expect(getDOM().childNodes(getDOM().firstChild(rootNodes[0])).length).toBe(1); });
it('should provide data for other nodes based on the nearest element parent', () => { const view = createViewWithData(); const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 1); expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement); });
it('should provide data for text nodes', () => { const view = createViewWithData(); const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 2); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asTextData(compView, 2).renderText); expect(debugCtx.injector.get(AComp)).toBe(compView.component); expect(debugCtx.component).toBe(compView.component); expect(debugCtx.context).toBe(compView.context); });
it('should provide data for elements', () => { const view = createViewWithData(); const compView = asElementData(view, 0).componentView; const debugCtx = Services.createDebugContext(compView, 0); expect(debugCtx.componentRenderElement).toBe(asElementData(view, 0).renderElement); expect(debugCtx.renderNode).toBe(asElementData(compView, 0).renderElement); expect(debugCtx.injector.get(AComp)).toBe(compView.component); expect(debugCtx.component).toBe(compView.component); expect(debugCtx.context).toBe(compView.context); expect(debugCtx.providerTokens).toEqual([AService]); expect(debugCtx.references['ref'].nativeElement) .toBe(asElementData(compView, 0).renderElement); });
it('should checkNoChanges', () => { const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 3, 'div'), ...contentQueryProviders(), anchorDef(NodeFlags.HasEmbeddedViews, null, null, 0, embeddedViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), aServiceProvider(), ])), ])); Services.checkAndUpdateView(view); Services.checkNoChangesView(view); const childView = Services.createEmbeddedView(view, view.def.nodes[3]); attachEmbeddedView(asElementData(view, 3), 0, childView); let err: any; try { Services.checkNoChangesView(view); } catch (e) { err = e; } expect(err).toBeTruthy(); expect(err.message) .toBe( `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Query 1 not dirty'. Current value: 'Query 1 dirty'.`); const debugCtx = getDebugContext(err); expect(debugCtx.view).toBe(view); expect(debugCtx.nodeIndex).toBe(2); });
it('should not stop dirty checking views that threw errors in change detection', () => { class AComp { a: any; } const update = jasmine.createSpy('updater'); const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null!, null!, 1, 'div', null!, null!, null!, null!, () => compViewDef( [ elementDef(NodeFlags.None, null!, null!, 0, 'span', null!, [[BindingFlags.TypeElementAttribute, 'a', SecurityContext.NONE]]), ], null!, update)), directiveDef( NodeFlags.Component, null!, 0, AComp, [], null!, null!, ), ])); const compView = asElementData(view, 0).componentView; update.and.callFake((check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); expect(update).toHaveBeenCalled(); update.calls.reset(); expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); expect(update).toHaveBeenCalled(); });
() => { class AComp { a: any; } const update = jasmine.createSpy('updater'); const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef( [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], null, update)), directiveDef( NodeFlags.IsComponent, null, 0, AComp, [], null, null, ), ])); const compView = asElementData(view, 0).componentView; update.and.callFake( (check: NodeCheckFn, view: ViewData) => { throw new Error('Test'); }); expect(() => Services.checkAndUpdateView(view)).toThrowError('Test'); expect(update).toHaveBeenCalled(); update.calls.reset(); Services.checkAndUpdateView(view); expect(update).not.toHaveBeenCalled(); });