UNPKG

igniteui-angular-sovn

Version:

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

1,062 lines (866 loc) 65.4 kB
import { Component, ViewChild, OnInit, DebugElement, QueryList, TemplateRef } from '@angular/core'; import { TestBed, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { configureTestSuite } from '../../test-utils/configure-suite'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { By } from '@angular/platform-browser'; import { UIInteractions, wait, waitForActiveNodeChange } from '../../test-utils/ui-interactions.spec'; import { IgxGridDetailTemplateDirective } from './public_api'; import { IgxGridComponent } from './grid.component'; import { IgxGridRowComponent } from './grid-row.component'; import { SampleTestData } from '../../test-utils/sample-test-data.spec'; import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec'; import { IgxGridExpandableCellComponent } from './expandable-cell.component'; import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; import { IgxInputDirective, IgxInputGroupComponent } from '../../input-group/public_api'; import { GridSummaryCalculationMode, GridSummaryPosition, GridSelectionMode } from '../common/enums'; import { IgxCheckboxComponent } from '../../checkbox/checkbox.component'; import { clearGridSubs, setupGridScrollDetection } from '../../test-utils/helper-utils.spec'; import { SortingDirection } from '../../data-operations/sorting-strategy'; import { IgxPaginatorComponent } from '../../paginator/paginator.component'; import { NgFor, NgIf } from '@angular/common'; import { IgxColumnLayoutComponent } from '../columns/column-layout.component'; import { CellType, IgxColumnComponent } from '../public_api'; const DEBOUNCETIME = 30; const ROW_TAG = 'igx-grid-row'; const GROUP_ROW_TAG = 'igx-grid-groupby-row'; const SUMMARY_ROW_TAG = 'igx-grid-summary-row'; const COLLAPSED_ICON_NAME = 'chevron_right'; const EXPANDED_ICON_NAME = 'expand_more'; const HIERARCHICAL_INDENT_CLASS = '.igx-grid__hierarchical-indent'; const SELECTED_ROW_CALSS_NAME = 'igx-grid__tr--selected'; describe('IgxGrid Master Detail #grid', () => { let fix: ComponentFixture<any>; let grid: IgxGridComponent; configureTestSuite((() => { return TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, DefaultGridMasterDetailComponent, AllExpandedGridMasterDetailComponent, MRLMasterDetailComponent ] }); })); describe('Basic', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); fix.detectChanges(); grid = fix.componentInstance.grid; tick(100); })); it('Should render an expand icon for all rows', () => { const expandIcons = grid.rowList.filter((row) => { const iconName = GridFunctions.getRowExpandIconName(row); return iconName === COLLAPSED_ICON_NAME; }); expect(grid.rowList.length).toEqual(expandIcons.length); }); it('Should correctly expand a basic detail view, update expansionStates and the context provided should be correct', () => { GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); const firstRowIconName = GridFunctions.getRowExpandIconName(grid.rowList.first); const firstRowDetail = GridFunctions.getMasterRowDetail(grid.rowList.first); expect(grid.expansionStates.size).toEqual(1); expect(grid.expansionStates.has(grid.rowList.first.key)).toBeTruthy(); expect(grid.expansionStates.get(grid.rowList.first.key)).toBeTruthy(); expect(firstRowIconName).toEqual(EXPANDED_ICON_NAME); expect(getDetailAddressText(firstRowDetail)).toEqual('Obere Str. 57'); }); it('Should render a detail view with dynamic elements and they should be clickable/focusable.', () => { GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); const firstDetail = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); const checkboxElem = firstDetail.query(By.directive(IgxCheckboxComponent)); const checkboxPos = checkboxElem.nativeElement.getBoundingClientRect(); const inputElem = firstDetail.query(By.directive(IgxInputGroupComponent)); const inputElemPos = inputElem.nativeElement.getBoundingClientRect(); const tracedCheckbox: any = document.elementFromPoint(checkboxPos.left + checkboxPos.height / 2, checkboxPos.top + checkboxPos.height / 2); const tracedInput: any = document.elementFromPoint(inputElemPos.left + inputElemPos.height / 2, inputElemPos.top + inputElemPos.height / 2); checkboxElem.componentInstance.nativeCheckbox.nativeElement.click(); fix.detectChanges(); expect(checkboxElem.nativeElement.contains(tracedCheckbox)).toBeTruthy(); expect(checkboxElem.componentInstance.checked).toBeTruthy(); UIInteractions.simulateClickAndSelectEvent(inputElem); fix.detectChanges(); expect(inputElem.nativeElement.contains(tracedInput)).toBeTruthy(); expect(document.activeElement).toEqual(tracedInput); }); it(`Should persist state of rendered templates, such as expansion state of expansion panel, checkbox state, etc. after scrolling them in and out of view.`, (async () => { GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); let firstDetail = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); let checkboxElem = firstDetail.query(By.directive(IgxCheckboxComponent)); let inputElem = firstDetail.query(By.directive(IgxInputGroupComponent)); expect(grid.rowList.first.key).toEqual('ALFKI'); expect(checkboxElem.componentInstance.checked).toBeFalsy(); expect(inputElem.componentInstance.input.value).toEqual(''); expect(getDetailAddressText(firstDetail.nativeElement)).toEqual('Obere Str. 57'); inputElem.componentInstance.input.value = 'Test value'; checkboxElem.componentInstance.checked = !checkboxElem.componentInstance.checked; fix.detectChanges(); grid.navigateTo(20); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(20); expect(GridFunctions.elementInGridView(grid, row.nativeElement)).toBeTruthy(); grid.navigateTo(0); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); firstDetail = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); checkboxElem = firstDetail.query(By.directive(IgxCheckboxComponent)); inputElem = firstDetail.query(By.directive(IgxInputGroupComponent)); expect(grid.rowList.first.key).toEqual('ALFKI'); expect(checkboxElem.componentInstance.checked).toBeTruthy(); expect(inputElem.componentInstance.input.value).toEqual('Test value'); expect(getDetailAddressText(firstDetail.nativeElement)).toEqual('Obere Str. 57'); })); it(`Should persist state of rendered templates, such as expansion state of expansion panel, checkbox state, etc. after scrolling them in and out of view.`, () => { GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); let firstRowDetail = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); let checkboxElem = firstRowDetail.query(By.directive(IgxCheckboxComponent)).componentInstance; let inputGroup = firstRowDetail.query(By.directive(IgxInputGroupComponent)).componentInstance; expect(grid.rowList.first.key).toEqual('ALFKI'); expect(checkboxElem.checked).toBeFalsy(); expect(inputGroup.input.value).toEqual(''); expect(getDetailAddressText(firstRowDetail.nativeElement)).toEqual('Obere Str. 57'); inputGroup.input.value = 'Test value'; checkboxElem.checked = !checkboxElem.checked; fix.detectChanges(); GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); GridFunctions.toggleMasterRow(fix, grid.rowList.first); fix.detectChanges(); firstRowDetail = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); checkboxElem = firstRowDetail.query(By.directive(IgxCheckboxComponent)).componentInstance; inputGroup = firstRowDetail.query(By.directive(IgxInputGroupComponent)).componentInstance; expect(grid.rowList.first.key).toEqual('ALFKI'); expect(checkboxElem.checked).toBeTruthy(); expect(inputGroup.input.value).toEqual('Test value'); expect(getDetailAddressText(firstRowDetail.nativeElement)).toEqual('Obere Str. 57'); }); it(`Should persist state of rendered templates, such as expansion state of expansion panel, checkbox state, etc. after scrolling them in and out of view.`, (async () => { fix.detectChanges(); await wait(DEBOUNCETIME * 2); const verticalScrollbar = grid.verticalScrollContainer.getScroll(); const verticalSrollHeight = (verticalScrollbar.firstElementChild as HTMLElement).offsetHeight; grid.navigateTo(26); await wait(DEBOUNCETIME * 2); fix.detectChanges(); await wait(DEBOUNCETIME * 2); fix.detectChanges(); GridFunctions.toggleMasterRow(fix, grid.rowList.last); await wait(DEBOUNCETIME * 2); fix.detectChanges(); const lastRowDetail = GridFunctions.getMasterRowDetail(grid.rowList.last); expect(grid.expansionStates.size).toEqual(1); expect(grid.expansionStates.has(grid.rowList.last.key)).toBeTruthy(); expect(grid.expansionStates.get(grid.rowList.last.key)).toBeTruthy(); expect(getDetailAddressText(lastRowDetail)).toEqual('Via Monte Bianco 34'); expect(verticalSrollHeight + lastRowDetail.offsetHeight) .toEqual((verticalScrollbar.firstElementChild as HTMLElement).offsetHeight); })); it('Should update view when setting a new expansionState object.', () => { const newExpanded = new Map<any, boolean>(); newExpanded.set('ALFKI', true); newExpanded.set('ANTON', true); newExpanded.set('AROUT', true); expect(grid.tbody.nativeElement.firstElementChild.children.length).toEqual(grid.rowList.length); grid.expansionStates = newExpanded; fix.detectChanges(); const gridRows = grid.rowList.toArray(); const firstDetail = GridFunctions.getMasterRowDetail(gridRows[0]); const secondDetail = GridFunctions.getMasterRowDetail(gridRows[2]); const thirdDetail = GridFunctions.getMasterRowDetail(gridRows[3]); expect(grid.tbody.nativeElement.firstElementChild.children.length).toEqual(grid.rowList.length + 3); expect(getDetailAddressText(firstDetail)).toEqual('Obere Str. 57'); expect(getDetailAddressText(secondDetail)).toEqual('Mataderos 2312'); expect(getDetailAddressText(thirdDetail)).toEqual('120 Hanover Sq.'); }); it('Should update rendered detail templates after grid data is changed.', () => { const newExpanded = new Map<any, boolean>(); newExpanded.set('ALFKI', true); newExpanded.set('ANTON', true); newExpanded.set('AROUT', true); expect(grid.tbody.nativeElement.firstElementChild.children.length).toEqual(grid.rowList.length); grid.expansionStates = newExpanded; fix.detectChanges(); const newData = [...grid.data].slice(0, 4); newData.splice(1, 1); grid.data = newData; fix.detectChanges(); const gridRows = grid.rowList.toArray(); const firstDetail = GridFunctions.getMasterRowDetail(gridRows[0]); const secondDetail = GridFunctions.getMasterRowDetail(gridRows[1]); const thirdDetail = GridFunctions.getMasterRowDetail(gridRows[2]); expect(grid.tbody.nativeElement.firstElementChild.children.length).toEqual(grid.rowList.length + 3); expect(getDetailAddressText(firstDetail)).toEqual('Obere Str. 57'); expect(getDetailAddressText(secondDetail)).toEqual('Mataderos 2312'); expect(getDetailAddressText(thirdDetail)).toEqual('120 Hanover Sq.'); }); it('Should expand and collapse a row in view by using the expandRow(rowID) and collapseRow(rowID) methods.', async () => { grid.expandRow(fix.componentInstance.data[0].ID); await wait(); fix.detectChanges(); const firstRow = grid.rowList.first; let firstRowIconName = GridFunctions.getRowExpandIconName(firstRow); expect(grid.expansionStates.size).toEqual(1); expect(grid.expansionStates.has(firstRow.key)).toBeTruthy(); expect(firstRow.expanded).toBeTruthy(); expect(firstRowIconName).toEqual(EXPANDED_ICON_NAME); grid.collapseRow(fix.componentInstance.data[0].ID); await wait(); fix.detectChanges(); firstRowIconName = GridFunctions.getRowExpandIconName(firstRow); expect(grid.expansionStates.get(fix.componentInstance.data[0].ID)).toBeFalsy(); expect(firstRow.expanded).toBeFalsy(); expect(firstRowIconName).toEqual(COLLAPSED_ICON_NAME); }); it('Should expand a row out of view by using the collapseRow() method and update expansionStates.', async () => { const lastIndex = fix.componentInstance.data.length - 1; const lastDataRecID = fix.componentInstance.data[lastIndex].ID; grid.expandRow(lastDataRecID); await wait(); fix.detectChanges(); expect(grid.expansionStates.size).toEqual(1); expect(grid.expansionStates.get(lastDataRecID)).toBeTruthy(); }); it('Should collapse a row out of view by using the collapseRow() method and update expansionStates.', async () => { GridFunctions.setAllExpanded(grid, fix.componentInstance.data); await wait(); fix.detectChanges(); const lastIndex = fix.componentInstance.data.length - 1; const lastDataRecID = fix.componentInstance.data[lastIndex].ID; grid.collapseRow(lastDataRecID); await wait(); fix.detectChanges(); expect(grid.expansionStates.size).toEqual(fix.componentInstance.data.length); expect(grid.expansionStates.get(lastDataRecID)).toBeFalsy(); }); it('Should toggle a row expand state by using the toggleRow(rowID) method.', async () => { grid.toggleRow(fix.componentInstance.data[0].ID); await wait(); fix.detectChanges(); expect(grid.expansionStates.size).toEqual(1); expect(grid.expansionStates.has(grid.rowList.first.key)).toBeTruthy(); expect(grid.rowList.toArray()[0].expanded).toBeTruthy(); grid.toggleRow(fix.componentInstance.data[0].ID); await wait(); fix.detectChanges(); expect(grid.expansionStates.get(fix.componentInstance.data[0].ID)).toBeFalsy(); expect(grid.rowList.toArray()[0].expanded).toBeFalsy(); }); it('Should expand all rows using the expandAll() method and the expansion state should be updated.', async () => { grid.expandAll(); await wait(); fix.detectChanges(); expect(grid.expansionStates.size).toEqual(0); grid.rowList.toArray().forEach(row => { expect(row.expanded).toBeTruthy(); }); }); it('Should collapse all rows using the collapseAll() method and the expansion state should be updated.', async () => { GridFunctions.setAllExpanded(grid, fix.componentInstance.data); await wait(); fix.detectChanges(); grid.rowList.toArray().forEach(row => { expect(row.expanded).toBeTruthy(); }); grid.collapseAll(); await wait(); fix.detectChanges(); expect(grid.expansionStates.size).toEqual(0); grid.rowList.toArray().forEach(row => { expect(row.expanded).toBeFalsy(); }); }); it('should allow setting external details template via Input.', () => { grid = fix.componentInstance.grid; grid.detailTemplate = fix.componentInstance.detailTemplate; fix.detectChanges(); grid.toggleRow(fix.componentInstance.data[0].ID); fix.detectChanges(); const gridRows = grid.rowList.toArray(); const firstDetail = GridFunctions.getMasterRowDetail(gridRows[0]); expect(firstDetail.textContent.trim()).toBe('NEW TEMPLATE'); }); }); describe('Keyboard Navigation ', () => { let gridContent: DebugElement; beforeEach(async () => { fix = TestBed.createComponent(AllExpandedGridMasterDetailComponent); fix.detectChanges(); grid = fix.componentInstance.grid; gridContent = GridFunctions.getGridContent(fix); await wait(DEBOUNCETIME * 4); fix.detectChanges(); }); it('Should navigate down through a detail view by focusing the whole row and continuing onto the next with arrow down.', () => { const targetCellElement = grid.gridAPI.get_cell_by_index(0, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent); fix.detectChanges(); const firstRowDetail = GridFunctions.getMasterRowDetail(grid.rowList.first); GridFunctions.verifyMasterDetailRowFocused(firstRowDetail); expect(targetCellElement.selected).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent); fix.detectChanges(); expect(grid.getCellByColumn(2, 'ContactName').selected).toBeTruthy(); }); it('Should navigate down through a detail view partially out of view by scrolling it so it becomes fully visible.', async () => { const row = grid.gridAPI.get_row_by_index(4) as IgxGridRowComponent; const targetCellElement = grid.gridAPI.get_cell_by_index(4, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent); await wait(DEBOUNCETIME); fix.detectChanges(); const detailRow = GridFunctions.getMasterRowDetail(row); GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); it('Should navigate down through a detail view completely out of view by scrolling to it.', async () => { grid.navigateTo(6, 0); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(6) as IgxGridRowComponent; const targetCellElement = grid.gridAPI.get_cell_by_index(6, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const detailRow = GridFunctions.getMasterRowDetail(row); GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); it('Should navigate up through a detail view by focusing the whole row and continuing onto the next with arrow up.', () => { const prevRow = grid.gridAPI.get_row_by_index(0) as IgxGridRowComponent; const targetCellElement = grid.gridAPI.get_cell_by_index(2, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent); fix.detectChanges(); const detailRow = GridFunctions.getMasterRowDetail(prevRow); GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(targetCellElement.selected).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent); fix.detectChanges(); expect(prevRow.cells.toArray()[0].selected).toBeTruthy(); }); it('Should navigate up through a detail view partially out of view by scrolling it so it becomes fully visible.', async () => { grid.verticalScrollContainer.addScrollTop(90); await wait(DEBOUNCETIME); fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(2); const targetCellElement = grid.gridAPI.get_cell_by_index(2, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent); await wait(DEBOUNCETIME); fix.detectChanges(); const detailRow = row.nativeElement.previousElementSibling as HTMLElement; GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); it('Should navigate up through a detail view completely out of view by scrolling to it.', async () => { grid.verticalScrollContainer.addScrollTop(170); await wait(DEBOUNCETIME); fix.detectChanges(); let row = grid.gridAPI.get_row_by_index(2); const targetCellElement = grid.gridAPI.get_cell_by_index(2, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent); await wait(DEBOUNCETIME); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(2); const detailRow = row.nativeElement.previousElementSibling as HTMLElement; GridFunctions.verifyMasterDetailRowFocused(detailRow); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); it('Should expand and collapse using Alt + Right/Down and Alt + Left/Up without losing focus on current row.', async () => { const row = grid.gridAPI.get_row_by_index(0) as IgxGridRowComponent; const targetCellElement = grid.gridAPI.get_cell_by_index(0, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); expect(targetCellElement.active).toBeTruthy(); // collapse with alt + arrowup UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeFalsy(); expect(targetCellElement.active).toBeTruthy(); // expand with alt + ArrowDown UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeTruthy(); expect(targetCellElement.active).toBeTruthy(); // collapse with alt + arrowleft UIInteractions.triggerEventHandlerKeyDown('ArrowLeft', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeFalsy(); expect(targetCellElement.active).toBeTruthy(); // expand with alt + arrowright UIInteractions.triggerEventHandlerKeyDown('ArrowRight', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeTruthy(); expect(targetCellElement.active).toBeTruthy(); }); it(`Should expand and collapse using Alt + Right/Down and Alt + Left/Up at the bottom of the grid without losing focus.`, async () => { // navigate to last grid.verticalScrollContainer.scrollTo(grid.verticalScrollContainer.igxForOf.length - 1); await wait(100); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(52) as IgxGridRowComponent; const targetCellElement = grid.gridAPI.get_cell_by_index(52, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); expect(targetCellElement.active).toBeTruthy(); // collapse with alt + arrowup UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeFalsy(); let targetCellElement2 = grid.getCellByColumn(52, 'ContactName'); expect(targetCellElement2.active).toBeTruthy(); // expand with alt + ArrowDown UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, true); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); expect(row.expanded).toBeTruthy(); targetCellElement2 = grid.getCellByColumn(52, 'ContactName'); expect(targetCellElement2.active).toBeTruthy(); }); it('Should navigate to the correct row/cell when using the navigateTo method in a grid with expanded detail views.', async () => { pending('This test should pass when the issue #7300 is fixed.'); grid.navigateTo(20, 0); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); let row = grid.gridAPI.get_row_by_index(20) as IgxGridRowComponent; expect(row).not.toBeNull(); expect(GridFunctions.elementInGridView(grid, row.nativeElement)).toBeTruthy(); grid.navigateTo(21, 0); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(20) as IgxGridRowComponent; const detailRow = GridFunctions.getMasterRowDetail(row); expect(GridFunctions.elementInGridView(grid, detailRow)).toBeTruthy(); }); it('Should navigate to the last data cell in the grid using Ctrl + End.', async () => { setupGridScrollDetection(fix, grid); const targetCellElement = grid.gridAPI.get_cell_by_index(0, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('End', gridContent, false, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const lastRow = grid.gridAPI.get_row_by_index(52); expect(lastRow).not.toBeUndefined(); expect(GridFunctions.elementInGridView(grid, lastRow.nativeElement)).toBeTruthy(); expect((lastRow.cells as QueryList<CellType>).last.active).toBeTruthy(); clearGridSubs(); }); it('Should navigate to the first data cell in the grid using Ctrl + Home.', async () => { setupGridScrollDetection(fix, grid); grid.verticalScrollContainer.scrollTo(grid.verticalScrollContainer.igxForOf.length - 1); await wait(DEBOUNCETIME); fix.detectChanges(); const targetCellElement = grid.gridAPI.get_cell_by_index(52, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('Home', gridContent, false, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); const fRow = grid.gridAPI.get_row_by_index(0); expect(fRow).not.toBeUndefined(); expect(GridFunctions.elementInGridView(grid, fRow.nativeElement)).toBeTruthy(); expect((fRow.cells as QueryList<CellType>).first.active).toBeTruthy(); clearGridSubs(); }); it('Should navigate to the last data row using Ctrl + ArrowDown when all rows are expanded.', async () => { setupGridScrollDetection(fix, grid); const targetCellElement = grid.gridAPI.get_cell_by_index(0, 'ContactName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, false, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const lastRow = grid.gridAPI.get_row_by_index(52); expect(lastRow).not.toBeUndefined(); expect(GridFunctions.elementInGridView(grid, lastRow.nativeElement)).toBeTruthy(); expect((lastRow.cells as QueryList<CellType>).first.active).toBeTruthy(); clearGridSubs(); }); it('Should navigate to the first data row using Ctrl + ArrowUp when all rows are expanded.', async () => { setupGridScrollDetection(fix, grid); grid.verticalScrollContainer.scrollTo(grid.verticalScrollContainer.igxForOf.length - 1); await wait(DEBOUNCETIME); fix.detectChanges(); const targetCellElement = grid.gridAPI.get_cell_by_index(52, 'CompanyName'); UIInteractions.simulateClickAndSelectEvent(targetCellElement); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, false, false, true); await waitForActiveNodeChange(grid); fix.detectChanges(); const fRow = grid.gridAPI.get_row_by_index(0); expect(fRow).not.toBeUndefined(); expect(GridFunctions.elementInGridView(grid, fRow.nativeElement)).toBeTruthy(); expect((fRow.cells as QueryList<CellType>).last.active).toBeTruthy(); clearGridSubs(); }); it(`Should navigate to the first/last row when using Ctrl+ArrowUp/ArrowDown and focus is on the detail row container.`, async () => { // Focus first cell let row = grid.gridAPI.get_row_by_index(0); let detailRow = GridFunctions.getMasterRowDetail(row); UIInteractions.simulateClickAndSelectEvent(detailRow); fix.detectChanges(); GridFunctions.verifyMasterDetailRowFocused(detailRow); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', gridContent, false, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(0); detailRow = GridFunctions.getMasterRowDetail(row); GridFunctions.verifyMasterDetailRowFocused(detailRow); // Got to details row UIInteractions.triggerEventHandlerKeyDown('ArrowUp', gridContent, false, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(0); detailRow = GridFunctions.getMasterRowDetail(row); GridFunctions.verifyMasterDetailRowFocused(detailRow); }); it('Should not navigate if keydown is done on an element inside the details template.', () => { const detailRow = GridFunctions.getMasterRowDetailDebug(fix, grid.rowList.first); const input = detailRow.query(By.css('input[name="Comment"]')); input.nativeElement.focus(); UIInteractions.triggerEventHandlerKeyDown('ArrowDown', input); fix.detectChanges(); expect(document.activeElement).toBe(input.nativeElement); }); }); describe('Integration', () => { describe('Paging', () => { it('Should not take into account expanded detail views as additional records.', fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.detectChanges(); fix.componentInstance.paging = true; fix.detectChanges(); grid.expandRow(fix.componentInstance.data[0].ID); fix.detectChanges(); const initialTotalRecords = grid.pagingState.metadata.countRecords; expect(grid.pagingState.metadata.countRecords).toEqual(initialTotalRecords); })); it('Should persist template state after paging to a page with fewer records and paging back.', fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); fix.componentInstance.perPage = 5; grid = fix.componentInstance.grid; fix.detectChanges(); fix.componentInstance.paging = true; fix.detectChanges(); grid.expandRow(fix.componentInstance.data[4].ID); fix.detectChanges(); // click the template checkbox let checkbox = fix.debugElement.query(By.directive(IgxCheckboxComponent)); checkbox.componentInstance.checked = !checkbox.componentInstance.checked; fix.detectChanges(); // go to last page that doesn't contain this view grid.page = grid.pagingState.metadata.countPages - 1; fix.detectChanges(); // go back to first page grid.page = 0; fix.detectChanges(); // check checkbox state checkbox = fix.debugElement.query(By.directive(IgxCheckboxComponent)); expect(checkbox.componentInstance.checked).toBeTruthy(); })); }); describe('Hiding', () => { it('Should set the expand/collapse icon to the new first visible column when hiding the first column.', fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.detectChanges(); grid.columnList.first.hidden = true; fix.detectChanges(); expect(grid.rowList.first.cells.first instanceof IgxGridExpandableCellComponent).toBeTruthy(); })); }); describe('Pinning', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.detectChanges(); })); it('Should keep/move the expand/collapse icon to the correct column when pinning the first column or another one.', () => { grid.columnList.last.pin(); fix.detectChanges(); expect(grid.rowList.first.cells.first instanceof IgxGridExpandableCellComponent).toBeTruthy(); grid.pinnedColumns[0].unpin(); fix.detectChanges(); expect(grid.rowList.first.cells.first instanceof IgxGridExpandableCellComponent).toBeTruthy(); }); it('Should render detail view correctly when expanding a master row and there are pinned columns.', () => { grid.columnList.last.pin(); grid.expandRow(fix.componentInstance.data[0].ID); fix.detectChanges(); const firstRowDetail = GridFunctions.getMasterRowDetail(grid.rowList.first); expect(getDetailAddressText(firstRowDetail)).toEqual('Obere Str. 57'); expect(firstRowDetail.querySelector(HIERARCHICAL_INDENT_CLASS)).toBeDefined(); }); }); describe('Column Moving', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.detectChanges(); })); it('Should keep the expand/collapse icon in the first column, even when moving a column in first place.', fakeAsync(() => { grid.moveColumn(grid.columnList.last, grid.columnList.first); tick(); fix.detectChanges(); expect(grid.rowList.first.cells.first instanceof IgxGridExpandableCellComponent).toBeTruthy(); })); it('Should keep the expand/collapse icon in the first column, even when moving a column out of first place.', fakeAsync(() => { grid.moveColumn(grid.columnList.first, grid.columnList.last); tick(); fix.detectChanges(); expect(grid.rowList.first.cells.first instanceof IgxGridExpandableCellComponent).toBeTruthy(); })); }); describe('Cell Selection', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.detectChanges(); })); it('Should exclude expanded detail views when doing range cell selection', fakeAsync(() => { grid.expandRow(fix.componentInstance.data[2].ID); const selectionChangeSpy = spyOn<any>(grid.rangeSelected, 'emit').and.callThrough(); const startCell = grid.gridAPI.get_cell_by_index(1, 'ContactName'); const endCell = grid.gridAPI.get_cell_by_index(6, 'CompanyName'); const range = { rowStart: 1, rowEnd: 6, columnStart: 0, columnEnd: 1 }; UIInteractions.simulatePointerOverElementEvent('pointerdown', startCell.nativeElement); startCell.nativeElement.dispatchEvent(new Event('click')); grid.cdr.detectChanges(); expect(startCell.active).toBe(true); for (let i = 2; i < 6; i++) { const cell = grid.gridAPI.get_cell_by_index(i, 'ContactName'); if (!cell) { UIInteractions.simulatePointerOverElementEvent('pointerenter', fix.debugElement.query(By.css('.addressArea')).nativeElement); continue; } UIInteractions.simulatePointerOverElementEvent('pointerenter', cell.nativeElement); grid.cdr.detectChanges(); } UIInteractions.simulatePointerOverElementEvent('pointerenter', endCell.nativeElement); UIInteractions.simulatePointerOverElementEvent('pointerup', endCell.nativeElement); GridSelectionFunctions.verifyCellsRegionSelected(grid, 1, 2, 0, 1, true); GridSelectionFunctions.verifyCellsRegionSelected(grid, 4, 5, 0, 1, true); grid.cdr.detectChanges(); expect(startCell.active).toBe(true); const rowDetail = GridFunctions.getMasterRowDetail(grid.rowList.toArray()[2]); expect(selectionChangeSpy).toHaveBeenCalledTimes(1); expect(selectionChangeSpy).toHaveBeenCalledWith(range); expect(rowDetail.querySelector('[class*="selected"]')).toBeNull(); })); it('getSelectedData should return correct values when there are master details', fakeAsync(() => { const range = { rowStart: 0, rowEnd: 5, columnStart: 'ContactName', columnEnd: 'ContactName' }; const expectedData = [ { ContactName: 'Maria Anders' }, { ContactName: 'Ana Trujillo' }, { ContactName: 'Antonio Moreno' } ]; grid.expandAll(); tick(100); fix.detectChanges(); grid.selectRange(range); fix.detectChanges(); expect(grid.getSelectedData()).toEqual(expectedData); })); it('getSelectedData should return correct values when there are master details and paging is enabled', fakeAsync(() => { const range = { rowStart: 0, rowEnd: 5, columnStart: 'ContactName', columnEnd: 'ContactName' }; const expectedDataFromSecondPage = [ { ContactName: 'Hanna Moos' }, { ContactName: 'Frédérique Citeaux' }, { ContactName: 'Martín Sommer' } ]; fix.componentInstance.paging = true; fix.detectChanges(); grid.paginator.perPage = 5; fix.detectChanges(); tick(16); grid.paginator.paginate(1); fix.detectChanges(); tick(16); grid.expandAll(); tick(100); fix.detectChanges(); grid.selectRange(range); fix.detectChanges(); expect(grid.getSelectedData()).toEqual(expectedDataFromSecondPage); const expectedDataFromThirdPage = [ { ContactName: 'Victoria Ashworth' }, { ContactName: 'Patricio Simpson' }, { ContactName: 'Francisco Chang' } ]; grid.paginator.paginate(2); fix.detectChanges(); tick(16); grid.expandAll(); tick(100); fix.detectChanges(); grid.selectRange(range); fix.detectChanges(); expect(grid.getSelectedData()).toEqual(expectedDataFromThirdPage); })); }); describe('Row Selection', () => { beforeEach(fakeAsync(() => { fix = TestBed.createComponent(DefaultGridMasterDetailComponent); grid = fix.componentInstance.grid; fix.componentInstance.rowSelectable = true; fix.detectChanges(); })); it('Should not render row selection checkbox for detail views.', () => { grid.expandRow(fix.componentInstance.data[2].ID); fix.detectChanges(); const rowDetail = GridFunctions.getMasterRowDetail(grid.rowList.toArray()[2]); expect(GridSelectionFunctions.getRowCheckboxDiv(rowDetail)).toBeNull(); }); it('Should highlight only the master row when selecting it and not the detail row.', () => { grid.expandRow(fix.componentInstance.data[2].ID); fix.detectChanges(); const row = grid.rowList.toArray()[2]; GridSelectionFunctions.rowCheckboxClick(row); fix.detectChanges(); const rowDetail = GridFunctions.getMasterRowDetail(row); expect(row.nativeElement.classList).toContain(SELECTED_ROW_CALSS_NAME); expect(rowDetail.querySelector('[class*="selected"]')).toBeNull(); }); }); describe('Search', () => { it('Should scroll to the correct parent rows when searching in a grid with expanded detail views.', async () => { fix = TestBed.createComponent(AllExpandedGridMasterDetailComponent); fix.detectChanges(); await wait(); fix.detectChanges(); grid = fix.componentInstance.grid; grid.findNext('Paolo'); await wait(DEBOUNCETIME); fix.detectChanges(); let row = grid.gridAPI.get_row_by_index(52); expect(row).not.toBeNull(); GridFunctions.elementInGridView(grid, row.nativeElement); grid.findPrev('Maria'); await wait(DEBOUNCETIME); fix.detectChanges(); row = grid.gridAPI.get_row_by_index(0); expect(row).not.toBeNull(); GridFunctions.elementInGridView(grid, row.nativeElement); }); }); describe('Updating', () => { beforeEach(async () => { fix = TestBed.createComponent(AllExpandedGridMasterDetailComponent); fix.detectChanges(); await wait(); fix.detectChanges(); grid = fix.componentInstance.grid; }); it('Should remove expanded detail view after deleting its parent row.', () => { let detailViews = GridFunctions.getAllMasterRowDetailDebug(fix); expect(detailViews[0].context.index).toBe(1); grid.deleteRow('ALFKI'); fix.detectChanges(); const row = grid.getRowByKey('ALFKI'); expect(row).toBeUndefined(); detailViews = GridFunctions.getAllMasterRowDetailDebug(fix); expect(detailViews[0].context.index).toBe(1); expect(detailViews[0].context.templateID.type).toBe('detailRow'); expect(detailViews[0].context.templateID.id).toBe('ANATR'); }); it('Should be able to expand detail view of newly added row.', async () => { grid.addRow({ ID: '123', CompanyName: 'Test', ContactName: 'Test', Address: 'Test Address' }); fix.detectChanges(); // scroll to bottom grid.verticalScrollContainer.scrollTo(grid.verticalScrollContainer.igxForOf.length - 1); await wait(DEBOUNCETIME); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); // check row can be expanded const lastRow = grid.rowList.last; GridFunctions.toggleMasterRow(fix, lastRow); await wait(); fix.detectChanges(); expect(lastRow.expanded).toBeTruthy(); const lastRowDetail = GridFunctions.getMasterRowDetail(grid.rowList.last); expect(getDetailAddressText(lastRowDetail)).toEqual('Test Address'); }); }); describe('Sorting', () => { it('Should rearrange detail views to their correct parents after sorting.', async () => { fix = TestBed.createComponent(AllExpandedGridMasterDetailComponent); fix.detectChanges(); await wait(); fix.detectChanges(); grid = fix.componentInstance.grid; grid.sort({ fieldName: 'ContactName', dir: SortingDirection.Desc, ignoreCase: true }); fix.detectChanges(); let row = grid.rowList.first; let detailRow = GridFunctions.getMasterRowDetail(row); expect(row.data['ContactName']).toBe('Yang Wang'); expect(getDetailAddressText(detailRow)).toEqual(row.data['Address']); row = grid.rowList.toArray()[1]; detailRow = GridFunctions.getMasterRowDetail(row); expect(row.data['ContactName']).toBe('Victoria Ashworth'); expect(getDetailAddressText(detailRow)).toEqual(row.data['Address']); }); }); describe('Filtering', () => { it('Should persist template state after filtering out the whole data and removing the filter.', fakeAsync(() => { fix = TestBed.createComponent(AllExpandedGridMasterDetailComponent); fix.detectChanges(); tick(100); fix.detectChanges(); grid = fix.componentInstance.grid; let checkbox = fix.debugElement.query(By.directive(IgxCheckboxComponent)); checkbox.componentInstance.checked = !checkbox.componentInstance.checked; fix.detectChanges(); // check checkbox state checkbox = fix.debugElement.quer