it('should listen to window events', () => { const handleEventSpy = jasmine.createSpy('handleEvent'); const addListenerSpy = spyOn(window, 'addEventListener'); const removeListenerSpy = spyOn(window, 'removeEventListener'); const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef( NodeFlags.None, null, null, 0, 'button', null, null, [['window', 'windowClick']], handleEventSpy)])); expect(addListenerSpy).toHaveBeenCalled(); expect(addListenerSpy.calls.mostRecent().args[0]).toBe('windowClick'); addListenerSpy.calls.mostRecent().args[1]({name: 'windowClick'}); expect(handleEventSpy).toHaveBeenCalled(); const handleEventArgs = handleEventSpy.calls.mostRecent().args; expect(handleEventArgs[0]).toBe(view); expect(handleEventArgs[1]).toBe('window:windowClick'); expect(handleEventArgs[2]).toBeTruthy(); Services.destroyView(view); expect(removeListenerSpy).toHaveBeenCalled(); });
it(`should update ${ArgumentType[inlineDynamic]}`, () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef( NodeFlags.None, null, null, 0, 'input', null, [ [BindingType.ElementProperty, 'title', SecurityContext.NONE], [BindingType.ElementProperty, 'value', SecurityContext.NONE] ]), ], null, (check, view) => { checkNodeInlineOrDynamic(check, view, 0, inlineDynamic, ['v1', 'v2']); })); Services.checkAndUpdateView(view); const el = rootNodes[0]; expect(getDOM().getProperty(el, 'title')).toBe('v1'); expect(getDOM().getProperty(el, 'value')).toBe('v2'); expect(getDOM().getAttribute(el, 'ng-reflect-title')).toBe('v1'); });
it('should create and attach component views', () => { let instance: AComp = undefined !; class AComp { constructor() { instance = this; } } const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef( 0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([ elementDef(0, NodeFlags.None, null, null, 0, 'span'), ])), directiveDef(1, NodeFlags.Component, null, 0, AComp, []), ])); const compView = asElementData(view, 0).componentView; expect(compView.context).toBe(instance); expect(compView.component).toBe(instance); const compRootEl = getDOM().childNodes(rootNodes[0])[0]; expect(getDOM().nodeName(compRootEl).toLowerCase()).toBe('span'); });
it('should destroy component views', () => { const log: string[] = []; class AComp {} class ChildProvider { ngOnDestroy() { log.push('ngOnDestroy'); } } const {view, rootNodes} = createAndGetRootNodes(compViewDef([ elementDef( 0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([ elementDef(0, NodeFlags.None, null, null, 1, 'span'), directiveDef(1, NodeFlags.OnDestroy, null, 0, ChildProvider, []) ])), directiveDef(1, NodeFlags.Component, null, 0, AComp, [], null, null, ), ])); Services.destroyView(view); expect(log).toEqual(['ngOnDestroy']); });
it('should project already attached embedded views', () => { class CreateViewService { constructor(templateRef: TemplateRef<any>, viewContainerRef: ViewContainerRef) { viewContainerRef.createEmbeddedView(templateRef); } } const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef( [ anchorDef(NodeFlags.HasEmbeddedViews, null, 0, 1, null, embeddedViewDef([textDef( null, ['a'])])), directiveDef( NodeFlags.None, null, 0, CreateViewService, [TemplateRef, ViewContainerRef]) ], [elementDef(NodeFlags.None, null, null, 1, 'div'), ngContentDef(null, 0)]))); const anchor = asElementData(view, 2); expect((getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[0])) .toBe(anchor.renderElement); const embeddedView = anchor.embeddedViews[0]; expect((getDOM().childNodes(getDOM().firstChild(rootNodes[0]))[1])) .toBe(asTextData(embeddedView, 0).renderText); });
it('should dirty check component views', () => { let value: any; class AComp { a: any; } const update = jasmine.createSpy('updater').and.callFake((view: ViewData) => { setCurrentNode(view, 0); checkNodeInline(value); }); const {view, rootNodes} = createAndGetRootNodes( compViewDef([ elementDef(NodeFlags.None, null, null, 1, 'div'), directiveDef(NodeFlags.None, null, 0, AComp, [], null, null, () => compViewDef( [ elementDef(NodeFlags.None, null, null, 0, 'span', null, [[BindingType.ElementAttribute, 'a', SecurityContext.NONE]]), ], update )), ])); const compView = asProviderData(view, 1).componentView; value = 'v1'; checkAndUpdateView(view); expect(update).toHaveBeenCalledWith(compView); update.calls.reset(); checkNoChangesView(view); expect(update).toHaveBeenCalledWith(compView); value = 'v2'; expect(() => checkNoChangesView(view)) .toThrowError( `Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); });
it('should dirty check component views', () => { let value: any; class AComp { a: any; } const update = jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => { check(view, 0, ArgumentType.Inline, value); }); const {view, rootNodes} = createAndGetRootNodes( compViewDef([ elementDef(0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef( [ elementDef(0, NodeFlags.None, null, null, 0, 'span', null, [[BindingFlags.TypeElementAttribute, 'a', SecurityContext.NONE]]), ], null, update )), directiveDef(1, NodeFlags.Component, null, 0, AComp, []), ])); const compView = asElementData(view, 0).componentView; value = 'v1'; Services.checkAndUpdateView(view); expect(update.calls.mostRecent().args[1]).toBe(compView); update.calls.reset(); Services.checkNoChangesView(view); expect(update.calls.mostRecent().args[1]).toBe(compView); value = 'v2'; expect(() => Services.checkNoChangesView(view)) .toThrowError( `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'v1'. Current value: 'v2'.`); });
it(`should update via strategy ${inlineDynamic}`, () => { class SomePipe implements PipeTransform { transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; } } let values: any[]; const {view, rootNodes} = createAndGetRootNodes(compViewDef( [ elementDef(0, NodeFlags.None, null !, null !, 3, 'span'), pipeDef(NodeFlags.None, SomePipe, []), purePipeDef(2, 2), directiveDef(3, NodeFlags.None, null, 0, Service, [], {data: [0, 'data']}), ], (check, view) => { const pureValue = checkNodeInlineOrDynamic( check, view, 2, inlineDynamic, [nodeValue(view, 1)].concat(values)); checkNodeInlineOrDynamic(check, view, 3, inlineDynamic, [pureValue]); })); const service = asProviderData(view, 3).instance; values = [1, 2]; Services.checkAndUpdateView(view); const obj0 = service.data; expect(obj0).toEqual([11, 22]); // instance should not change // if the values don't change Services.checkAndUpdateView(view); expect(service.data).toBe(obj0); values = [3, 2]; Services.checkAndUpdateView(view); const obj1 = service.data; expect(obj1).not.toBe(obj0); expect(obj1).toEqual([13, 22]); });
it('should query all matches', () => { class QueryService { a: QueryList<AService>; } const {view} = createAndGetRootNodes(compViewDef([ elementDef(NodeFlags.None, null, null, 4, 'div'), directiveDef(NodeFlags.None, null, 1, QueryService, []), queryDef( NodeFlags.TypeContentQuery | NodeFlags.DynamicQuery, someQueryId, {'a': QueryBindingType.All}), aServiceProvider(), aServiceProvider(), ])); Services.checkAndUpdateView(view); const qs: QueryService = asProviderData(view, 1).instance; expect(qs.a instanceof QueryList).toBeTruthy(); expect(qs.a.toArray()).toEqual([ asProviderData(view, 3).instance, asProviderData(view, 4).instance, ]); });
it('should include projected nodes when attaching / detaching embedded views', () => { const {view, rootNodes} = createAndGetRootNodes(compViewDef(hostElDef([textDef(0, ['a'])], [ elementDef(NodeFlags.None, null !, null !, 1, 'div'), anchorDef(NodeFlags.EmbeddedViews, null !, 0, 0, null !, embeddedViewDef([ 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); });