UNPKG

igniteui-angular-sovn

Version:

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

1,182 lines (945 loc) 121 kB
import { DebugElement } from '@angular/core'; import { TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { IgxGridComponent } from './grid.component'; import { IGridEditEventArgs, IGridEditDoneEventArgs } from '../common/events'; import { IgxColumnComponent } from '../columns/column.component'; import { DisplayDensity } from '../../core/density'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { IgxStringFilteringOperand, IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; import { TransactionType, Transaction } from '../../services/public_api'; import { configureTestSuite } from '../../test-utils/configure-suite'; import { DefaultSortingStrategy, SortingDirection } from '../../data-operations/sorting-strategy'; import { clearGridSubs, setupGridScrollDetection } from '../../test-utils/helper-utils.spec'; import { GridFunctions, GridSummaryFunctions } from '../../test-utils/grid-functions.spec'; import { IgxGridRowEditingComponent, IgxGridRowEditingTransactionComponent, IgxGridWithEditingAndFeaturesComponent, IgxGridRowEditingWithoutEditableColumnsComponent, IgxGridCustomOverlayComponent, IgxGridEmptyRowEditTemplateComponent, VirtualGridComponent, ObjectCloneStrategy, IgxGridCustomRowEditTemplateComponent } from '../../test-utils/grid-samples.spec'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { DefaultDataCloneStrategy } from '../../data-operations/data-clone-strategy'; import { CellType, RowType } from '../public_api'; const CELL_CLASS = '.igx-grid__td'; const ROW_EDITED_CLASS = 'igx-grid__tr--edited'; const ROW_DELETED_CLASS = 'igx-grid__tr--deleted'; const SUMMARY_ROW = 'igx-grid-summary-row'; const COLUMN_HEADER_GROUP_CLASS = '.igx-grid-thead__item'; const DEBOUNCETIME = 30; describe('IgxGrid - Row Editing #grid', () => { configureTestSuite((() => { return TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxGridRowEditingComponent, IgxGridRowEditingTransactionComponent, IgxGridWithEditingAndFeaturesComponent, IgxGridRowEditingWithoutEditableColumnsComponent, IgxGridCustomOverlayComponent, IgxGridEmptyRowEditTemplateComponent, IgxGridCustomRowEditTemplateComponent, VirtualGridComponent ] }); })); describe('General tests', () => { let fix; let grid: IgxGridComponent; let cell: CellType; let cellElem: CellType; let cellDebug: DebugElement; let gridContent: DebugElement; beforeEach(() => { fix = TestBed.createComponent(IgxGridRowEditingComponent); fix.detectChanges(); grid = fix.componentInstance.grid; gridContent = GridFunctions.getGridContent(fix); cell = grid.getCellByColumn(2, 'ProductName'); cellElem = grid.gridAPI.get_cell_by_index(2, 'ProductName'); cellDebug = GridFunctions.getRowCells(fix, 2)[2]; // row = grid.gridAPI.get_row_by_index(2); }); it('Should throw a warning when [rowEditable] is set on a grid w/o [primaryKey]', () => { grid.primaryKey = null; grid.rowEditable = false; fix.detectChanges(); spyOn(console, 'warn'); grid.rowEditable = true; fix.detectChanges(); // Throws warning but still sets the property correctly expect(grid.rowEditable).toBeTruthy(); UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(console.warn).toHaveBeenCalledWith('The grid must have a `primaryKey` specified when using `rowEditable`!'); expect(console.warn).toHaveBeenCalledTimes(1); }); it('Should be able to enter edit mode on dblclick, enter and f2', () => { fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(2); UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(row.inEditMode).toBe(true); UIInteractions.triggerEventHandlerKeyDown('escape', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(true); UIInteractions.triggerEventHandlerKeyDown('escape', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); UIInteractions.triggerEventHandlerKeyDown('f2', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(true); UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); }); it('Should not be able to enter edit mode on dblclick, enter and f2 when [rowEditable] is set on a grid w/o [primaryKey]', () => { grid.primaryKey = null; grid.rowEditable = true; fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(2); UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(row.inEditMode).toBe(false); UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); UIInteractions.triggerEventHandlerKeyDown('f2', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); }); it('Emit all events with proper arguments', () => { const row = grid.gridAPI.get_row_by_index(2); const initialRowData = { ...cell.row.data }; const newCellValue = 'Aaaaa'; const updatedRowData = Object.assign({}, row.data, { ProductName: newCellValue }); spyOn(grid.cellEditEnter, 'emit').and.callThrough(); spyOn(grid.cellEdit, 'emit').and.callThrough(); spyOn(grid.cellEditDone, 'emit').and.callThrough(); spyOn(grid.cellEditExit, 'emit').and.callThrough(); spyOn(grid.cellEditExit, 'emit').and.callThrough(); spyOn(grid.rowEditEnter, 'emit').and.callThrough(); spyOn(grid.rowEdit, 'emit').and.callThrough(); spyOn(grid.rowEditExit, 'emit').and.callThrough(); spyOn(grid.rowEditDone, 'emit').and.callThrough(); let cellInput = null; UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(row.inEditMode).toBe(true); const cellEditArgs: IGridEditEventArgs = { primaryKey: cell.row.key, cellID: cell.id, rowID: cell.row.key, rowData: cell.row.data, oldValue: cell.value, cancel: false, column: cell.column, owner: grid, valid: true, event: jasmine.anything() as any }; let rowEditArgs: IGridEditEventArgs = { rowID: row.key, primaryKey: cell.row.key, rowData: initialRowData, oldValue: row.data, cancel: false, valid: true, owner: grid, isAddRow: row.addRowUI, event: jasmine.anything() as any }; expect(grid.cellEditEnter.emit).toHaveBeenCalledWith(cellEditArgs); expect(grid.rowEditEnter.emit).toHaveBeenCalledWith(rowEditArgs); UIInteractions.triggerEventHandlerKeyDown('escape', gridContent); fix.detectChanges(); expect(row.inEditMode).toBe(false); let cellEditExitArgs: IGridEditDoneEventArgs = { cellID: cell.id, rowID: cell.row.key, primaryKey: cell.row.key, rowData: cell.row.data, oldValue: cell.value, valid: true, newValue: cell.value, column: cell.column, owner: grid, event: jasmine.anything() as any }; const rowEditExitArgs: IGridEditDoneEventArgs = { rowID: row.key, primaryKey: row.key, rowData: initialRowData, newValue: initialRowData, oldValue: row.data, owner: grid, isAddRow: row.addRowUI, event: jasmine.anything() as any, valid: true }; expect(grid.cellEditExit.emit).toHaveBeenCalledWith(cellEditExitArgs); expect(grid.rowEditExit.emit).toHaveBeenCalledWith(rowEditExitArgs); UIInteractions.simulateDoubleClickAndSelectEvent(cellDebug); fix.detectChanges(); expect(row.inEditMode).toBe(true); cellInput = (cellElem as any).nativeElement.querySelector('[igxinput]'); UIInteractions.setInputElementValue(cellInput, newCellValue); fix.detectChanges(); cellEditExitArgs = { cellID: cell.id, primaryKey: cell.row.key, rowID: cell.row.key, rowData: Object.assign({}, row.data, { ProductName: newCellValue }), oldValue: cell.value, newValue: newCellValue, valid: true, column: cell.column, owner: grid, event: jasmine.anything() as any }; cellEditArgs.newValue = newCellValue; cellEditArgs.rowData = Object.assign({}, row.data, { ProductName: newCellValue }); rowEditArgs = { rowID: row.key, primaryKey: cell.row.key, rowData: initialRowData, newValue: Object.assign({}, row.data, { ProductName: newCellValue }), oldValue: row.data, cancel: false, owner: grid, isAddRow: row.addRowUI, valid: true, event: jasmine.anything() as any }; const cellDoneArgs: IGridEditDoneEventArgs = { rowID: cell.row.key, primaryKey: row.key, cellID: cell.id, rowData: updatedRowData, // with rowEditable - IgxGridRowEditingComponent oldValue: cell.value, newValue: newCellValue, valid: true, column: cell.column, owner: grid, event: jasmine.anything() as any }; const rowDoneArgs: IGridEditDoneEventArgs = { rowID: row.key, primaryKey: row.key, rowData: updatedRowData, // with rowEditable - IgxGridRowEditingComponent oldValue: row.data, newValue: Object.assign({}, row.data, { ProductName: newCellValue }), owner: grid, isAddRow: row.addRowUI, event: jasmine.anything() as any, valid: true }; UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(grid.cellEdit.emit).toHaveBeenCalledWith(cellEditArgs); expect(grid.cellEditDone.emit).toHaveBeenCalledWith(cellDoneArgs); expect(grid.cellEditExit.emit).toHaveBeenCalledWith(cellEditExitArgs); expect(grid.rowEdit.emit).toHaveBeenCalledWith(rowEditArgs); expect(grid.rowEditDone.emit).toHaveBeenCalledWith(rowDoneArgs); }); it('Should display the banner below the edited row if it is not the last one', () => { cell.editMode = true; const editRow = grid.gridAPI.get_row_by_index(2).nativeElement; //cellElem.row.nativeElement; const banner = GridFunctions.getRowEditingOverlay(fix); fix.detectChanges(); const bannerTop = banner.getBoundingClientRect().top; const editRowBottom = editRow.getBoundingClientRect().bottom; // The banner appears below the row expect(bannerTop).toBeGreaterThanOrEqual(editRowBottom); // No much space between the row and the banner expect(bannerTop - editRowBottom).toBeLessThan(2); }); it('Should display the banner after the edited row if it is the last one, but has room underneath it', () => { const lastItemIndex = 6; cell = grid.getCellByColumn(lastItemIndex, 'ProductName'); cellElem = grid.gridAPI.get_cell_by_index(lastItemIndex, 'ProductName'); cell.editMode = true; const editRow = grid.gridAPI.get_row_by_index(lastItemIndex).nativeElement; const banner = GridFunctions.getRowEditingOverlay(fix); fix.detectChanges(); const bannerTop = banner.getBoundingClientRect().top; const editRowBottom = editRow.getBoundingClientRect().bottom; // The banner appears below the row expect(bannerTop).toBeGreaterThanOrEqual(editRowBottom); // No much space between the row and the banner expect(bannerTop - editRowBottom).toBeLessThan(2); }); it('Should display the banner above the edited row if it is the last one', () => { cell = grid.getCellByColumn(grid.data.length - 1, 'ProductName'); cellElem = grid.gridAPI.get_cell_by_index(grid.data.length - 1, 'ProductName'); cell.editMode = true; const editRow = grid.gridAPI.get_cell_by_index(grid.data.length - 1, 'ProductName').nativeElement; const banner = GridFunctions.getRowEditingOverlay(fix); fix.detectChanges(); const bannerBottom = banner.getBoundingClientRect().bottom; const editRowTop = editRow.getBoundingClientRect().top; // The banner appears above the row expect(bannerBottom).toBeLessThanOrEqual(editRowTop); // No much space between the row and the banner expect(editRowTop - bannerBottom).toBeLessThan(2); }); it(`Should preserve updated value inside the cell when it enters edit mode again`, () => { cell.editMode = true; cell.update('IG'); fix.detectChanges(); // TODO cell cell.editMode = false; cell.editMode = true; expect(cell.value).toEqual('IG'); }); it(`Should correctly get column.editable for grid with no transactions`, () => { grid.columnList.forEach(c => { c.editable = true; }); const primaryKeyColumn = grid.columnList.find(c => c.field === grid.primaryKey); const nonPrimaryKeyColumn = grid.columnList.find(c => c.field !== grid.primaryKey); expect(primaryKeyColumn).toBeDefined(); expect(nonPrimaryKeyColumn).toBeDefined(); grid.rowEditable = false; expect(primaryKeyColumn.editable).toBeTruthy(); expect(nonPrimaryKeyColumn.editable).toBeTruthy(); grid.rowEditable = true; expect(primaryKeyColumn.editable).toBeFalsy(); expect(nonPrimaryKeyColumn.editable).toBeTruthy(); }); it('Should properly exit pending state when committing row edit w/o changes', () => { const initialDataLength = grid.data.length; UIInteractions.simulateClickAndSelectEvent(grid.gridAPI.get_cell_by_index(cell.row.index, cell.column.index)); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(cell.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('enter', gridContent); fix.detectChanges(); expect(cell.editMode).toBeFalsy(); grid.deleteRow(2); fix.detectChanges(); expect(grid.data.length).toEqual(initialDataLength - 1); }); it('Overlay position: Open overlay for top row', () => { grid.height = '300px'; fix.detectChanges(); let row: HTMLElement = grid.gridAPI.get_row_by_index(0).nativeElement; cell = grid.getCellByColumn(0, 'ProductName'); cell.editMode = true; let overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(row.getBoundingClientRect().bottom === overlayContent.getBoundingClientRect().top).toBeTruthy(); cell.editMode = false; row = grid.gridAPI.get_row_by_index(2).nativeElement; cell = grid.getCellByColumn(2, 'ProductName'); cell.editMode = true; overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(row.getBoundingClientRect().bottom === overlayContent.getBoundingClientRect().top).toBeTruthy(); cell.editMode = false; row = grid.gridAPI.get_row_by_index(3).nativeElement; cell = grid.getCellByColumn(3, 'ProductName'); cell.editMode = true; overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(row.getBoundingClientRect().top === overlayContent.getBoundingClientRect().bottom).toBeTruthy(); cell.editMode = false; row = grid.gridAPI.get_row_by_index(0).nativeElement; cell = grid.getCellByColumn(0, 'ProductName'); cellElem = grid.gridAPI.get_cell_by_index(0, 'ProductName'); cell.editMode = true; overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(row.getBoundingClientRect().bottom === overlayContent.getBoundingClientRect().top).toBeTruthy(); cell.editMode = false; }); it('should end row editing when clearing or applying advanced filter', () => { fix.detectChanges(); const row = grid.gridAPI.get_row_by_index(2); // Enter row edit mode UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(row.inEditMode).toBe(true); // Open Advanced Filtering dialog. grid.openAdvancedFilteringDialog(); fix.detectChanges(); // Clear the filters. GridFunctions.clickAdvancedFilteringClearFilterButton(fix); fix.detectChanges(); expect(row.inEditMode).toBe(false); // Close the dialog. GridFunctions.clickAdvancedFilteringCancelButton(fix); fix.detectChanges(); // Enter row edit mode UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); expect(row.inEditMode).toBe(true); // Open Advanced Filtering dialog. grid.openAdvancedFilteringDialog(); fix.detectChanges(); // Apply the filters. GridFunctions.clickAdvancedFilteringApplyButton(fix); fix.detectChanges(); expect(row.inEditMode).toBe(false); }); }); describe('Navigation - Keyboard', () => { let fix; let grid: IgxGridComponent; let gridContent: DebugElement; let targetCell: any; let editedCell: CellType; beforeEach(() => { fix = TestBed.createComponent(IgxGridWithEditingAndFeaturesComponent); fix.detectChanges(); grid = fix.componentInstance.grid; setupGridScrollDetection(fix, grid); gridContent = GridFunctions.getGridContent(fix); }); afterEach(() => { clearGridSubs(); }); it(`Should jump from first editable columns to overlay buttons`, () => { targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); // TO button UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); expect(targetCell.editMode).toBeFalsy(); const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); expect(document.activeElement).toEqual(doneButtonElement); // FROM button to last cell grid.rowEditTabs.last.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab')); fix.detectChanges(); expect(targetCell.editMode).toBeTruthy(); }); it(`Should jump from last editable columns to overlay buttons`, (async () => { grid.tbody.nativeElement.focus(); fix.detectChanges(); GridFunctions.scrollLeft(grid, 800); fix.detectChanges(); await wait(DEBOUNCETIME); targetCell = grid.gridAPI.get_cell_by_index(0, 'Test'); UIInteractions.simulateClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('f2', gridContent); fix.detectChanges(); // TO button expect(targetCell.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); expect(targetCell.editMode).toBeFalsy(); const cancelButtonElementDebug = GridFunctions.getRowEditingCancelDebugElement(fix); expect(document.activeElement).toEqual(cancelButtonElementDebug.nativeElement); // FROM button to last cell grid.rowEditTabs.first.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab', false, true)); fix.detectChanges(); await wait(DEBOUNCETIME * 2); fix.detectChanges(); expect(targetCell.editMode).toBeTruthy(); })); it(`Should scroll editable column into view when navigating from buttons`, (async () => { let cell = grid.getCellByColumn(0, 'Downloads'); let cellElem = grid.gridAPI.get_cell_by_index(0, 'Downloads'); // let cellDebug; UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); await wait(DEBOUNCETIME); fix.detectChanges(); // go to 'Cancel' const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); expect(document.activeElement).toEqual(doneButtonElement); grid.rowEditTabs.last.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab', false, true)); await wait(DEBOUNCETIME); fix.detectChanges(); // go to LAST editable cell grid.rowEditTabs.first.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab', false, true)); fix.detectChanges(); await wait(DEBOUNCETIME * 2); fix.detectChanges(); cell = grid.getCellByColumn(0, 'Test'); cellElem = grid.gridAPI.get_cell_by_index(0, 'Test'); expect(cell.editMode).toBeTruthy(); expect(grid.headerContainer.getScroll().scrollLeft).toBeGreaterThan(0); // move to Cancel UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); await wait(DEBOUNCETIME); fix.detectChanges(); // Focus cancel const cancelButtonElement = GridFunctions.getRowEditingCancelButton(fix); cancelButtonElement.focus(); await wait(DEBOUNCETIME); grid.rowEditTabs.first.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab')); await wait(); fix.detectChanges(); // move to FIRST editable cell grid.rowEditTabs.last.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab')); fix.detectChanges(); await wait(DEBOUNCETIME * 2); fix.detectChanges(); cell = grid.getCellByColumn(0, 'Downloads'); cellElem = grid.gridAPI.get_cell_by_index(0, 'Downloads'); expect(cell.editMode).toBeTruthy(); expect(grid.headerContainer.getScroll().scrollLeft).toEqual(0); })); it(`Should skip non-editable columns`, () => { const cell = grid.gridAPI.get_cell_by_index(0, 'ID'); const cellReleaseDate = grid.gridAPI.get_cell_by_index(0, 'ReleaseDate'); targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); expect(targetCell.editMode).toBeTruthy(); // Move forwards UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); fix.detectChanges(); expect(targetCell.editMode).toBeFalsy(); expect(cell.editMode).toBeFalsy(); expect(cellReleaseDate.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); expect(targetCell.editMode).toBeTruthy(); expect(cell.editMode).toBeFalsy(); expect(cellReleaseDate.editMode).toBeFalsy(); }); it(`Should skip non-editable columns when column pinning is enabled`, () => { fix.componentInstance.pinnedFlag = true; fix.detectChanges(); targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // from pinned to unpinned UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'ReleaseDate' editedCell = grid.gridAPI.get_cell_by_index(0, 'ReleaseDate'); expect(editedCell.editMode).toBeTruthy(); // from unpinned to pinned UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // EXPECT edited cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); }); it(`Should skip non-editable columns when column hiding is enabled`, () => { fix.componentInstance.hiddenFlag = true; fix.detectChanges(); targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // jump over 1 hidden, editable UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'Items' editedCell = grid.gridAPI.get_cell_by_index(0, 'Items'); expect(editedCell.editMode).toBeTruthy(); // jump over 1 hidden, editable UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true, false); fix.detectChanges(); // EXPECT edited cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // jump over 3 hidden, both editable and not UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true, false); fix.detectChanges(); // EXPECT edited cell to be 'Downloads' editedCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); expect(editedCell.editMode).toBeTruthy(); }); it(`Should skip non-editable columns when column pinning & hiding is enabled`, () => { fix.componentInstance.hiddenFlag = true; fix.detectChanges(); fix.componentInstance.pinnedFlag = true; fix.detectChanges(); // jump over 1 hidden, pinned targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // jump over 3 hidden, both editable and not UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // EXPECT focused cell to be 'Items' editedCell = grid.gridAPI.get_cell_by_index(0, 'Items'); expect(editedCell.editMode).toBeTruthy(); // jump back to pinned UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // EXPECT edited cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // jump over 1 hidden, pinned UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // EXPECT edited cell to be 'Downloads' editedCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); expect(editedCell.editMode).toBeTruthy(); }); it(`Should skip non-editable columns when column grouping is enabled`, (async () => { fix.componentInstance.columnGroupingFlag = true; fix.detectChanges(); targetCell = grid.gridAPI.get_cell_by_index(0, 'ReleaseDate'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // Should disregards the Igx-Column-Group component // EXPECT focused cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // Go forwards, jump over Category and group end UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); // EXPECT focused cell to be 'Items' editedCell = grid.gridAPI.get_cell_by_index(0, 'Items'); expect(editedCell.editMode).toBeTruthy(); // Go backwards, jump over group end and return to 'Released' UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); // EXPECT focused cell to be 'Released' editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); // Go to release date UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); editedCell = grid.gridAPI.get_cell_by_index(0, 'ReleaseDate'); expect(editedCell.editMode).toBeTruthy(); })); it(`Should skip non-editable columns when all column features are enabled`, () => { fix.componentInstance.hiddenFlag = true; fix.componentInstance.pinnedFlag = true; fix.componentInstance.columnGroupingFlag = true; fix.detectChanges(); targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // Move from Downloads over hidden to Released in Column Group editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); // Move from pinned 'Released' (in Column Group) to unpinned 'Items' editedCell = grid.gridAPI.get_cell_by_index(0, 'Items'); expect(editedCell.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // Move back to pinned 'Released' (in Column Group) editedCell = grid.gridAPI.get_cell_by_index(0, 'Released'); expect(editedCell.editMode).toBeTruthy(); UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // Move back to pinned 'Downloads' editedCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); expect(editedCell.editMode).toBeTruthy(); }); it(`Should update row changes when focus overlay buttons on tabbing`, (async () => { grid.getColumnByName("ID").hidden = true; grid.tbody.nativeElement.focus(); fix.detectChanges(); targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); fix.detectChanges(); UIInteractions.simulateClickAndSelectEvent(targetCell); fix.detectChanges(); UIInteractions.triggerEventHandlerKeyDown('Enter', gridContent); fix.detectChanges(); // change first editable cell value targetCell.editValue = '500'; fix.detectChanges(); // go to Done UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); expect(GridFunctions.getRowEditingBannerText(fix)).toBe('You have 1 changes in this row and 1 hidden columns'); // go to last editable cell grid.rowEditTabs.first.handleTab(UIInteractions.getKeyboardEvent('keydown', 'tab', false, true)); fix.detectChanges(); await wait(DEBOUNCETIME); fix.detectChanges(); const currentEditCell = grid.gridAPI.get_cell_by_index(0, 'Test'); expect(currentEditCell.editMode).toBeTruthy(); expect(grid.headerContainer.getScroll().scrollLeft).toBeGreaterThan(0); // change last editable cell value currentEditCell.editValue = 'No test'; fix.detectChanges(); // move to Cancel UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); expect(GridFunctions.getRowEditingBannerText(fix)).toBe('You have 2 changes in this row and 1 hidden columns'); })); it(`Should show no row changes when changing the cell value to the original one`, () => { targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); fix.detectChanges(); const originalValue = targetCell.value; UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); // change first editable cell value targetCell.editValue = '500'; fix.detectChanges(); // go to next cell UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); expect(GridFunctions.getRowEditingBannerText(fix)).toBe('You have 1 changes in this row and 0 hidden columns'); // return to first editable cell UIInteractions.triggerEventHandlerKeyDown('tab', gridContent, false, true); fix.detectChanges(); // change cell value to the original one targetCell.editValue = originalValue; fix.detectChanges(); // go to next cell UIInteractions.triggerEventHandlerKeyDown('tab', gridContent); fix.detectChanges(); expect(GridFunctions.getRowEditingBannerText(fix)).toBe('You have 0 changes in this row and 0 hidden columns'); }); it(`Should focus last edited cell after click on editable buttons`, (async () => { targetCell = grid.gridAPI.get_cell_by_index(0, 'Downloads'); UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); // Scroll the grid GridFunctions.scrollLeft(grid, 750); // Focus done button const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); const doneButtonElementDebug = GridFunctions.getRowEditingDoneDebugElement(fix); doneButtonElement.focus(); fix.detectChanges(); expect(document.activeElement).toEqual(doneButtonElement); doneButtonElementDebug.triggerEventHandler('click', new Event('click')); fix.detectChanges(); expect(targetCell.active).toBeTruthy(); })); }); describe('Exit row editing', () => { let fix; let grid: IgxGridComponent; let cell: CellType; let cellElem: CellType; beforeEach(() => { fix = TestBed.createComponent(IgxGridRowEditingComponent); fix.detectChanges(); grid = fix.componentInstance.grid; cell = grid.getCellByColumn(0, 'ProductName'); cellElem = grid.gridAPI.get_cell_by_index(0, 'ProductName'); }); it(`Should call correct methods on clicking DONE and CANCEL buttons in row edit overlay`, () => { const mockEvent = new MouseEvent('click'); spyOn(grid.gridAPI.crudService, 'endEdit'); // put cell in edit mode cell.editMode = true; fix.detectChanges(); // ged CANCEL button and click it const cancelButtonElement = GridFunctions.getRowEditingCancelButton(fix); cancelButtonElement.dispatchEvent(mockEvent); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(false, mockEvent); cell.editMode = true; fix.detectChanges(); // ged DONE button and click it const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); doneButtonElement.dispatchEvent(mockEvent); fix.detectChanges(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true, mockEvent); }); it(`Should exit row editing AND do not commit when press Escape key on Done and Cancel buttons`, () => { const mockEvent = new KeyboardEvent('keydown', { key: 'escape' }); spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; fix.detectChanges(); // press Escape on Done button const doneButtonElement = GridFunctions.getRowEditingDoneButton(fix); // const doneButtonElementDebug = GridFunctions.getRowEditingDoneDebugElement(fix); doneButtonElement.dispatchEvent(mockEvent); fix.detectChanges(); const overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(cell.editMode).toEqual(false); expect(overlayContent).toBeFalsy(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(false, mockEvent); UIInteractions.simulateDoubleClickAndSelectEvent(cellElem); fix.detectChanges(); // press Escape on Cancel button const cancelButtonElement = GridFunctions.getRowEditingDoneButton(fix); cancelButtonElement.dispatchEvent(mockEvent); fix.detectChanges(); expect(cell.editMode).toEqual(false); expect(overlayContent).toBeFalsy(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(false, mockEvent); }); it(`Should exit row editing AND COMMIT on add row`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; grid.addRow({ ProductID: 99, ProductName: 'ADDED', InStock: true, UnitsInStock: 20000, OrderDate: new Date('2018-03-01') }); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true); expect(cell.editMode).toBeFalsy(); }); it(`Should exit row editing AND COMMIT on delete row`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; fix.detectChanges(); grid.deleteRow(grid.gridAPI.get_row_by_index(2).key); fix.detectChanges(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true); expect(cell.editMode).toBeFalsy(); }); it(`Should exit row editing AND DISCARD on filter`, () => { spyOn(grid.gridAPI.crudService, 'exitCellEdit').and.callThrough(); spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode // const cell = grid.getCellByColumn(0, 'ProductName'); cell.editMode = true; grid.filter('ProductName', 'a', IgxStringFilteringOperand.instance().condition('contains'), true); fix.detectChanges(); expect(grid.gridAPI.crudService.exitCellEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.exitCellEdit).toHaveBeenCalledWith(undefined); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(false); expect(cell.editMode).toBeFalsy(); }); it(`Should exit row editing AND DISCARD on sort`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); spyOn(grid.crudService, 'exitCellEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; cell.update('123'); grid.sort({ fieldName: 'ProductName', dir: SortingDirection.Asc, ignoreCase: true, strategy: DefaultSortingStrategy.instance() }); fix.detectChanges(); expect(cell.editMode).toBe(false); expect(cell.value).toBe('Aniseed Syrup'); // SORT does not submit expect(grid.crudService.exitCellEdit).toHaveBeenCalled(); expect(grid.crudService.endEdit).toHaveBeenCalled(); expect(grid.crudService.endEdit).toHaveBeenCalledWith(false); }); it(`Should exit row editing AND COMMIT on displayDensity change`, () => { grid.displayDensity = DisplayDensity.comfortable; fix.detectChanges(); cell.editMode = true; fix.detectChanges(); let overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); expect(cell.editMode).toBeTruthy(); grid.displayDensity = DisplayDensity.cosy; fix.detectChanges(); overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeFalsy(); expect(cell.editMode).toBeFalsy(); }); it(`Should NOT exit row editing on click on non-editable cell in same row`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; fix.detectChanges(); let overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); expect(cell.editMode).toBeTruthy(); const nonEditableCell = grid.gridAPI.get_cell_by_index(0, 'ProductID'); UIInteractions.simulateClickAndSelectEvent(nonEditableCell); fix.detectChanges(); expect(grid.gridAPI.crudService.endEdit).not.toHaveBeenCalled(); overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); expect(cell.editMode).toBeFalsy(); expect(nonEditableCell.editMode).toBeFalsy(); }); it(`Should exit row editing AND COMMIT on click on non-editable cell in other row`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; fix.detectChanges(); let overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); const nonEditableCell = grid.gridAPI.get_cell_by_index(2, 'ProductID'); UIInteractions.simulateClickAndSelectEvent(nonEditableCell); fix.detectChanges(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true, (jasmine.anything() as any)); overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeFalsy(); expect(cell.editMode).toBeFalsy(); }); it(`Should exit row editing AND COMMIT on click on editable cell in other row`, () => { spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); // put cell in edit mode cell.editMode = true; fix.detectChanges(); let overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); const otherEditableCell = grid.gridAPI.get_cell_by_index(2, 'ProductName'); UIInteractions.simulateClickAndSelectEvent(otherEditableCell); fix.detectChanges(); overlayContent = GridFunctions.getRowEditingOverlay(fix); expect(overlayContent).toBeTruthy(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalled(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true, jasmine.anything() as any); expect(cell.editMode).toBeFalsy(); expect(otherEditableCell.editMode).toBeTruthy(); }); it(`Should exit row editing AND DISCARD on ESC KEYDOWN`, () => { const targetCell = grid.gridAPI.get_cell_by_index(0, 'ProductName') as any; UIInteractions.simulateDoubleClickAndSelectEvent(targetCell); fix.detectChanges(); spyOn(grid.gridAPI.crudService, 'exitCellEdit').and.callThrough(); UIInteractions.triggerKeyDownEvtUponElem('escape', grid.tbody.nativeElement, true); fix.detectChanges(); expect(grid.gridAPI.crudService.exitCellEdit).toHaveBeenCalled(); expect(cell.editMode).toBeFalsy(); }); it(`Should exit edit mode when edited row is being deleted`, () => { const row = grid.gridAPI.get_row_by_index(0); spyOn(grid.gridAPI.crudService, 'endEdit').and.callThrough(); cell.editMode = true; fix.detectChanges(); expect(grid.rowEditingOverlay.collapsed).toBeFalsy(); row.delete(); fix.detectChanges(); expect(grid.rowEditingOverlay.collapsed).toBeTruthy(); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledTimes(1); expect(grid.gridAPI.crudService.endEdit).toHaveBeenCalledWith(true); }); }); describe('Integration', () => { let fix; let grid: IgxGridComponent; let cell: CellType; let cellElem: CellType; beforeEach(() => { fix = TestBed.createComponent(IgxGridRowEditingComponent); fix.detectChanges(); grid = fix.componentInstance.grid; cell = grid.getCellByColumn(0, 'ProductName'); ce