UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

749 lines (726 loc) 40.2 kB
import { NgFor, NgIf } from '@angular/common'; import { ChangeDetectorRef, Component, DebugElement, ElementRef, EventEmitter, QueryList, ViewChild } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DisplayDensity } from '../core/density'; import { AnimationService } from '../services/animation/animation'; import { configureTestSuite } from '../test-utils/configure-suite'; import { TreeTestFunctions } from './tree-functions.spec'; import { IgxTreeNavigationService } from './tree-navigation.service'; import { IgxTreeNodeComponent } from './tree-node/tree-node.component'; import { IgxTreeSelectionService } from './tree-selection.service'; import { IgxTreeComponent } from './tree.component'; import { IgxTreeService } from './tree.service'; const TREE_ROOT_CLASS = 'igx-tree__root'; const NODE_TAG = 'igx-tree-node'; describe('IgxTree #treeView', () => { configureTestSuite(); describe('Unit Tests', () => { let mockNavService: IgxTreeNavigationService; let mockTreeService: IgxTreeService; let mockSelectionService: IgxTreeSelectionService; let mockElementRef: ElementRef<any>; let mockNodes: QueryList<IgxTreeNodeComponent<any>>; let mockNodesArray: IgxTreeNodeComponent<any>[] = []; let tree: IgxTreeComponent = null; beforeEach(() => { mockNodesArray = []; mockNavService = jasmine.createSpyObj('navService', ['register', 'update_disabled_cache', 'update_visible_cache', 'init_invisible_cache', 'setFocusedAndActiveNode', 'handleKeydown']); mockTreeService = jasmine.createSpyObj('treeService', ['register', 'collapse', 'expand', 'collapsing', 'isExpanded']); mockSelectionService = jasmine.createSpyObj('selectionService', ['register', 'deselectNodesWithNoEvent', 'ensureStateOnNodeDelete', 'selectNodesWithNoEvent']); mockElementRef = jasmine.createSpyObj('elementRef', [], { nativeElement: jasmine.createSpyObj('nativeElement', ['focus'], {}) }); tree?.ngOnDestroy(); tree = new IgxTreeComponent(mockNavService, mockSelectionService, mockTreeService, mockElementRef); mockNodes = jasmine.createSpyObj('mockList', ['toArray'], { changes: new Subject<void>(), get first() { return mockNodesArray[0]; }, get last() { return mockNodesArray[mockNodesArray.length - 1]; }, get length() { return mockNodesArray.length; }, forEach: (cb: (n: IgxTreeNodeComponent<any>) => void): void => { mockNodesArray.forEach(cb); }, find: (cb: (n: IgxTreeNodeComponent<any>) => boolean): IgxTreeNodeComponent<any> => mockNodesArray.find(cb), filter: jasmine.createSpy('filter'). and.callFake((cb: (n: IgxTreeNodeComponent<any>) => boolean): IgxTreeNodeComponent<any>[] => mockNodesArray.filter(cb)), }); spyOn(mockNodes, 'toArray').and.returnValue(mockNodesArray); }); afterEach(() => { tree?.ngOnDestroy(); }); describe('IgxTreeComponent', () => { it('Should update nav children cache when events are fired', fakeAsync(() => { expect(mockNavService.init_invisible_cache).toHaveBeenCalledTimes(0); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(0); expect(mockNavService.update_disabled_cache).toHaveBeenCalledTimes(0); tree.ngOnInit(); tick(); expect(mockNavService.init_invisible_cache).toHaveBeenCalledTimes(0); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(0); expect(mockNavService.update_disabled_cache).toHaveBeenCalledTimes(0); tree.disabledChange.emit('mockNode' as any); tick(); expect(mockNavService.update_disabled_cache).toHaveBeenCalledTimes(1); expect(mockNavService.update_disabled_cache).toHaveBeenCalledWith('mockNode' as any); tree.nodeCollapsing.emit({ node: 'mockNode' as any } as any); tick(); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(1); expect(mockNavService.update_visible_cache).toHaveBeenCalledWith('mockNode' as any, false); tree.nodeExpanding.emit({ node: 'mockNode' as any } as any); tick(); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(2); expect(mockNavService.update_visible_cache).toHaveBeenCalledWith('mockNode' as any, true); tree.nodes = mockNodes; const mockNode = TreeTestFunctions.createNodeSpy({ expandedChange: new EventEmitter<void>(), closeAnimationDone: new EventEmitter<void>(), openAnimationDone: new EventEmitter<void>() }) as any; mockNodesArray.push( mockNode ); spyOnProperty(mockNodes, 'first', 'get').and.returnValue(mockNode); tree.ngAfterViewInit(); tick(); expect(mockNavService.init_invisible_cache).toHaveBeenCalledTimes(1); tree.nodes.first.expandedChange.emit(true); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(3); expect(mockNavService.update_visible_cache).toHaveBeenCalledWith(tree.nodes.first, true); tree.nodes.first.expandedChange.emit(false); expect(mockNavService.update_visible_cache).toHaveBeenCalledTimes(4); expect(mockNavService.update_visible_cache).toHaveBeenCalledWith(tree.nodes.first, false); (tree.nodes.changes as any).next(); tick(); expect(mockNavService.init_invisible_cache).toHaveBeenCalledTimes(2); tree.ngOnDestroy(); })); it('Should update delegate keyboard events to nav service', () => { const mockEvent: any = {}; tree.handleKeydown(mockEvent as any); expect(mockNavService.handleKeydown).toHaveBeenCalledWith(mockEvent as any); }); it('Should search through nodes and return expected value w/ `findNodes`', () => { tree.nodes = mockNodes; let id = 0; let itemRef = {} as any; mockNodesArray = TreeTestFunctions.createNodeSpies(0, 5); mockNodesArray.forEach(n => { itemRef = { id: id++ }; n.data = itemRef; }); expect(tree.findNodes(itemRef)).toEqual([mockNodesArray[mockNodesArray.length - 1]]); expect(tree.nodes.filter).toHaveBeenCalledTimes(1); expect(tree.findNodes(1, (p, n) => n.data.id === p)).toEqual([mockNodes.find(n => n.data.id === 1)]); expect(tree.nodes.filter).toHaveBeenCalledTimes(2); expect(tree.findNodes('Not found', (p, n) => n.data.id === p)).toEqual(null); expect(tree.nodes.filter).toHaveBeenCalledTimes(3); }); it('Should return only root level nodes w/ `rootNodes` accessor', () => { tree.nodes = mockNodes; const arr = []; for (let i = 0; i < 7; i++) { const level = i > 4 ? 1 : 0; arr.push({ level }); } mockNodesArray = [...arr]; expect(tree.rootNodes.length).toBe(5); mockNodesArray.forEach(n => { (n as any).level = 1; }); expect(tree.rootNodes.length).toBe(0); mockNodesArray.forEach(n => { (n as any).level = 0; }); expect(tree.rootNodes.length).toBe(7); tree.nodes = null; expect(tree.rootNodes).toBe(undefined); }); it('Should expandAll nodes nodes w/ proper methods', () => { tree.nodes = mockNodes; const customArrayParam = []; for (let i = 0; i < 5; i++) { const node = jasmine.createSpyObj('node', ['expand', 'collapse'], { _expanded: false, get expanded() { return this._expanded; }, set expanded(val: boolean) { this._expanded = val; } }); node.spyProp = spyOnProperty(node, 'expanded', 'set').and.callThrough(); mockNodesArray.push(node); if (i > 3) { customArrayParam.push(node); } } spyOn(mockNodesArray, 'forEach').and.callThrough(); tree.expandAll(); expect(mockNodesArray.forEach).toHaveBeenCalledTimes(1); mockNodesArray.forEach(n => { expect((n as any).spyProp).toHaveBeenCalledWith(true); expect((n as any).spyProp).toHaveBeenCalledTimes(1); }); tree.expandAll(customArrayParam); customArrayParam.forEach(n => { expect((n as any).spyProp).toHaveBeenCalledWith(true); expect((n as any).spyProp).toHaveBeenCalledTimes(2); }); }); it('Should collapseAll nodes nodes w/ proper methods', () => { tree.nodes = mockNodes; const customArrayParam = []; for (let i = 0; i < 5; i++) { const node = jasmine.createSpyObj('node', ['expand', 'collapse'], { _expanded: false, get expanded() { return this._expanded; }, set expanded(val: boolean) { this._expanded = val; } }); node.spyProp = spyOnProperty(node, 'expanded', 'set').and.callThrough(); mockNodesArray.push(node); if (i > 3) { customArrayParam.push(node); } } spyOn(mockNodesArray, 'forEach').and.callThrough(); tree.collapseAll(); expect(mockNodesArray.forEach).toHaveBeenCalledTimes(1); mockNodesArray.forEach(n => { expect((n as any).spyProp).toHaveBeenCalledWith(false); expect((n as any).spyProp).toHaveBeenCalledTimes(1); }); tree.collapseAll(customArrayParam); customArrayParam.forEach(n => { expect((n as any).spyProp).toHaveBeenCalledWith(false); expect((n as any).spyProp).toHaveBeenCalledTimes(2); }); }); it('Should deselectAll nodes w/ proper methond', () => { tree.nodes = mockNodes; tree.deselectAll(); expect(mockSelectionService.deselectNodesWithNoEvent).toHaveBeenCalledWith(undefined); const customParam = jasmine.createSpyObj<any>('nodes', ['toArray']); tree.deselectAll(customParam); expect(mockSelectionService.deselectNodesWithNoEvent).toHaveBeenCalledWith(customParam); }); }); describe('IgxTreeNodeComponent', () => { let mockTree: IgxTreeComponent; let mockCdr: ChangeDetectorRef; let mockAnimationService: AnimationService; beforeEach(() => { mockTree = jasmine.createSpyObj<any>('mockTree', ['findNodes'], { nodeCollapsing: jasmine.createSpyObj('spy', ['emit']), nodeExpanding: jasmine.createSpyObj('spy', ['emit']), nodeCollapsed: jasmine.createSpyObj('spy', ['emit']), nodeExpanded: jasmine.createSpyObj('spy', ['emit']), _displayDensity: DisplayDensity.comfortable, get displayDensity() { return this._displayDensity; } }); mockCdr = jasmine.createSpyObj<ChangeDetectorRef>('mockCdr', ['detectChanges', 'markForCheck'], {}); mockAnimationService = jasmine.createSpyObj<AnimationService>('mockAB', ['buildAnimation'], {}); }); it('Should call service expand/collapse methods when toggling state through `[expanded]` input', () => { const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); expect(mockTreeService.collapse).not.toHaveBeenCalled(); expect(mockTreeService.expand).not.toHaveBeenCalled(); expect(mockTree.nodeExpanded.emit).not.toHaveBeenCalled(); expect(mockTree.nodeCollapsed.emit).not.toHaveBeenCalled(); expect(mockTree.nodeExpanding.emit).not.toHaveBeenCalled(); expect(mockTree.nodeExpanded.emit).not.toHaveBeenCalled(); node.expanded = true; expect(mockTreeService.expand).toHaveBeenCalledTimes(1); expect(mockTreeService.expand).toHaveBeenCalledWith(node, false); node.expanded = false; expect(mockTreeService.collapse).toHaveBeenCalledTimes(1); expect(mockTreeService.collapse).toHaveBeenCalledWith(node); // events are not emitted when chainging state through input expect(mockTree.nodeExpanded.emit).not.toHaveBeenCalled(); expect(mockTree.nodeCollapsed.emit).not.toHaveBeenCalled(); expect(mockTree.nodeExpanding.emit).not.toHaveBeenCalled(); expect(mockTree.nodeExpanded.emit).not.toHaveBeenCalled(); }); it('Expand() should expand currently collapsing node', () => { mockTreeService = new IgxTreeService(); mockTreeService.register(mockTree); const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); mockTreeService.expandedNodes.add(node); mockTreeService.collapsingNodes.add(node); node.expand(); expect(mockTree.nodeExpanding.emit).toHaveBeenCalledTimes(1); }); it('Collapse() shouldn`t affect a currently collapsing node', () => { mockTreeService = new IgxTreeService(); mockTreeService.register(mockTree); const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); mockTreeService.expandedNodes.add(node); mockTreeService.collapsingNodes.add(node); node.collapse(); expect(mockTree.nodeCollapsing.emit).toHaveBeenCalledTimes(0); }); it('Should call service expand/collapse methods when calling API state methods', () => { mockTreeService = new IgxTreeService(); mockTreeService.register(mockTree); const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); node.expandedChange = jasmine.createSpyObj('emitter', ['emit']) const openAnimationSpy = spyOn(node, 'playOpenAnimation'); const closeAnimationSpy = spyOn(node, 'playCloseAnimation'); const mockObj = jasmine.createSpyObj<any>('mockElement', ['focus']); spyOn(mockTreeService, 'collapse').and.callThrough(); spyOn(mockTreeService, 'collapsing').and.callThrough(); spyOn(mockTreeService, 'expand').and.callThrough(); spyOn(node, 'expandedChange').and.callThrough(); const ingArgs = { owner: mockTree, cancel: false, node }; const edArgs = { owner: mockTree, node }; (node as any).childrenContainer = mockObj; expect(mockTreeService.collapse).not.toHaveBeenCalled(); expect(mockTreeService.expand).not.toHaveBeenCalled(); expect(mockTreeService.collapsing).not.toHaveBeenCalled(); expect(openAnimationSpy).not.toHaveBeenCalled(); expect(closeAnimationSpy).not.toHaveBeenCalled(); expect(mockCdr.markForCheck).not.toHaveBeenCalled(); expect(mockTreeService.collapsing).not.toHaveBeenCalled(); expect(mockTree.nodeExpanding.emit).not.toHaveBeenCalledWith(); expect(mockTree.nodeCollapsing.emit).not.toHaveBeenCalledWith(); expect(mockTree.nodeExpanded.emit).not.toHaveBeenCalledWith(); expect(mockTree.nodeCollapsed.emit).not.toHaveBeenCalledWith(); expect(node.expandedChange).not.toHaveBeenCalled(); node.ngOnInit(); node.expand(); expect(openAnimationSpy).toHaveBeenCalledWith(mockObj); expect(openAnimationSpy).toHaveBeenCalledTimes(1); expect(mockTree.nodeExpanded.emit).toHaveBeenCalledTimes(0); expect(mockTree.nodeExpanding.emit).toHaveBeenCalledWith(ingArgs); expect(mockTreeService.expand).toHaveBeenCalledWith(node, true); expect(mockTreeService.expand).toHaveBeenCalledTimes(1); node.openAnimationDone.emit(); expect(node.expandedChange.emit).toHaveBeenCalledTimes(1); expect(node.expandedChange.emit).toHaveBeenCalledWith(true); expect(mockTree.nodeExpanded.emit).toHaveBeenCalledTimes(1); expect(mockTree.nodeExpanded.emit).toHaveBeenCalledWith(edArgs); node.collapse(); expect(closeAnimationSpy).toHaveBeenCalledWith(mockObj); expect(closeAnimationSpy).toHaveBeenCalledTimes(1); expect(mockTree.nodeCollapsed.emit).toHaveBeenCalledTimes(0); expect(mockTree.nodeCollapsing.emit).toHaveBeenCalledWith(ingArgs); // collapse happens after animation finishes expect(mockTreeService.collapse).toHaveBeenCalledTimes(0); node.closeAnimationDone.emit(); expect(mockTreeService.collapse).toHaveBeenCalledTimes(1); expect(mockTreeService.collapse).toHaveBeenCalledWith(node); expect(node.expandedChange.emit).toHaveBeenCalledTimes(2); expect(node.expandedChange.emit).toHaveBeenCalledWith(false); expect(mockTree.nodeCollapsed.emit).toHaveBeenCalledTimes(1); expect(mockTree.nodeCollapsed.emit).toHaveBeenCalledWith(edArgs); spyOn(node, 'expand'); spyOn(node, 'collapse'); node.toggle(); expect(node.expand).toHaveBeenCalledTimes(1); expect(node.collapse).toHaveBeenCalledTimes(0); spyOn(mockTreeService, 'isExpanded').and.returnValue(true); node.toggle(); expect(node.expand).toHaveBeenCalledTimes(1); expect(node.collapse).toHaveBeenCalledTimes(1); }); it('Should properly get tree display density token', () => { const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); expect(node.isCosy).toBeFalse(); expect(node.isCompact).toBeFalse(); spyOnProperty(mockTree, 'displayDensity', 'get').and.returnValue(DisplayDensity.cosy); expect(node.isCosy).toBeTrue(); expect(node.isCompact).toBeFalse(); spyOnProperty(mockTree, 'displayDensity', 'get').and.returnValue(DisplayDensity.compact); expect(node.isCosy).toBeFalse(); expect(node.isCompact).toBeTrue(); }); it('Should have correct path to node, regardless if node has parent or not', () => { const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); expect(node.path).toEqual([node]); const childNode = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, node); expect(childNode.path).toEqual([node, childNode]); }); it('Should clear itself from selection service on destroy', () => { const node = new IgxTreeNodeComponent<any>(mockTree, mockSelectionService, mockTreeService, mockNavService, mockCdr, mockAnimationService, mockElementRef, null); node.ngOnDestroy(); expect(mockSelectionService.ensureStateOnNodeDelete).toHaveBeenCalledWith(node); }); }); describe('IgxTreeService', () => { it('Should properly register tree', () => { const service = new IgxTreeService(); expect((service as any).tree).toBe(undefined); const mockTree = jasmine.createSpyObj<any>('tree', ['findNodes']); service.register(mockTree); expect((service as any).tree).toBe(mockTree); }); it('Should keep a proper collection of expanded and collapsing nodes at all time, firing `expandedChange` when needed', () => { const service = new IgxTreeService(); const mockTree = jasmine.createSpyObj<any>('tree', ['findNodes'], { _singleBranchExpand: false, get singleBranchExpand(): boolean { return this._singleBranchExpand; }, set singleBranchExpand(val: boolean) { this._singleBranchExpand = val; } }); service.register(mockTree); spyOn(service.expandedNodes, 'add').and.callThrough(); spyOn(service.expandedNodes, 'delete').and.callThrough(); spyOn(service.collapsingNodes, 'add').and.callThrough(); spyOn(service.collapsingNodes, 'delete').and.callThrough(); expect(service.expandedNodes.size).toBe(0); expect(service.collapsingNodes.size).toBe(0); const mockNode = jasmine.createSpyObj<any>('node', ['collapse'], { expandedChange: jasmine.createSpyObj('emitter', ['emit']) }); service.expand(mockNode); expect(service.collapsingNodes.delete).toHaveBeenCalledWith(mockNode); expect(service.collapsingNodes.delete).toHaveBeenCalledTimes(1); expect(service.expandedNodes.add).toHaveBeenCalledWith(mockNode); expect(mockNode.expandedChange.emit).toHaveBeenCalledTimes(1); expect(mockNode.expandedChange.emit).toHaveBeenCalledWith(true); expect(service.expandedNodes.size).toBe(1); expect(mockNode.collapse).not.toHaveBeenCalled(); service.expand(mockNode); expect(service.collapsingNodes.delete).toHaveBeenCalledTimes(2); expect(mockNode.expandedChange.emit).toHaveBeenCalledTimes(1); expect(service.expandedNodes.size).toBe(1); service.collapse(mockNode); expect(mockNode.expandedChange.emit).toHaveBeenCalledTimes(2); expect(mockNode.expandedChange.emit).toHaveBeenCalledWith(false); expect(service.collapsingNodes.delete).toHaveBeenCalledWith(mockNode); expect(service.collapsingNodes.delete).toHaveBeenCalledTimes(3); expect(service.expandedNodes.delete).toHaveBeenCalledTimes(1); expect(service.expandedNodes.delete).toHaveBeenCalledWith(mockNode); expect(service.expandedNodes.size).toBe(0); service.collapse(mockNode); expect(mockNode.expandedChange.emit).toHaveBeenCalledTimes(2); expect(service.collapsingNodes.delete).toHaveBeenCalledTimes(4); expect(service.expandedNodes.delete).toHaveBeenCalledTimes(2); const mockArray = []; for (let i = 0; i < 5; i++) { const node = jasmine.createSpyObj('node', ['collapse'], { _expanded: false, get expanded() { return this._expanded; }, set expanded(val: boolean) { this._expanded = val; } }); node.spyProp = spyOnProperty(node, 'expanded', 'set').and.callThrough(); mockArray.push(node); } spyOn(mockTree, 'findNodes').and.returnValue(mockArray); spyOnProperty(mockTree, 'singleBranchExpand', 'get').and.returnValue(true); service.expand(mockNode); mockArray.forEach(n => { expect((n as any).spyProp).toHaveBeenCalledWith(false); expect(n.collapse).not.toHaveBeenCalled(); }); service.collapse(mockNode); service.expand(mockNode, true); mockArray.forEach(n => { expect(n.collapse).toHaveBeenCalled(); expect(n.collapse).toHaveBeenCalledTimes(1); }); expect(service.collapsingNodes.size).toBe(0); service.collapsing(mockNode); expect(service.collapsingNodes.size).toBe(1); service.collapse(mockNode); spyOnProperty(mockTree, 'singleBranchExpand', 'get').and.returnValue(true); spyOn(mockTree, 'findNodes').and.returnValue(null); service.expand(mockNode, true); expect(mockTree.findNodes).toHaveBeenCalledWith(mockNode, (service as any).siblingComparer); mockArray.forEach(n => { expect(n.collapse).toHaveBeenCalledTimes(1); }); }); }); }); describe('Rendering Tests', () => { let fix: ComponentFixture<IgxTreeSampleComponent>; let tree: IgxTreeComponent; beforeAll( waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxTreeSampleComponent ] }).compileComponents(); }) ); beforeEach(() => { fix = TestBed.createComponent<IgxTreeSampleComponent>(IgxTreeSampleComponent); fix.detectChanges(); tree = fix.componentInstance.tree; }); describe('General', () => { it('Should only render node children', () => { const treeEl: HTMLElement = fix.debugElement.queryAll(By.css(`.${TREE_ROOT_CLASS}`))[0].nativeElement; let childNodes = treeEl.children; expect(childNodes.length).toBe(5); for (let i = 0; i < childNodes.length; i++) { expect(childNodes.item(i).tagName === NODE_TAG); } fix.componentInstance.divChild = true; childNodes = treeEl.children; expect(childNodes.length).toBe(5); for (let i = 0; i < childNodes.length; i++) { expect(childNodes.item(i).tagName === NODE_TAG); } }); it('Should not render collapsed nodes', () => { let allNodes: DebugElement[] = fix.debugElement.queryAll(By.css(NODE_TAG)); expect(allNodes.length).toBe(5); tree.nodes.first.expanded = true; fix.detectChanges(); allNodes = fix.debugElement.queryAll(By.css(NODE_TAG)); expect(allNodes.length).toBe(10); const visibleNodes = tree.nodes.filter(n => allNodes.findIndex(e => e.nativeElement === n.nativeElement) > -1); visibleNodes.forEach(n => { expect(n.level === 0 || n.parentNode.expanded === true).toBeTruthy(); }); }); it('Should apply proper node classes depending on tree displayDenisty', () => { pending('Test not implemented'); }); it('Should do nothing when calling expand()/collapse() on expanded/collapsed node', fakeAsync(() => { const expandingSpy = spyOn(tree.nodeExpanding, 'emit').and.callThrough(); const collapsingSpy = spyOn(tree.nodeCollapsing, 'emit').and.callThrough(); tree.nodes.first.collapse(); expect(expandingSpy).not.toHaveBeenCalled(); tree.nodes.first.expanded = true; tree.nodes.first.expand(); expect(collapsingSpy).not.toHaveBeenCalled(); })); it('Should properly emit state toggle events', fakeAsync(() => { // node event spies const collapsingSpy = spyOn(tree.nodeCollapsing, 'emit').and.callThrough(); const expandingSpy = spyOn(tree.nodeExpanding, 'emit').and.callThrough(); const expandedSpy = spyOn(tree.nodeExpanded, 'emit').and.callThrough(); const collapsedSpy = spyOn(tree.nodeCollapsed, 'emit').and.callThrough(); expect(collapsingSpy).not.toHaveBeenCalled(); expect(expandingSpy).not.toHaveBeenCalled(); expect(expandedSpy).not.toHaveBeenCalled(); expect(collapsedSpy).not.toHaveBeenCalled(); tree.nodes.first.expand(); expect(expandingSpy).toHaveBeenCalledTimes(1); expect(collapsingSpy).not.toHaveBeenCalled(); expect(expandedSpy).not.toHaveBeenCalled(); expect(collapsedSpy).not.toHaveBeenCalled(); tick(); fix.detectChanges(); tick(); expect(expandingSpy).toHaveBeenCalledTimes(1); expect(expandedSpy).toHaveBeenCalledTimes(1); expect(collapsingSpy).not.toHaveBeenCalled(); expect(collapsedSpy).not.toHaveBeenCalled(); tree.nodes.first.collapse(); expect(expandingSpy).toHaveBeenCalledTimes(1); expect(expandedSpy).toHaveBeenCalledTimes(1); expect(collapsingSpy).toHaveBeenCalledTimes(1); expect(collapsedSpy).not.toHaveBeenCalled(); tick(); fix.detectChanges(); tick(); expect(expandingSpy).toHaveBeenCalledTimes(1); expect(expandedSpy).toHaveBeenCalledTimes(1); expect(collapsingSpy).toHaveBeenCalledTimes(1); expect(collapsedSpy).toHaveBeenCalledTimes(1); // cancel ingEvents const unsub$ = new Subject<void>(); tree.nodeExpanding.pipe(takeUntil(unsub$)).subscribe(e => { e.cancel = true; }); tree.nodes.first.expand(); expect(expandingSpy).toHaveBeenCalledTimes(2); expect(expandedSpy).toHaveBeenCalledTimes(1); tick(); fix.detectChanges(); tick(); expect(expandingSpy).toHaveBeenCalledTimes(2); expect(expandedSpy).toHaveBeenCalledTimes(1); tree.nodes.first.expanded = true; unsub$.next(); tree.nodeCollapsing.pipe(takeUntil(unsub$)).subscribe(e => { e.cancel = true; }); tree.nodes.first.collapse(); expect(collapsingSpy).toHaveBeenCalledTimes(2); expect(collapsedSpy).toHaveBeenCalledTimes(1); tick(); fix.detectChanges(); tick(); expect(collapsingSpy).toHaveBeenCalledTimes(2); expect(collapsedSpy).toHaveBeenCalledTimes(1); unsub$.next(); unsub$.complete(); })); it('Should collapse all sibling nodes when `singleBranchExpand` is set and node is toggled', fakeAsync(() => { pending('Causes jasmine to hang'); tree.rootNodes.forEach((n, index) => index > 0 ? n.expanded = true : n.expanded = false); fix.detectChanges(); expect(tree.nodes.filter(n => n.expanded).length).toBe(4); tree.singleBranchExpand = true; tree.rootNodes.forEach(n => { spyOn(n.expandedChange, 'emit').and.callThrough(); }); const collapsingSpy = spyOn(tree.nodeCollapsing, 'emit').and.callThrough(); const expandingSpy = spyOn(tree.nodeExpanding, 'emit').and.callThrough(); const expandedSpy = spyOn(tree.nodeCollapsed, 'emit').and.callThrough(); const collapsedSpy = spyOn(tree.nodeExpanded, 'emit').and.callThrough(); // should not emit event when nodes are toggled through input tree.rootNodes[0].expanded = true; fix.detectChanges(); tree.rootNodes.forEach(n => { expect(n.expandedChange.emit).toHaveBeenCalled(); }); expect(expandingSpy).not.toHaveBeenCalled(); expect(collapsingSpy).not.toHaveBeenCalled(); expect(expandedSpy).not.toHaveBeenCalled(); expect(collapsedSpy).not.toHaveBeenCalled(); expect(tree.nodes.filter(n => n.expanded).length).toBe(1); const expandedArgs = { node: tree.rootNodes[1], owner: tree }; const collapsedArgs = { node: tree.rootNodes[0], owner: tree }; tree.rootNodes[1].expand(); tick(); fix.detectChanges(); expect(expandingSpy).toHaveBeenCalledTimes(1); expect(expandingSpy).toHaveBeenCalledWith(Object.assign({}, expandedArgs, { cancel: false })); expect(collapsingSpy).toHaveBeenCalledTimes(1); expect(expandingSpy).toHaveBeenCalledWith(Object.assign({}, collapsedArgs, { cancel: false })); expect(expandedSpy).toHaveBeenCalledTimes(1); expect(expandedSpy).toHaveBeenCalledWith(expandedArgs); expect(collapsedSpy).toHaveBeenCalledTimes(1); expect(collapsedSpy).toHaveBeenCalledWith(collapsedArgs); tree.singleBranchExpand = false; fix.detectChanges(); const deepNode = tree.findNodes('2-1', (_id: '2-1', n: IgxTreeNodeComponent<any>) => n.data.id === '2-1')[0]; expect(deepNode).not.toBeNull(); fix.componentInstance.expandToNode(deepNode); const siblingNodes = tree.findNodes(deepNode, (tn: IgxTreeNodeComponent<any>, n: IgxTreeNodeComponent<any>) => n.level === tn.level && n.parentNode === tn.parentNode ); expect(siblingNodes.length).toBe(5); siblingNodes.forEach(n => n.expanded = true); fix.detectChanges(); expect(tree.nodes.filter(e => e.expanded).length).toBe(7); siblingNodes[0].expanded = false; fix.detectChanges(); expect(tree.nodes.filter(e => e.expanded).length).toBe(6); tree.singleBranchExpand = true; siblingNodes[0].expanded = true; fix.detectChanges(); expect(tree.nodes.filter(e => e.expanded).length).toBe(3); const nodeLevels = tree.nodes.filter(n => n.expanded).map(n => n.level); expect(nodeLevels).toEqual([0, 1, 2]); })); }); describe('ARIA', () => { it('Should render proper roles for tree and nodes', () => { pending('Test not implemented'); }); it('Should render proper label for expand/collapse indicator, depending on node state', () => { pending('Test not implemented'); }); it('Should render proper roles for nodes containing link children', () => { pending('Test not implemented'); }); }); }); }); @Component({ template: ` <igx-tree> <igx-tree-node [(expanded)]="node.expanded" [data]="node" *ngFor="let node of data"> {{ node.label }} <igx-tree-node [(expanded)]="child.expanded" [data]="child" *ngFor="let child of node.children"> {{ child.label }} <igx-tree-node [(expanded)]="leafChild.expanded" [data]="leafChild" *ngFor="let leafChild of child.children"> {{ leafChild.label }} </igx-tree-node> </igx-tree-node> </igx-tree-node> <div *ngIf="divChild"></div> </igx-tree> `, standalone: true, imports: [IgxTreeComponent, NgIf, IgxTreeNodeComponent, NgFor] }) class IgxTreeSampleComponent { @ViewChild(IgxTreeComponent) public tree: IgxTreeComponent; public divChild = true; public data = createHierarchicalData(5, 3); public expandToNode(node: IgxTreeNodeComponent<any>): void { node.path.forEach(n => n.expanded = true); } } class MockDataItem { public selected = false; public expanded = false; public children: MockDataItem[] = []; constructor(public id: string, public label: string) { } } const createHierarchicalData = (siblings: number, depth: number): MockDataItem[] => { let id = 0; const returnArr = []; for (let i = 0; i < siblings; i++) { const item = new MockDataItem(`${depth}-${id}`, `Label ${depth}-${id}`); id++; returnArr.push(item); if (depth > 0) { item.children = createHierarchicalData(siblings, depth - 1); } } return returnArr; };