UNPKG

igniteui-angular-sovn

Version:

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

776 lines (642 loc) 41.1 kB
import { configureTestSuite } from '../test-utils/configure-suite'; import { waitForAsync, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { IgxTreeNavigationComponent, IgxTreeScrollComponent } from './tree-samples.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { UIInteractions, wait } from '../test-utils/ui-interactions.spec'; import { IgxTreeNavigationService } from './tree-navigation.service'; import { ElementRef, EventEmitter } from '@angular/core'; import { IgxTreeSelectionService } from './tree-selection.service'; import { TreeTestFunctions } from './tree-functions.spec'; import { IgxTreeService } from './tree.service'; import { IgxTreeComponent } from './tree.component'; import { IgxTree, IgxTreeNode, IgxTreeSelectionType } from './common'; import { IgxTreeNodeComponent } from './tree-node/tree-node.component'; describe('IgxTree - Navigation #treeView', () => { configureTestSuite(); describe('Navigation - UI Tests', () => { let fix; let tree: IgxTreeComponent; beforeAll(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxTreeNavigationComponent, IgxTreeScrollComponent ] }).compileComponents(); })); beforeEach(fakeAsync(() => { fix = TestBed.createComponent(IgxTreeNavigationComponent); fix.detectChanges(); tree = fix.componentInstance.tree; })); describe('UI Interaction tests - None', () => { beforeEach(fakeAsync(() => { tree.selection = IgxTreeSelectionType.None; fix.detectChanges(); })); it('Initial tab index without focus SHOULD be 0 for all nodes and active input should be set correctly', () => { const visibleNodes = (tree as any).navService.visibleChildren; visibleNodes.forEach(node => { expect(node.header.nativeElement.tabIndex).toEqual(0); }); // Should render node with `node.active === true`, set through input, as active in the tree expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); expect(tree.nodes.toArray()[17].active).toBeTruthy(); tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); visibleNodes.forEach(node => { if (node !== tree.nodes.first) { expect(node.header.nativeElement.tabIndex).toEqual(-1); } else { expect(node.header.nativeElement.tabIndex).toEqual(0); } }); }); it('Should focus/activate correct node on ArrowDown/ArrowUp (+ Ctrl) key pressed', () => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); // ArrowDown + Ctrl should only focus the next visible node UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement, true, false, false, true); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); // ArrowDown should focus and activate the next visible node UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[28]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[28]); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.toArray()[28]); // ArrowUp + Ctrl should only focus the previous visible node UIInteractions.triggerKeyDownEvtUponElem('arrowup', tree.nodes.toArray()[28].nativeElement, true, false, false, true); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[28]); // ArrowUp should focus and activate the previous visible node UIInteractions.triggerKeyDownEvtUponElem('arrowup', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); }); it('Should focus and activate the first/last visible node on Home/End key press', () => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); tree.nodes.first.expand(); fix.detectChanges(); tree.nodes.toArray()[2].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('home', tree.nodes.toArray()[2].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); UIInteractions.triggerKeyDownEvtUponElem('end', tree.nodes.first.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.last); expect((tree as any).navService.activeNode).toEqual(tree.nodes.last); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.last); }); it('Should collapse/navigate to correct node on Arrow left key press', fakeAsync(() => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); // If node is collapsed and has no parents the focus and activation should not be moved on Arrow left key press tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); tick(); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowleft', tree.nodes.first.nativeElement); tick(); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); // If node is collapsed and has parent the focus and activation should be moved to the parent node on Arrow left key press tree.nodes.first.expand(); tick(); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement); tick(); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowleft', tree.nodes.first.nativeElement); tick(); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); // If node is expanded the node should collapse on Arrow left key press UIInteractions.triggerKeyDownEvtUponElem('arrowleft', tree.nodes.first.nativeElement); tick(); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.nodes.first.expanded).toBeFalsy(); })); it('Should expand/navigate to correct node on Arrow right key press', () => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); // If node has no children the focus and activation should not be moved on Arrow right key press tree.nodes.last.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.last.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.last); expect((tree as any).navService.activeNode).toEqual(tree.nodes.last); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.last); // If node is collapsed and has children the node should be expanded on Arrow right key press UIInteractions.triggerKeyDownEvtUponElem('home', tree.nodes.last.nativeElement); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.first.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.first); expect(tree.nodes.first.expanded).toBeTruthy(); // If node is expanded and has children the focus and activation should be moved to the first child on Arrow right key press UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.first.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[1]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[1]); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.toArray()[1]); }); it('Pressing Asterisk on focused node should expand all expandable nodes in the same group', () => { tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.first.nativeElement); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.first.nativeElement); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('*', tree.nodes.first.nativeElement); fix.detectChanges(); expect(tree.nodes.toArray()[2].expanded).toBeTruthy(); expect(tree.nodes.toArray()[12].expanded).toBeTruthy(); }); it('Pressing Enter should activate the focused node and not prevent the keydown event`s deafault behavior', () => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement, true, false, false, true); fix.detectChanges(); const mockEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }); spyOn(mockEvent, 'preventDefault'); tree.nodes.toArray()[17].nativeElement.dispatchEvent(mockEvent); expect(mockEvent.preventDefault).not.toHaveBeenCalled(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.toArray()[17]); }); it('Should correctly set node`s selection state on Space key press', () => { spyOn(tree.activeNodeChanged, 'emit').and.callThrough(); // Space on None Selection Mode tree.selection = 'None'; tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement, true, false, false, true); fix.detectChanges(); spyOn((tree as any).selectionService, 'selectNode').and.callThrough(); spyOn((tree as any).selectionService, 'deselectNode').and.callThrough(); spyOn((tree as any).selectionService, 'selectMultipleNodes').and.callThrough(); UIInteractions.triggerKeyDownEvtUponElem('space', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect(tree.nodes.toArray()[17].selected).toBeFalsy(); expect((tree as any).selectionService.selectNode).toHaveBeenCalledTimes(0); expect((tree as any).selectionService.deselectNode).toHaveBeenCalledTimes(0); expect((tree as any).selectionService.selectMultipleNodes).toHaveBeenCalledTimes(0); expect((tree as any).navService.activeNode).not.toEqual(tree.nodes.toArray()[17]); // Space for select tree.selection = 'BiState'; UIInteractions.triggerKeyDownEvtUponElem('space', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect(tree.nodes.toArray()[17].selected).toBeTruthy(); expect((tree as any).selectionService.selectNode).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.deselectNode).toHaveBeenCalledTimes(0); expect((tree as any).selectionService.selectMultipleNodes).toHaveBeenCalledTimes(0); expect((tree as any).selectionService.selectNode).toHaveBeenCalledWith(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); expect(tree.activeNodeChanged.emit).toHaveBeenCalledWith(tree.nodes.toArray()[17]); // Space with Shift key UIInteractions.triggerKeyDownEvtUponElem('arrowup', tree.nodes.toArray()[17].nativeElement, true, false, false, true); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('space', tree.nodes.first.nativeElement, true, false, true, false); fix.detectChanges(); expect(tree.nodes.first.selected).toBeTruthy(); expect(tree.nodes.toArray()[17].selected).toBeTruthy(); expect((tree as any).selectionService.selectNode).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.deselectNode).toHaveBeenCalledTimes(0); expect((tree as any).selectionService.selectMultipleNodes).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.selectMultipleNodes).toHaveBeenCalledWith(tree.nodes.first); expect((tree as any).navService.activeNode).toEqual(tree.nodes.first); // Space for deselect UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.first.nativeElement, true, false, false, true); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('space', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect(tree.nodes.toArray()[17].selected).toBeFalsy(); expect((tree as any).selectionService.selectNode).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.deselectNode).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.selectMultipleNodes).toHaveBeenCalledTimes(1); expect((tree as any).selectionService.deselectNode).toHaveBeenCalledWith(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); }); }); describe('UI Interaction tests - Scroll to focused node', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(IgxTreeScrollComponent); fix.detectChanges(); tree = fix.componentInstance.tree; tree.selection = IgxTreeSelectionType.None; fix.detectChanges(); })); it('The tree container should be scrolled so that the focused node is in view', fakeAsync(() => { // set another node as active element, expect node to be in view tick(); const treeElement = tree.nativeElement; let targetNode = tree.nodes.last; let nodeElement = targetNode.nativeElement; let nodeRect = nodeElement.getBoundingClientRect(); let treeRect = treeElement.getBoundingClientRect(); // expect node is in view expect((treeRect.top > nodeRect.top) || (treeRect.bottom < nodeRect.bottom)).toBeFalsy(); targetNode = tree.nodes.first; nodeElement = targetNode?.header.nativeElement; targetNode.active = true; tick(); fix.detectChanges(); nodeRect = nodeElement.getBoundingClientRect(); treeRect = treeElement.getBoundingClientRect(); expect(treeElement.scrollTop).toBe(0); expect((treeRect.top > nodeRect.top) || (treeRect.bottom < nodeRect.bottom)).toBeFalsy(); let lastNodeIndex = 0; let nodeIndex = 0; for (let i = 0; i < 150; i++) { while (nodeIndex === lastNodeIndex) { nodeIndex = Math.floor(Math.random() * tree.nodes.length); } lastNodeIndex = nodeIndex; targetNode = tree.nodes.toArray()[nodeIndex]; nodeElement = targetNode.header.nativeElement; targetNode.active = true; tick(); fix.detectChanges(); tick(); fix.detectChanges(); // recalculate rectangles treeRect = treeElement.getBoundingClientRect(); nodeRect = targetNode.header.nativeElement.getBoundingClientRect(); expect((treeRect.top <= nodeRect.top) && (treeRect.bottom >= nodeRect.bottom)).toBeTruthy(); } })); }); describe('UI Interaction tests - Disabled Nodes', () => { beforeEach(fakeAsync(() => { tree.selection = IgxTreeSelectionType.None; fix.detectChanges(); fix.componentInstance.isDisabled = true; fix.detectChanges(); })); it('TabIndex on disabled node should be -1', () => { expect(tree.nodes.last.header.nativeElement.tabIndex).toEqual(-1); }); it('Should focus and activate the first/last enabled and visible node on Home/End key press', () => { tree.nodes.first.disabled = true; fix.detectChanges(); tree.nodes.toArray()[38].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('home', tree.nodes.first.nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); UIInteractions.triggerKeyDownEvtUponElem('end', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[38]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[38]); }); it('Should navigate to correct node on Arrow left/right key press', () => { // If a node is collapsed and has a disabled parent the focus and activation // should not be moved from the node on Arrow left key press tree.nodes.first.expanded = true; fix.detectChanges(); tree.nodes.toArray()[2].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); tree.nodes.first.disabled = true; fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowleft', tree.nodes.toArray()[2].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[2]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[2]); // If a node is expanded and all its children are disabled the focus and activation // should not be moved from the node on Arrow right key press UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.toArray()[2].nativeElement); fix.detectChanges(); tree.nodes.toArray()[2]._children.forEach(child => { child.disabled = true; }); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.toArray()[2].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[2]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[2]); // If a node is expanded and has enabled children the focus and activation // should be moved to the first enabled child on Arrow right key press tree.nodes.toArray()[4].disabled = false; fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowright', tree.nodes.toArray()[2].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[4]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[4]); }); it('Should navigate to the right node on Arrow up/down key press', () => { tree.nodes.toArray()[28].disabled = true; tree.nodes.toArray()[38].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowup', tree.nodes.toArray()[38].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[17]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[17]); UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.toArray()[17].nativeElement); fix.detectChanges(); expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[38]); expect((tree as any).navService.activeNode).toEqual(tree.nodes.toArray()[38]); }); it('Pressing Asterisk on focused node should expand only the enabled and expandable nodes in the same group', () => { tree.nodes.toArray()[17].disabled = true; tree.nodes.first.header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('*', tree.nodes.first.nativeElement); fix.detectChanges(); expect(tree.nodes.toArray()[17].expanded).toBeFalsy(); expect(tree.nodes.first.expanded).toBeTruthy(); expect(tree.nodes.toArray()[28].expanded).toBeTruthy(); expect(tree.nodes.toArray()[38].expanded).toBeTruthy(); expect(tree.nodes.last.expanded).toBeFalsy(); }); }); describe('UI Interaction tests - igxTreeNodeLink', () => { beforeEach(fakeAsync(() => { tree.selection = IgxTreeSelectionType.None; fix.detectChanges(); fix.componentInstance.showNodesWithDirective = true; fix.detectChanges(); })); it('Nodes with igxTreeNodeLink should have tabIndex -1', () => { expect(tree.nodes.toArray()[41].header.nativeElement.tabIndex).toEqual(-1); expect(tree.nodes.last.header.nativeElement.tabIndex).toEqual(-1); }); it('When focus falls on link with directive, document.activeElement should be link with directive', fakeAsync(() => { tree.nodes.toArray()[40].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('arrowdown', tree.nodes.toArray()[40].nativeElement); fix.detectChanges(); tick(); fix.detectChanges(); const linkNode = tree.nodes.toArray()[41].linkChildren.first.nativeElement; expect(linkNode.tabIndex).toEqual(0); // When focus falls on link with directive, parent has focused class (nav.service.focusedNode === link.parent) expect((tree as any).navService.focusedNode).toEqual(tree.nodes.toArray()[41]); expect(document.activeElement).toEqual(linkNode); })); it('Link with passed parent in ng-template outisde of node parent has proper ref to parent', () => { tree.nodes.toArray()[40].header.nativeElement.dispatchEvent(new Event('pointerdown')); fix.detectChanges(); tree.nodes.toArray()[46].expanded = true; fix.detectChanges(); UIInteractions.triggerKeyDownEvtUponElem('end', tree.nodes.toArray()[46].nativeElement); fix.detectChanges(); expect(tree.nodes.last.registeredChildren[0].tabIndex).toEqual(0); }); }); }); describe('IgxTreeNavigationSerivce - Unit Tests', () => { let selectionService: IgxTreeSelectionService; let treeService: IgxTreeService; let navService: IgxTreeNavigationService; let mockTree: IgxTree; let mockEmitter: EventEmitter<IgxTreeNode<any>>; let mockNodesLevel1: IgxTreeNodeComponent<any>[]; let mockNodesLevel2_1: IgxTreeNodeComponent<any>[]; let mockNodesLevel2_2: IgxTreeNodeComponent<any>[]; let mockNodesLevel3_1: IgxTreeNodeComponent<any>[]; let mockNodesLevel3_2: IgxTreeNodeComponent<any>[]; let allNodes: IgxTreeNodeComponent<any>[]; const mockQuery1: any = {}; const mockQuery2: any = {}; const mockQuery3: any = {}; const mockQuery4: any = {}; const mockQuery5: any = {}; const mockQuery6: any = {}; beforeEach(() => { selectionService = new IgxTreeSelectionService(); treeService = new IgxTreeService(); navService?.ngOnDestroy(); navService = new IgxTreeNavigationService(treeService, selectionService); mockNodesLevel1 = TreeTestFunctions.createNodeSpies(0, 3, null, [mockQuery2, mockQuery3, []], [mockQuery6, mockQuery3, []]); mockNodesLevel2_1 = TreeTestFunctions.createNodeSpies(1, 2, mockNodesLevel1[0], [mockQuery4, mockQuery5], [mockQuery4, mockQuery5]); mockNodesLevel2_2 = TreeTestFunctions.createNodeSpies(1, 1, mockNodesLevel1[1], [[]]); mockNodesLevel3_1 = TreeTestFunctions.createNodeSpies(2, 2, mockNodesLevel2_1[0], [[], []]); mockNodesLevel3_2 = TreeTestFunctions.createNodeSpies(2, 2, mockNodesLevel2_1[1], [[], []]); allNodes = [ mockNodesLevel1[0], mockNodesLevel2_1[0], ...mockNodesLevel3_1, mockNodesLevel2_1[1], ...mockNodesLevel3_2, mockNodesLevel1[1], ...mockNodesLevel2_2, mockNodesLevel1[2] ]; Object.assign(mockQuery1, TreeTestFunctions.createQueryListSpy(allNodes)); Object.assign(mockQuery2, TreeTestFunctions.createQueryListSpy(mockNodesLevel2_1)); Object.assign(mockQuery3, TreeTestFunctions.createQueryListSpy(mockNodesLevel2_2)); Object.assign(mockQuery4, TreeTestFunctions.createQueryListSpy(mockNodesLevel3_1)); Object.assign(mockQuery5, TreeTestFunctions.createQueryListSpy(mockNodesLevel3_2)); Object.assign(mockQuery6, TreeTestFunctions.createQueryListSpy([ mockNodesLevel2_1[0], ...mockNodesLevel3_1, mockNodesLevel2_1[1], ...mockNodesLevel3_2 ])); }); describe('IgxNavigationService', () => { beforeEach(() => { mockEmitter = jasmine.createSpyObj('emitter', ['emit']); mockTree = jasmine.createSpyObj('tree', [''], { selection: IgxTreeSelectionType.BiState, activeNodeChanged: mockEmitter, nodes: mockQuery1 }); navService.register(mockTree); }); it('Should properly register the specified tree', () => { navService = new IgxTreeNavigationService(treeService, selectionService); expect((navService as any).tree).toBeFalsy(); navService.register(mockTree); expect((navService as any).tree).toEqual(mockTree); }); it('Should properly calculate VisibleChildren collection', () => { navService.init_invisible_cache(); expect(navService.visibleChildren.length).toEqual(3); (Object.getOwnPropertyDescriptor(allNodes[0], 'expanded').get as jasmine.Spy<any>) .and.returnValue(true); navService.init_invisible_cache(); expect(navService.visibleChildren.length).toEqual(5); (Object.getOwnPropertyDescriptor(allNodes[0], 'disabled').get as jasmine.Spy<any>) .and.returnValue(true); navService.update_disabled_cache(allNodes[0]); expect(navService.visibleChildren.length).toEqual(4); allNodes.forEach(e => { (Object.getOwnPropertyDescriptor(e, 'disabled').get as jasmine.Spy<any>) .and.returnValue(true); navService.update_disabled_cache(e); }); expect(navService.visibleChildren.length).toEqual(0); mockTree.nodes = null; expect(navService.visibleChildren.length).toEqual(0); }); it('Should set activeNode and focusedNode correctly', () => { const someNode = { tabIndex: null, header: { nativeElement: jasmine.createSpyObj('nativeElement', ['focus']) } } as any; const someNode2 = { tabIndex: null, header: { nativeElement: jasmine.createSpyObj('nativeElement', ['focus']) } } as any; navService.focusedNode = someNode; expect(someNode.header.nativeElement.focus).toHaveBeenCalled(); expect(someNode.tabIndex).toBe(0); navService.setFocusedAndActiveNode(someNode2); expect(navService.activeNode).toEqual(someNode2); expect(someNode2.header.nativeElement.focus).toHaveBeenCalled(); expect(someNode2.tabIndex).toBe(0); expect(someNode.tabIndex).toBe(-1); expect(mockTree.activeNodeChanged.emit).toHaveBeenCalledTimes(1); expect(mockTree.activeNodeChanged.emit).toHaveBeenCalledWith(someNode2); // do not change active node when call w/ same node // navService.focusedNode = navService.focusedNode; expect(mockTree.activeNodeChanged.emit).toHaveBeenCalledTimes(1); // handle call w/ null navService.focusedNode = null; expect(someNode2.tabIndex).toBe(-1); expect(mockTree.activeNodeChanged.emit).toHaveBeenCalledTimes(1); }); it('Should traverse visibleChildren on handleKeyDown', async () => { navService.init_invisible_cache(); const mockEvent1 = new KeyboardEvent('keydown', { key: 'arrowdown', bubbles: true }); spyOn(mockEvent1, 'preventDefault'); spyOn(navService, 'handleKeydown').and.callThrough(); navService.focusedNode = mockNodesLevel1[0]; navService.handleKeydown(mockEvent1); expect(mockEvent1.preventDefault).toHaveBeenCalled(); expect(navService.handleKeydown).toHaveBeenCalledTimes(1); expect(navService.focusedNode).toEqual(mockNodesLevel1[1]); const mockEvent2 = new KeyboardEvent('keydown', { key: 'arrowup', bubbles: true }); spyOn(mockEvent2, 'preventDefault'); navService.handleKeydown(mockEvent2); expect(mockEvent2.preventDefault).toHaveBeenCalled(); expect(navService.handleKeydown).toHaveBeenCalledTimes(2); expect(navService.focusedNode).toEqual(mockNodesLevel1[0]); const mockEvent3 = new KeyboardEvent('keydown', { key: 'arrowdown', bubbles: true, repeat: true }); spyOn(mockEvent3, 'preventDefault'); // when event is repeated, prevent default and wait navService.handleKeydown(mockEvent3); expect(navService.handleKeydown).toHaveBeenCalledTimes(3); expect(mockEvent3.preventDefault).toHaveBeenCalled(); // when event is repeating, node does not change immediately expect(navService.focusedNode).toEqual(mockNodesLevel1[0]); await wait(1); expect(navService.focusedNode).toEqual(mockNodesLevel1[1]); // does nothing if there is no focused node navService.focusedNode = null; const mockEvent4 = new KeyboardEvent('keydown', { key: 'arrowdown', bubbles: true, repeat: false }); spyOn(mockEvent4, 'preventDefault'); navService.handleKeydown(mockEvent4); expect(mockEvent4.preventDefault).not.toHaveBeenCalled(); // do not move focused node if on last node navService.focusedNode = allNodes[allNodes.length - 1]; navService.handleKeydown(mockEvent4); expect(navService.focusedNode).toEqual(allNodes[allNodes.length - 1]); }); it('Should update visible children on all relevant tree events', () => { const mockTreeService = jasmine.createSpyObj<IgxTreeService>('mockSelection', ['register', 'collapse', 'expand', 'collapsing'], { collapsingNodes: jasmine.createSpyObj<Set<IgxTreeNodeComponent<any>>>('mockCollpasingSet', ['add', 'delete', 'has'], { size: 0 }), expandedNodes: jasmine.createSpyObj<Set<IgxTreeNodeComponent<any>>>('mockExpandedSet', ['add', 'delete', 'has'], { size: 0 }), }); const mockElementRef = jasmine.createSpyObj<ElementRef>('mockElement', ['nativeElement'], { nativeElement: jasmine.createSpyObj<HTMLElement>('mockElement', ['focus'], { clientHeight: 300, scrollHeight: 300 }) }); const mockSelectionService = jasmine.createSpyObj<IgxTreeSelectionService>('mockSelection', ['selectNodesWithNoEvent', 'selectMultipleNodes', 'deselectNode', 'selectNode', 'register']); const nav = new IgxTreeNavigationService(mockTreeService, mockSelectionService); const lvl1Nodes = TreeTestFunctions.createNodeSpies(0, 5); const mockQuery = TreeTestFunctions.createQueryListSpy(lvl1Nodes); Object.assign(mockQuery, { changes: new EventEmitter<any>() }); spyOn(nav, 'init_invisible_cache'); spyOn(nav, 'update_disabled_cache'); spyOn(nav, 'update_visible_cache'); spyOn(nav, 'register'); const tree = new IgxTreeComponent(nav, mockSelectionService, mockTreeService, mockElementRef); tree.nodes = mockQuery; expect(nav.register).toHaveBeenCalledWith(tree); expect(nav.init_invisible_cache).not.toHaveBeenCalled(); expect(nav.update_disabled_cache).not.toHaveBeenCalled(); expect(nav.update_visible_cache).not.toHaveBeenCalled(); // not initialized tree.ngOnInit(); // manual call expect(nav.init_invisible_cache).not.toHaveBeenCalled(); expect(nav.update_disabled_cache).not.toHaveBeenCalled(); expect(nav.update_visible_cache).not.toHaveBeenCalled(); // nav service will now be updated after any of the following are emitted const emitNode = tree.nodes.first; tree.disabledChange.emit(emitNode); expect(nav.init_invisible_cache).not.toHaveBeenCalled(); expect(nav.update_disabled_cache).toHaveBeenCalledTimes(1); expect(nav.update_visible_cache).toHaveBeenCalledTimes(0); tree.disabledChange.emit(emitNode); expect(nav.update_disabled_cache).toHaveBeenCalledTimes(2); tree.nodeCollapsing.emit({ node: emitNode, owner: tree, cancel: false }); expect(nav.update_visible_cache).toHaveBeenCalledTimes(1); tree.nodeExpanding.emit({ node: emitNode, owner: tree, cancel: false }); expect(nav.update_visible_cache).toHaveBeenCalledTimes(2); // attach emitters to mock children lvl1Nodes.forEach(e => { e.expandedChange = new EventEmitter<boolean>(); e.openAnimationDone = new EventEmitter(); e.closeAnimationDone = new EventEmitter(); }); tree.ngAfterViewInit(); // inits cache on tree.ngAfterViewInit(); expect(nav.init_invisible_cache).toHaveBeenCalledTimes(1); // init cache when tree nodes collection changes; (tree.nodes as any).changes.emit(); expect(nav.init_invisible_cache).toHaveBeenCalledTimes(2); emitNode.expandedChange.emit(true); expect(nav.update_visible_cache).toHaveBeenCalledTimes(3); emitNode.expandedChange.emit(false); expect(nav.update_visible_cache).toHaveBeenCalledTimes(4); nav.ngOnDestroy(); tree.ngOnDestroy(); }); }); }); });