UNPKG

igniteui-angular-sovn

Version:

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

1,056 lines 50.8 kB
import { configureTestSuite } from '../../test-utils/configure-suite';
import { TestBed, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { Component, ViewChild, DebugElement} from '@angular/core';
import { IgxChildGridRowComponent, IgxHierarchicalGridComponent } from './hierarchical-grid.component';
import { wait, UIInteractions, waitForSelectionChange } from '../../test-utils/ui-interactions.spec';
import { IgxRowIslandComponent } from './row-island.component';
import { By } from '@angular/platform-browser';
import { IgxHierarchicalRowComponent } from './hierarchical-row.component';
import { clearGridSubs, setupHierarchicalGridScrollDetection } from '../../test-utils/helper-utils.spec';
import { GridFunctions } from '../../test-utils/grid-functions.spec';
import { IgxGridCellComponent } from '../cell.component';
import { IGridCellEventArgs, IgxColumnComponent } from '../public_api';

const DEBOUNCE_TIME = 30;
const GRID_CONTENT_CLASS = '.igx-grid__tbody-content';
const GRID_FOOTER_CLASS = '.igx-grid__tfoot';

describe('IgxHierarchicalGrid Navigation', () => {
    let fixture;
    let hierarchicalGrid: IgxHierarchicalGridComponent;
    let baseHGridContent: DebugElement;
    configureTestSuite();

    beforeAll(waitForAsync(() => {
        TestBed.configureTestingModule({
            imports: [
                NoopAnimationsModule,
                IgxHierarchicalGridTestBaseComponent,
                IgxHierarchicalGridTestComplexComponent,
                IgxHierarchicalGridMultiLayoutComponent,
                IgxHierarchicalGridSmallerChildComponent
            ]
        }).compileComponents();
    }));

    describe('IgxHierarchicalGrid Basic Navigation #hGrid', () => {

        beforeEach(waitForAsync(() => {
            fixture = TestBed.createComponent(IgxHierarchicalGridTestBaseComponent);
            fixture.detectChanges();
            hierarchicalGrid = fixture.componentInstance.hgrid;
            setupHierarchicalGridScrollDetection(fixture, hierarchicalGrid);
            baseHGridContent = GridFunctions.getGridContent(fixture);
            GridFunctions.focusFirstCell(fixture, hierarchicalGrid);
        }));

        afterEach(() => {
            clearGridSubs();
        });

        // simple tests
        it('should allow navigating down from parent row into child grid.', async () => {
            hierarchicalGrid.expandChildren = false;
            hierarchicalGrid.height = '600px';
            hierarchicalGrid.width = '800px';
            fixture.componentInstance.rowIsland.height = '350px';
            fixture.detectChanges();
            await wait();

            // expand row
            const row1 = hierarchicalGrid.dataRowList.first as IgxHierarchicalRowComponent;
            UIInteractions.simulateClickAndSelectEvent(row1.expander);
            fixture.detectChanges();
            await wait();

            // activate cell
            const fCell = hierarchicalGrid.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, fCell);

            // arrow down
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);
            fixture.detectChanges();

            // verify selection in child.
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.value).toEqual(0);
            expect(selectedCell.column.field).toMatch('ID');
        });

        it('should allow navigating up from child row into parent grid.', () => {
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childFirstCell =  childGrid.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, childFirstCell);

            // arrow up in child
            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, false);
            fixture.detectChanges();

            // verify selection in parent.
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.value).toEqual(0);
            expect(selectedCell.column.field).toMatch('ID');
        });

        it('should allow navigating down in child grid when child grid selected cell moves outside the parent view port.', async () => {
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childCell =  childGrid.dataRowList.toArray()[3].cells.first;
            GridFunctions.focusCell(fixture, childCell);

            // arrow down in child
            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait();
            // parent should scroll down so that cell in child is in view.
            const selectedCell = fixture.componentInstance.selectedCell;
            const selectedCellElem = childGrid.gridAPI.get_cell_by_index(selectedCell.row.index, selectedCell.column.field) as IgxGridCellComponent;
            const gridOffsets = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect();
            const rowOffsets = selectedCellElem.intRow.nativeElement.getBoundingClientRect();
            expect(rowOffsets.top >= gridOffsets.top && rowOffsets.bottom <= gridOffsets.bottom).toBeTruthy();
        });

        it('should allow navigating up in child grid when child grid selected cell moves outside the parent view port.',  async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childCell =  childGrid.dataRowList.toArray()[4].cells.first;
            GridFunctions.focusCell(fixture, childCell);

            const prevScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);
            // parent should scroll up so that cell in child is in view.
            const currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(prevScrTop - currScrTop).toBeGreaterThanOrEqual(childGrid.rowHeight);
        });

        it('should allow navigating to end in child grid when child grid target row moves outside the parent view port.', async () => {
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childCell =  childGrid.dataRowList.toArray()[0].cells.toArray()[0];
            GridFunctions.focusCell(fixture, childCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('end', childGridContent, false, false, true);
            fixture.detectChanges();
            await wait();

            // verify selection in child.
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toEqual(9);
            expect(selectedCell.column.field).toMatch('childData2');

            // parent should be scrolled down
            const currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(currScrTop).toBeGreaterThanOrEqual(childGrid.rowHeight * 5);
        });

        it('should allow navigating to start in child grid when child grid target row moves outside the parent view port.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait();

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const horizontalScrDir = childGrid.dataRowList.toArray()[0].virtDirRow;
            horizontalScrDir.scrollTo(6);
            fixture.detectChanges();
            await wait();

            const childLastCell =  childGrid.dataRowList.toArray()[9].cells.toArray()[3];
            GridFunctions.focusCell(fixture, childLastCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('home', childGridContent, false, false, true);
            await wait(DEBOUNCE_TIME * 3);
            fixture.detectChanges();

            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.value).toEqual(0);
            expect(selectedCell.column.index).toBe(0);
            expect(selectedCell.row.index).toBe(0);

            // check if child row is in view of parent.
            const gridOffsets = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect();
            const rowElem = childGrid.gridAPI.get_row_by_index(selectedCell.row.index);
            const rowOffsets = rowElem.nativeElement.getBoundingClientRect();
            expect(rowOffsets.top).toBeGreaterThanOrEqual(gridOffsets.top);
            expect(rowOffsets.bottom).toBeLessThanOrEqual(gridOffsets.bottom);
        });

        it('should allow navigating to bottom in child grid when child grid target row moves outside the parent view port.', async () => {
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childCell =  childGrid.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, childCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, true);
            // wait for parent grid to complete scroll to child cell.
            await wait();
            fixture.detectChanges();

            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.value).toBe(9);
            expect(selectedCell.column.index).toBe(0);
            expect(selectedCell.row.index).toBe(9);

            const currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(currScrTop).toBeGreaterThanOrEqual(childGrid.rowHeight * 5);
        });

        it('should not lose activation when pressing Ctrl+ArrowDown is pressed at the bottom row(expended) in a child grid.', async () => {
            hierarchicalGrid.height = '600px';
            hierarchicalGrid.width = '800px';
            fixture.componentInstance.rowIsland.height = '400px';
            fixture.detectChanges();
            await wait();

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            childGrid.data = childGrid.data.slice(0, 5);
            fixture.detectChanges();

            childGrid.dataRowList.toArray()[4].expander.nativeElement.click();
            fixture.detectChanges();
            await wait();

            const childCell =  childGrid.dataRowList.toArray()[4].cells.toArray()[0];
            GridFunctions.focusCell(fixture, childCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, true);
            await wait();
            fixture.detectChanges();

            const childLastRowCell =  childGrid.dataRowList.toArray()[4].cells.toArray()[0];
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(childLastRowCell.row.index);
            expect(selectedCell.column.visibleIndex).toBe(childLastRowCell.column.visibleIndex);
            expect(selectedCell.column.index).toBe(childLastRowCell.column.index);
            expect(selectedCell.value).toBe(childLastRowCell.value);

            const currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(currScrTop).toEqual(0);
        });

        it('should allow navigating to top in child grid when child grid target row moves outside the parent view port.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childLastRowCell =  childGrid.dataRowList.toArray()[9].cells.first;
            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            GridFunctions.focusCell(fixture, childLastRowCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const childFirstRowCell =  childGrid.dataRowList.first.cells.first;
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(childFirstRowCell.row.index);
            expect(selectedCell.column.visibleIndex).toBe(childFirstRowCell.column.visibleIndex);
            expect(selectedCell.column.index).toBe(childFirstRowCell.column.index);
            expect(selectedCell.value).toBe(childFirstRowCell.value);

           // check if child row is in view of parent.
           const gridOffsets = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect();
           const selectedCellElem = childGrid.gridAPI.get_cell_by_index(selectedCell.row.index, selectedCell.column.field) as IgxGridCellComponent;
           const rowOffsets = selectedCellElem.intRow.nativeElement.getBoundingClientRect();
           expect(rowOffsets.top).toBeGreaterThanOrEqual(gridOffsets.top);
           expect(rowOffsets.bottom).toBeLessThanOrEqual(gridOffsets.bottom);
        });

        it('should scroll top of child grid into view when pressing Ctrl + Arrow Up when cell is selected in it.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childLastRowCell =  childGrid.dataRowList.toArray()[9].cells.first;
            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            GridFunctions.focusCell(fixture, childLastRowCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const childFirstRowCell =  childGrid.dataRowList.first.cells.first;
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(childFirstRowCell.row.index);
            expect(selectedCell.column.visibleIndex).toBe(childFirstRowCell.column.visibleIndex);
            expect(selectedCell.column.index).toBe(childFirstRowCell.column.index);
            expect(selectedCell.value).toBe(childFirstRowCell.value);

            // check if child row is in view of parent.
            const gridOffsets = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect();
            const rowElem = childGrid.gridAPI.get_row_by_index(selectedCell.row.index);
            const rowOffsets = rowElem.nativeElement.getBoundingClientRect();
            expect(rowOffsets.top).toBeGreaterThanOrEqual(gridOffsets.top);
            expect(rowOffsets.bottom).toBeLessThanOrEqual(gridOffsets.bottom);
        });

        it('when navigating down from parent into child should scroll child grid to top and start navigation from first row.', async () => {
            const ri = fixture.componentInstance.rowIsland;
            ri.height = '200px';
            fixture.detectChanges();

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            childGrid.verticalScrollContainer.scrollTo(9);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            let currScrTop = childGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(currScrTop).toBeGreaterThan(0);

            const fCell = hierarchicalGrid.dataRowList.toArray()[0].cells.toArray()[0];
            GridFunctions.focusCell(fixture, fCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            const childFirstCell =  childGrid.dataRowList.toArray()[0].cells.toArray()[0];
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(childFirstCell.row.index);
            expect(selectedCell.column.index).toBe(childFirstCell.column.index);
            currScrTop = childGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(currScrTop).toBe(0);
        });

        it('when navigating up from parent into child should scroll child grid to bottom and start navigation from last row.', async () => {
            const ri = fixture.componentInstance.rowIsland;
            ri.height = '200px';
            fixture.detectChanges();
            await wait();
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait();

            const parentCell = hierarchicalGrid.gridAPI.get_cell_by_key(1, 'ID');
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait();

            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const vertScr = childGrid.verticalScrollContainer.getScroll();
            const currScrTop = vertScr.scrollTop;
            // should be scrolled to bottom
            expect(currScrTop).toBe(vertScr.scrollHeight - vertScr.clientHeight);
        });

        it('should move activation to last data cell in grid when ctrl+end is used.', async () => {
            const parentCell = hierarchicalGrid.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('end', baseHGridContent, false, false, true);
            fixture.detectChanges();
            await waitForSelectionChange(hierarchicalGrid);

            const lastDataCell = hierarchicalGrid.getCellByKey(19, 'childData2');
            const selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(lastDataCell.row.index);
            expect(selectedCell.column.index).toBe(lastDataCell.column.index);
        });

        it('if next child cell is not in view should scroll parent so that it is in view.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(4);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const parentCell = hierarchicalGrid.dataRowList.toArray()[0].cells.toArray()[0];
            GridFunctions.focusCell(fixture, parentCell);

            const prevScroll = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait();

            // check if selected row is in view of parent.
            const gridOffsets = hierarchicalGrid.tbody.nativeElement.getBoundingClientRect();
            const rowElem = hierarchicalGrid.gridAPI.get_row_by_index(parentCell.row.index);
            const rowOffsets = rowElem.nativeElement.getBoundingClientRect();
            expect(rowOffsets.top >= gridOffsets.top && rowOffsets.bottom <= gridOffsets.bottom).toBeTruthy();
            expect(hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop - prevScroll).toBeGreaterThanOrEqual(100);
        });

        it('should expand/collapse hierarchical row using ALT+Arrow Right/ALT+Arrow Left.', async () => {
            const parentRow = hierarchicalGrid.dataRowList.first as IgxHierarchicalRowComponent;
            expect(parentRow.expanded).toBe(true);
            const parentCell = parentRow.cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            // collapse
            UIInteractions.triggerEventHandlerKeyDown('arrowleft', baseHGridContent, true, false, false);
            fixture.detectChanges();
            await wait();

            expect(parentRow.expanded).toBe(false);
            // expand
            UIInteractions.triggerEventHandlerKeyDown('arrowright', baseHGridContent, true, false, false);
            await wait();
            fixture.detectChanges();
            expect(parentRow.expanded).toBe(true);
        });

        it('should retain active cell when expand/collapse hierarchical row using ALT+Arrow Right/ALT+Arrow Left.', async () => {
            // scroll to last row
            const lastDataIndex = hierarchicalGrid.dataView.length - 2;
            hierarchicalGrid.verticalScrollContainer.scrollTo(lastDataIndex);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            hierarchicalGrid.verticalScrollContainer.scrollTo(lastDataIndex);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            let parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(38, 'ID');
            GridFunctions.focusCell(fixture, parentCell);

             // collapse
            UIInteractions.triggerEventHandlerKeyDown('arrowleft', baseHGridContent, true, false, false);
            fixture.detectChanges();
            await wait();

            parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(38, 'ID');
            expect(parentCell.active).toBeTruthy();

            // expand
            UIInteractions.triggerEventHandlerKeyDown('arrowright', baseHGridContent, true, false, false);
            fixture.detectChanges();
            await wait();

            parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(38, 'ID');
            expect(parentCell.active).toBeTruthy();
        });

        it('should expand/collapse hierarchical row using ALT+Arrow Down/ALT+Arrow Up.', async () => {
            const parentRow = hierarchicalGrid.dataRowList.first as IgxHierarchicalRowComponent;
            expect(parentRow.expanded).toBe(true);
            let parentCell = parentRow.cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            // collapse
            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent, true, false, false);
            fixture.detectChanges();
            await wait();

            expect(parentRow.expanded).toBe(false);
            // expand
            parentCell = parentRow.cells.first;
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, true, false, false);
            fixture.detectChanges();
            await wait();

            expect(parentRow.expanded).toBe(true);
        });

        it('should skip child grids that have no data when navigating up/down', async () => {
            // set first child to not have data
            const child1 = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            child1.data = [];
            fixture.detectChanges();
            await wait();

            const parentRow = hierarchicalGrid.dataRowList.first;
            const parentCell = parentRow.cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait();

            // second data row in parent should be focused
            const parentRow2 = hierarchicalGrid.getRowByIndex(2);
            const parentCell2 = parentRow2.cells[0];

            let selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(parentCell2.row.index);
            expect(selectedCell.column.index).toBe(parentCell2.column.index);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait();

            // first data row in parent should be selected
            selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(parentCell.row.index);
            expect(parentCell.selected).toBeTruthy();
        });

        it('should skip nested child grids that have no data when navigating up/down', async () => {
            const child1 = hierarchicalGrid.gridAPI.getChildGrids(false)[0] as IgxHierarchicalGridComponent;
            child1.height = '150px';
            await wait();
            fixture.detectChanges();
            const row = child1.dataRowList.first as IgxHierarchicalRowComponent;
            row.toggle();
            await wait();
            fixture.detectChanges();
            //  set nested child to not have data
            const subChild = child1.gridAPI.getChildGrids(false)[0];
            subChild.data = [];
            subChild.cdr.detectChanges();
            fixture.detectChanges();
            await wait();

            const fchildRowCell = row.cells.first;
            GridFunctions.focusCell(fixture, fchildRowCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            // second child row should be in view
            const sChildRowCell = child1.getRowByIndex(2).cells[0];
            let selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.value).toBe(sChildRowCell.value);

            expect(child1.verticalScrollContainer.getScroll().scrollTop).toBeGreaterThanOrEqual(150);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, false);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            selectedCell = fixture.componentInstance.selectedCell;
            expect(selectedCell.row.index).toBe(0);
            expect(child1.verticalScrollContainer.getScroll().scrollTop).toBe(0);
        });

        it('should navigate inside summary row with Ctrl + Arrow Right/ Ctrl + Arrow Left', async () => {
            const col = hierarchicalGrid.getColumnByName('ID');
            col.hasSummary = true;
            fixture.detectChanges();

            let summaryCells = hierarchicalGrid.summariesRowList.toArray()[0].summaryCells.toArray();

            const firstCell =  summaryCells[0];
            GridFunctions.focusCell(fixture, firstCell);

            const footerContent = fixture.debugElement.queryAll(By.css(GRID_FOOTER_CLASS))[2].children[0];
            UIInteractions.triggerEventHandlerKeyDown('arrowright', footerContent, false, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            summaryCells = hierarchicalGrid.summariesRowList.toArray()[0].summaryCells.toArray();
            const lastCell = summaryCells.find((s) => s.column.field === 'childData2');
            expect(lastCell.active).toBeTruthy();

            UIInteractions.triggerEventHandlerKeyDown('arrowleft', footerContent, false, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            summaryCells = hierarchicalGrid.summariesRowList.toArray()[0].summaryCells.toArray();
            const fCell = summaryCells.find((s) => s.column.field === 'ID');
            expect(fCell.active).toBeTruthy();
        });

        it('should navigate to Cancel button when there is row in edit mode', async () => {
            hierarchicalGrid.columnList.forEach((c) => {
                if (c.field !== hierarchicalGrid.primaryKey) {
                    c.editable = true;
                }
            });

            hierarchicalGrid.rowEditable = true;
            fixture.detectChanges();
            await wait();

            const cellElem = hierarchicalGrid.gridAPI.get_cell_by_index(0, 'ID');
            GridFunctions.focusCell(fixture, cellElem);

            UIInteractions.triggerEventHandlerKeyDown('end', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const cell = hierarchicalGrid.gridAPI.get_cell_by_index(0, 'childData2');
            UIInteractions.simulateDoubleClickAndSelectEvent(cell);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            UIInteractions.triggerKeyDownEvtUponElem('tab', cell.nativeElement, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const activeEl = document.activeElement;
            expect(activeEl.innerHTML).toEqual('Cancel');

            UIInteractions.triggerKeyDownEvtUponElem('tab', activeEl, true, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

           expect(document.activeElement.tagName.toLowerCase()).toBe('input');
        });

        it('should navigate to row edit button "Done" on shift + tab', async () => {
            hierarchicalGrid.columnList.forEach((c) => {
                if (c.field !== hierarchicalGrid.primaryKey) {
                    c.editable = true;
                }
            });
            hierarchicalGrid.rowEditable = true;
            fixture.detectChanges();
            await wait();

            hierarchicalGrid.getColumnByName('ID').hidden = true;
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            hierarchicalGrid.navigateTo(2);
            await wait();

            const cell = hierarchicalGrid.gridAPI.get_cell_by_index(2, 'ChildLevels');
            UIInteractions.simulateDoubleClickAndSelectEvent(cell);
            fixture.detectChanges();
            await wait();

            UIInteractions.triggerKeyDownEvtUponElem('tab', cell.nativeElement, true, false, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const activeEl = document.activeElement;
            expect(activeEl.innerHTML).toEqual('Done');

            UIInteractions.triggerKeyDownEvtUponElem('tab', activeEl, true);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

           expect(document.activeElement.tagName.toLowerCase()).toBe('input');
        });
    });


    describe('IgxHierarchicalGrid Complex Navigation #hGrid', () => {

        beforeEach(waitForAsync(() => {
            fixture = TestBed.createComponent(IgxHierarchicalGridTestComplexComponent);
            fixture.detectChanges();
            hierarchicalGrid = fixture.componentInstance.hgrid;
            setupHierarchicalGridScrollDetection(fixture, hierarchicalGrid);
            baseHGridContent = GridFunctions.getGridContent(fixture);
            GridFunctions.focusFirstCell(fixture, hierarchicalGrid);
        }));

        afterEach(() => {
            clearGridSubs();
        });

        // complex tests
        it('in case prev cell is not in view port should scroll the closest scrollable parent so that cell comes in view.', async () => {
            // scroll parent so that child top is not in view
            hierarchicalGrid.verticalScrollContainer.addScrollTop(300);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const child = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const nestedChild = child.gridAPI.getChildGrids(false)[0];
            const nestedChildCell = nestedChild.dataRowList.toArray()[1].cells.toArray()[0];

            GridFunctions.focusCell(fixture, nestedChildCell);
            let oldScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            fixture.detectChanges();

            // navigate up
            const nestedChildGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[2];
            UIInteractions.triggerEventHandlerKeyDown('arrowup', nestedChildGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            let nextCell =  nestedChild.dataRowList.toArray()[0].cells.toArray()[0];
            let currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            const elemHeight = nestedChildCell.intRow.nativeElement.clientHeight;
            // check if parent of parent has been scroll up so that the focused cell is in view
            expect(oldScrTop - currScrTop).toEqual(elemHeight);
            oldScrTop = currScrTop;

            expect(nextCell.selected).toBe(true);
            expect(nextCell.active).toBe(true);
            expect(nextCell.rowIndex).toBe(0);

            // navigate up into parent
            UIInteractions.triggerEventHandlerKeyDown('arrowup', nestedChildGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            nextCell =  child.dataRowList.toArray()[0].cells.toArray()[0];
            currScrTop = hierarchicalGrid.verticalScrollContainer.getScroll().scrollTop;
            expect(oldScrTop - currScrTop).toBeGreaterThanOrEqual(100);

            expect(nextCell.selected).toBe(true);
            expect(nextCell.active).toBe(true);
            expect(nextCell.rowIndex).toBe(0);
        });

        it('in case next cell is not in view port should scroll the closest scrollable parent so that cell comes in view.', async () => {
            const child = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const nestedChild = child.gridAPI.getChildGrids(false)[0];
            const nestedChildCell = nestedChild.dataRowList.toArray()[1].cells.toArray()[0];

            // navigate down in nested child
            GridFunctions.focusCell(fixture, nestedChildCell);

            const nestedChildGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[2];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', nestedChildGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            // check if parent has scrolled down to show focused cell.
            expect(child.verticalScrollContainer.getScroll().scrollTop).toBe(nestedChildCell.intRow.nativeElement.clientHeight);
            const nextCell = nestedChild.dataRowList.toArray()[2].cells.toArray()[0];

            expect(nextCell.selected).toBe(true);
            expect(nextCell.active).toBe(true);
            expect(nextCell.rowIndex).toBe(2);
        });

        it('should allow navigating up from parent into nested child grid', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            await wait();
            fixture.detectChanges();

            const child = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const lastIndex =  child.dataView.length - 1;
            child.verticalScrollContainer.scrollTo(lastIndex);
            await wait();
            fixture.detectChanges();

            child.verticalScrollContainer.scrollTo(lastIndex);
            await wait();
            fixture.detectChanges();

            const parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(2, 'ID');
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent , false, false, false);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            const nestedChild = child.gridAPI.getChildGrids(false)[5];
            const lastCell = nestedChild.gridAPI.get_cell_by_index(4, 'ID');
            expect(lastCell.selected).toBe(true);
            expect(lastCell.active).toBe(true);
            expect(lastCell.row.index).toBe(4);
        });
    });

    describe('IgxHierarchicalGrid sibling row islands Navigation #hGrid', () => {

        beforeEach(waitForAsync(() => {
            fixture = TestBed.createComponent(IgxHierarchicalGridMultiLayoutComponent);
            fixture.detectChanges();
            hierarchicalGrid = fixture.componentInstance.hgrid;
            setupHierarchicalGridScrollDetection(fixture, hierarchicalGrid);
            baseHGridContent = GridFunctions.getGridContent(fixture);
            GridFunctions.focusFirstCell(fixture, hierarchicalGrid);
        }));

        afterEach(() => {
            clearGridSubs();
        });

        it('should allow navigating up between sibling child grids.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait();

            const child1 = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const child2 = hierarchicalGrid.gridAPI.getChildGrids(false)[5];

            const child2Cell = child2.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, child2Cell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[2];
            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const lastCellPrevRI = child1.dataRowList.last.cells.first;

            expect(lastCellPrevRI.active).toBe(true);
            expect(lastCellPrevRI.selected).toBe(true);
            expect(lastCellPrevRI.rowIndex).toBe(9);
        });

        it('should allow navigating down between sibling child grids.', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            const child1 = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const child2 = hierarchicalGrid.gridAPI.getChildGrids(false)[5];

            child1.verticalScrollContainer.scrollTo(child1.dataView.length - 1);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            const child2Cell = child2.dataRowList.toArray()[0].cells.toArray()[0];
            const lastCellPrevRI = child1.dataRowList.last.cells.toArray()[0];
            GridFunctions.focusCell(fixture, lastCellPrevRI);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, false);
            await wait(DEBOUNCE_TIME);
            fixture.detectChanges();

            expect(child2Cell.selected).toBe(true);
            expect(child2Cell.active).toBe(true);
        });

        it('should navigate up from parent row to the correct child sibling.', async () => {
            const parentCell = hierarchicalGrid.dataRowList.toArray()[1].cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            // Arrow Up into prev child grid
            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const child2 = hierarchicalGrid.gridAPI.getChildGrids(false)[5];

            const child2Cell = child2.dataRowList.last.cells.first;
            expect(child2Cell.selected).toBe(true);
            expect(child2Cell.active).toBe(true);
            expect(child2Cell.rowIndex).toBe(9);
        });

        it('should navigate down from parent row to the correct child sibling.', async () => {
            const parentCell = hierarchicalGrid.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, parentCell);

            // Arrow down into next child grid
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            const child1 = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const child1Cell = child1.dataRowList.toArray()[0].cells.toArray()[0];
            expect(child1Cell.selected).toBe(true);
            expect(child1Cell.active).toBe(true);
            expect(child1Cell.rowIndex).toBe(0);
        });

        it('should navigate to last cell in previous child using Arrow Up from last cell of sibling with more columns', async () => {
            const childGrid2 = hierarchicalGrid.gridAPI.getChildGrids(false)[5];

            childGrid2.dataRowList.first.virtDirRow.scrollTo(7);
            fixture.detectChanges();
            await wait();

            const child2LastCell = childGrid2.dataRowList.first.cells.first;
            GridFunctions.focusCell(fixture, child2LastCell);

            const childGridContent = fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[2];
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            let childLastCell = childGrid.selectedCells;
            expect(childLastCell.length).toBe(0);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME * 2);

            childLastCell = childGrid.selectedCells;
            expect(childLastCell.length).toBe(1);
            expect(childLastCell[0].active).toBeTruthy();
        });
    });

    describe('IgxHierarchicalGrid Smaller Child Navigation #hGrid', () => {

        beforeEach(waitForAsync(() => {
            fixture = TestBed.createComponent(IgxHierarchicalGridSmallerChildComponent);
            fixture.detectChanges();
            hierarchicalGrid = fixture.componentInstance.hgrid;
            setupHierarchicalGridScrollDetection(fixture, hierarchicalGrid);
            baseHGridContent = GridFunctions.getGridContent(fixture);
            GridFunctions.focusFirstCell(fixture, hierarchicalGrid);
        }));

        afterEach(() => {
            clearGridSubs();
        });

        it('should navigate to last cell in next row for child grid using Arrow Down from last cell of parent with more columns', async () => {
            const parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(0, 'Col2');
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowdown', baseHGridContent, false, false, false);

            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            // last cell in child should be focused
            const childGrid = hierarchicalGrid.gridAPI.getChildGrids(false)[0];
            const childLastCell =  childGrid.gridAPI.get_cell_by_index(0, 'Col1');

            expect(childLastCell.selected).toBe(true);
            expect(childLastCell.active).toBe(true);
        });

        it('should navigate to last cell in next row for child grid using Arrow Up from last cell of parent with more columns', async () => {
            hierarchicalGrid.verticalScrollContainer.scrollTo(2);
            fixture.detectChanges();
            await wait();

            const parentCell = hierarchicalGrid.gridAPI.get_cell_by_index(2, 'Col2');
            GridFunctions.focusCell(fixture, parentCell);

            UIInteractions.triggerEventHandlerKeyDown('arrowup', baseHGridContent, false, false, false);

            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);

            // last cell in child should be focused
            const childGrids =  fixture.debugElement.queryAll(By.directive(IgxChildGridRowComponent));
            const childGrid = childGrids[1].query(By.directive(IgxHierarchicalGridComponent)).componentInstance;
            const childLastCell =  childGrid.gridAPI.get_cell_by_index(9, 'ProductName');

            expect(childLastCell.selected).toBe(true);
            expect(childLastCell.active).toBe(true);
        });

        it('should navigate to last cell in next child using Arrow Down from last cell of previous child with more columns', async () => {
            const childGrids =  fixture.debugElement.queryAll(By.directive(IgxChildGridRowComponent));
            const firstChildGrid = childGrids[0].query(By.directive(IgxHierarchicalGridComponent)).componentInstance;
            const secondChildGrid = childGrids[1].query(By.directive(IgxHierarchicalGridComponent)).componentInstance;

            firstChildGrid.verticalScrollContainer.scrollTo(9);
            fixture.detectChanges();
            await wait();

            const firstChildCell =  firstChildGrid.gridAPI.get_cell_by_index(9, 'Col1');
            GridFunctions.focusCell(fixture, firstChildCell);

            const childGridContent =  fixture.debugElement.queryAll(By.css(GRID_CONTENT_CLASS))[1];
            UIInteractions.triggerEventHandlerKeyDown('arrowdown', childGridContent, false, false, false);
            fixture.detectChanges();
            await wait(DEBOUNCE_TIME);


            const secondChildCell =  secondChildGrid.gridAPI.get_cell_by_index(0, 'ProductName');
            expect(secondChildCell.selected).toBe(true);
            expect(secondChildCell.active).toBe(true);
        });
    });
});


@Component({
    template: `
    <igx-hierarchical-grid #grid1 [data]="data" (selected)="selected($event)"
     [autoGenerate]="true" [height]="'400px'" [width]="'500px'" #hierarchicalGrid primaryKey="ID" [expandChildren]="true">
        <igx-row-island (selected)="selected($event)" [key]="'childData'" [autoGenerate]="true" [height]="null" #rowIsland>
            <igx-row-island (selected)="selected($event)" [key]="'childData'" [autoGenerate]="true" [height]="null" #rowIsland2 >
            </igx-row-island>
        </igx-row-island>
    </igx-hierarchical-grid>`,
    standalone: true,
    imports: [IgxHierarchicalGridComponent, IgxRowIslandComponent ]
})
export class IgxHierarchicalGridTestBaseComponent {
    @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hgrid: IgxHierarchicalGridComponent;
    @ViewChild('rowIsland', { read: IgxRowIslandComponent, static: true }) public rowIsland: IgxRowIslandComponent;
    @ViewChild('rowIsland2', { read: IgxRowIslandComponent, static: true }) public rowIsland2: IgxRowIslandComponent;
    public data;
    public selectedCell;

    constructor() {
        // 3 level hierarchy
        this.data = this.generateData(20, 3);
    }

    public selected(event: IGridCellEventArgs) {
        this.selectedCell = event.cell;
    }

    public generateData(count: number, level: number) {
        const prods = [];
        const currLevel = level;
        let children;
        for (let i = 0; i < count; i++) {
           if (level > 0 ) {
               children = this.generateData(count / 2 , currLevel - 1);
           }
           prods.push({
            ID: i, ChildLevels: currLevel,  ProductName: 'Product: A' + i, Col1: i,
            Col2: i, Col3: i, childData: children, childData2: children });
        }
        return prods;
    }
}

@Component({
    template: `
    <igx-hierarchical-grid #grid1 [height]="'400px'" [width]="'500px'" [data]="data" [autoGenerate]="true"
    [expandChildren]='true' #hierarchicalGrid>
        <igx-row-island [key]="'childData'" [autoGenerate]="true" [expandChildren]='true' [height]="'300px'" #rowIsland>
            <igx-row-island [key]="'childData'" [autoGenerate]="true" #rowIsland2 [height]="'200px'" >
            </igx-row-island>
        </igx-row-island>
    </igx-hierarchical-grid>`,
    standalone: true,
    imports: [IgxHierarchicalGridComponent, IgxRowIslandComponent]
})
export class IgxHierarchicalGridTestComplexComponent extends IgxHierarchicalGridTestBaseComponent {
    constructor() {
        super();
        // 3 level hierarchy
        this.data = this.generateData(20, 3);
    }
}

@Component({
    template: `
    <igx-hierarchical-grid #grid1 [data]="data" [autoGenerate]="true" [height]="'500px'" [width]="'500px'"
    [expandChildren]='true' #hierarchicalGrid>
        <igx-row-island [key]="'childData'" [autoGenerate]="true" [height]="'150px'">
            <igx-row-island [key]="'childData2'" [autoGenerate]="true" [height]="'100px'">
            </igx-row-island>
        </igx-row-island>
        <igx-row-island [key]="'childData2'" [autoGenerate]="true" [height]="'150px'">
        </igx-row-island>
    </igx-hierarchical-grid>`,
    standalone: true,
    imports: [IgxHierarchicalGridComponent, IgxRowIslandComponent]
})
export class IgxHierarchicalGridMultiLayoutComponent extends IgxHierarchicalGridTestBaseComponent {}

@Component({
    template: `
    <igx-hierarchical-grid #grid1 [data]="data" [autoGenerate]="false" [height]="'500px'" [width]="'800px'"
    [expandChildren]='true' #hierarchicalGrid>
        <igx-column field="ID"></igx-column>
        <igx-column field="ChildLevels"></igx-column>
        <igx-column field="ProductName"></igx-column>
        <igx-column field="Col1"></igx-column>
        <igx-column field="Col2"></igx-column>

        <igx-row-island [key]="'childData'" [autoGenerate]="false" [height]="'200px'">
            <igx-column field="ID"></igx-column>
            <igx-column field="ChildLevels"></igx-column>
            <igx-column field="ProductName"></igx-column>
            <igx-column field="Col1"></igx-column>
            <igx-row-island [key]="'childData2'" [autoGenerate]="true" [height]="'100px'">
            </igx-row-island>
        </igx-row-island>
        <igx-row-island [key]="'childData2'" [autoGenerate]="false" [height]="'200px'">
            <igx-column field="ID"></igx-column>
            <igx-column field="ChildLevels"></igx-column>
            <igx-column field="ProductName"></igx-column>
            <igx-row-island [key]="'childData2'" [autoGenerate]="true" [height]="'100px'">
            </igx-row-island>
        </igx-row-island>
    </igx-hierarchical-grid>`,
    standalone: true,
    imports: [IgxHierarchicalGridComponent, IgxRowIslandComponent, IgxColumnComponent]
})
export class IgxHierarchicalGridSmallerChildComponent extends IgxHierarchicalGridTestBaseComponent {}