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
text/typescript
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