UNPKG

igniteui-angular-sovn

Version:

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

690 lines (540 loc) 29 kB
import { fakeAsync, flush, TestBed, tick } from '@angular/core/testing'; import { Validators } from '@angular/forms'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators' import { IgxInputDirective } from '../../directives/input/input.directive'; import { IgxTooltipTargetDirective } from '../../directives/tooltip/tooltip-target.directive'; import { configureTestSuite } from '../../test-utils/configure-suite'; import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec'; import { IgxGridCustomEditorsComponent, IgxGridValidationTestBaseComponent, IgxGridValidationTestCustomErrorComponent, IgxTreeGridValidationTestComponent } from '../../test-utils/grid-validation-samples.spec'; import { UIInteractions } from '../../test-utils/ui-interactions.spec'; import { IGridFormGroupCreatedEventArgs } from '../common/grid.interface'; import { IgxTreeGridComponent } from '../tree-grid/tree-grid.component'; import { IgxGridComponent } from './grid.component'; describe('IgxGrid - Validation #grid', () => { configureTestSuite((() => { return TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, IgxGridValidationTestBaseComponent, IgxGridValidationTestCustomErrorComponent, IgxGridCustomEditorsComponent, IgxTreeGridValidationTestComponent ] }); })); describe('Basic Validation - ', () => { let fixture; const $destroyer = new Subject<boolean>(); beforeEach(() => { fixture = TestBed.createComponent(IgxGridValidationTestBaseComponent); fixture.detectChanges(); }); afterEach(() => { UIInteractions.clearOverlay(); $destroyer.next(true); }); it('should allow setting built-in validators via template-driven configuration on the column', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const firstColumn = grid.columnList.first; const validators = firstColumn.validators; expect(validators.length).toBeGreaterThanOrEqual(3); const minValidator = validators.find(validator => validator['inputName'] === 'minlength'); const maxValidator = validators.find(validator => validator['inputName'] === 'maxlength'); const requiredValidator = validators.find(validator => validator['inputName'] === 'required'); expect(parseInt(minValidator['minlength'], 10)).toBe(4); expect(parseInt(maxValidator['maxlength'], 10)).toBe(8); expect(requiredValidator).toBeDefined(); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('asd'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //min length should be 4 GridFunctions.verifyCellValid(cell, false); cell.editMode = true; cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); }); it('should allow setting custom validators via template-driven configuration on the column', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const firstColumn = grid.columnList.first; const validators = firstColumn.validators; const customValidator = validators.find(validator => validator['forbiddenName']); expect(customValidator).toBeDefined(); expect(customValidator['forbiddenName']).toEqual('bob'); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('bob'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //the name should not contain bob GridFunctions.verifyCellValid(cell, false); cell.editMode = true; cell.update('valid'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); }); it('should allow setting validators on the exposed FormGroup object', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; grid.formGroupCreated.pipe(takeUntil($destroyer)).subscribe((args: IGridFormGroupCreatedEventArgs) => { const prodName = args.formGroup.get('ProductName'); prodName.addValidators(Validators.email); }); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //the name should be correct email GridFunctions.verifyCellValid(cell, false); expect(cell.formControl.errors.email).toBeTrue(); cell.editMode = true; cell.update('m@in.com'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); expect(cell.formControl.errors).toBeFalsy(); }); it('should allow setting validation triggers - "change" , "blur".', async () => { const grid = fixture.componentInstance.grid as IgxGridComponent; //changing validation triger to blur grid.validationTrigger = 'blur'; fixture.detectChanges(); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); fixture.detectChanges(); const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement; input.value = 'asd'; input.dispatchEvent(new Event('input')); fixture.detectChanges(); //the cell should be invalid after blur event is fired cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); input.dispatchEvent(new Event('blur')); fixture.detectChanges(); // fix.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, false); }); it('should mark invalid cell with igx-grid__td--invalid class and show the related error cell template', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('asd'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //min length should be 4 GridFunctions.verifyCellValid(cell, false); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long '); }); it('should show the error message on error icon hover and when the invalid cell becomes active.', fakeAsync(() => { const grid = fixture.componentInstance.grid as IgxGridComponent; let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); const input = fixture.debugElement.query(By.directive(IgxInputDirective)).nativeElement; input.value = 'asd'; input.dispatchEvent(new Event('input')); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //min length should be 4 GridFunctions.verifyCellValid(cell, false); GridSelectionFunctions.verifyCellActive(cell, true); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long '); cell.errorTooltip.first.close(); tick(); fixture.detectChanges(); expect(cell.errorTooltip.first.collapsed).toBeTrue(); const element = fixture.debugElement.query(By.directive(IgxTooltipTargetDirective)).nativeElement; element.dispatchEvent(new MouseEvent('mouseenter')); flush(); fixture.detectChanges(); expect(cell.errorTooltip.first.collapsed).toBeFalse(); })); it('should allow preventing edit mode for cell/row to end by canceling the related event if isValid event argument is false', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; grid.cellEdit.pipe(takeUntil($destroyer)).subscribe((args) => { if (!args.valid) { args.cancel = true; } }); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); const lastValue = cell.value; cell.formControl.setValue('asd'); fixture.detectChanges(); grid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); // should have been canceled and left in editmode because of non valid value // should not have updated the value GridFunctions.verifyCellValid(cell, false); expect(!!grid.gridAPI.crudService.rowInEditMode).toEqual(true); expect(grid.gridAPI.crudService.cellInEditMode).toEqual(true); expect(cell.value).toEqual(lastValue); cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); }); it('should trigger the validationStatusChange event on grid when validation status changes', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; spyOn(grid.validationStatusChange, "emit").and.callThrough(); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('asd'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); grid.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); expect(grid.validationStatusChange.emit).toHaveBeenCalledWith({ status: 'INVALID', owner: grid }); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); grid.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, true); expect(grid.validationStatusChange.emit).toHaveBeenCalledWith({ status: 'INVALID', owner: grid }); }); it('should return invalid transaction using the transaction service API', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('asd'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); let invalidRecords = grid.validation.getInvalid(); GridFunctions.verifyCellValid(cell, false); expect(invalidRecords[0].fields[1].status).toEqual('INVALID'); cell.editMode = true; cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); invalidRecords = grid.validation.getInvalid(); expect(invalidRecords.length).toEqual(0); }); it('should update formControl state when grid data is updated.', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const originalDataCopy = JSON.parse(JSON.stringify(grid.data)); grid.data = JSON.parse(JSON.stringify(grid.data)); let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('asd'); fixture.detectChanges(); grid.crudService.endEdit(true); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //min length should be 4 GridFunctions.verifyCellValid(cell, false); grid.data = originalDataCopy; fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); expect(cell.editValue).toBe(originalDataCopy[1].ProductName); }); it('should create formControl for dynamically added columns\' cells', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; expect(grid.columns.length).toBe(4); // edit the row prior to adding new column, so that the formGroup of the row is created let cell = grid.gridAPI.get_cell_by_visible_index(1, 3); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); fixture.detectChanges(); cell.update(100); grid.crudService.endEdit(true); fixture.detectChanges(); // add new column fixture.componentInstance.columns.push({ field: 'NewColumn', dataType: 'string' }); fixture.detectChanges(); expect(grid.columns.length).toBe(5); // edit the new field's cell of the previously edited row cell = grid.gridAPI.get_cell_by_visible_index(1, 4); // will throw if form control was not created UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('asd'); fixture.detectChanges(); grid.crudService.endEdit(true); fixture.detectChanges(); }); }); describe('Custom Validation - ', () => { let fixture; beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(IgxGridValidationTestCustomErrorComponent); fixture.detectChanges(); })); it('should allow setting custom validators via template-driven configuration on the column', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; let cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.update('bob'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); //bob cannot be the name GridFunctions.verifyCellValid(cell, false); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' This name is forbidden. '); cell.editMode = true; cell.update('test'); fixture.detectChanges(); cell = grid.gridAPI.get_cell_by_visible_index(1, 1); GridFunctions.verifyCellValid(cell, true); }); }); describe('Custom Editor Templates - ', () => { let fixture; beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(IgxGridCustomEditorsComponent); fixture.componentInstance.grid.batchEditing = true; fixture.detectChanges(); })); it('should trigger validation on change when using custom editor bound via formControl.', () => { // template bound via formControl const template = fixture.componentInstance.formControlTemplate; const grid = fixture.componentInstance.grid as IgxGridComponent; const col = grid.columns[1]; col.inlineEditorTemplate = template; fixture.detectChanges(); const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); const input = fixture.debugElement.query(By.css('input')); UIInteractions.clickAndSendInputElementValue(input, 'bob'); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long '); }); it('should trigger validation on change when using custom editor bound via editValue.', () => { // template bound via ngModel to editValue const template = fixture.componentInstance.modelTemplate; const grid = fixture.componentInstance.grid as IgxGridComponent; const col = grid.columns[1]; col.inlineEditorTemplate = template; fixture.detectChanges(); const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); const input = fixture.debugElement.query(By.css('input')); UIInteractions.clickAndSendInputElementValue(input, 'bob'); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long '); }); it('should trigger validation on blur when using custom editor bound via editValue.', () => { // template bound via ngModel to editValue const template = fixture.componentInstance.modelTemplate; const grid = fixture.componentInstance.grid as IgxGridComponent; const col = grid.columns[1]; col.inlineEditorTemplate = template; grid.validationTrigger = 'blur'; fixture.detectChanges(); const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); const input = fixture.debugElement.query(By.css('input')); UIInteractions.clickAndSendInputElementValue(input, 'bob'); fixture.detectChanges(); // invalid value is entered, but no blur has happened yet. // Hence validation state is still valid. GridFunctions.verifyCellValid(cell, true); expect(cell.errorTooltip.length).toBe(0); // exit edit mode grid.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); const erorrMessage = cell.errorTooltip.first.elementRef.nativeElement.children[0].textContent; expect(erorrMessage).toEqual(' Entry should be at least 4 character(s) long '); }); }); describe('Transactions integration - ', () => { let fixture; beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(IgxGridValidationTestBaseComponent); fixture.componentInstance.batchEditing = true; fixture.detectChanges(); })); it('should update validity when setting new value through grid API', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); grid.updateCell('IG', 2, 'ProductName'); grid.validation.markAsTouched(2); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); grid.transactions.undo(); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, true); grid.updateRow({ ProductID: 2, ProductName: '', SupplierID: 1, CategoryID: 1, QuantityPerUnit: '24 - 12 oz bottles', UnitPrice: '19.0000', UnitsInStock: 66, UnitsOnOrder: 40, ReorderLevel: 25, Discontinued: false, OrderDate: new Date('2003-03-17').toISOString(), OrderDate2: new Date('2003-03-17').toISOString() }, 2); grid.validation.markAsTouched(2); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); grid.transactions.undo(); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, true); grid.transactions.redo(); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); }); it('should update validation status when using undo/redo api', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('IG'); fixture.detectChanges(); grid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); grid.transactions.undo(); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, true); grid.transactions.redo(); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); grid.transactions.commit(grid.data); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); expect((grid.validation as any).getValidity().length).toEqual(1); grid.validation.clear(); fixture.detectChanges(); }); it('should not invalidate cleared number cell', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const cell = grid.gridAPI.get_cell_by_visible_index(1, 3); // Set cell to null, which should invalidate UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update(null); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(1); GridFunctions.verifyCellValid(cell, false); // Exit edit. CRUD service sets number value to 0 grid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(0); GridFunctions.verifyCellValid(cell, true); // Undo. Expect previous value grid.transactions.undo(); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(0); expect((grid.validation as any).getValidity().length).toEqual(1); GridFunctions.verifyCellValid(cell, true); // Redo. Expect value 0 grid.transactions.redo(); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(0); expect((grid.validation as any).getValidity().length).toEqual(1); GridFunctions.verifyCellValid(cell, true); grid.transactions.commit(grid.data); grid.validation.clear(); fixture.detectChanges(); expect((grid.validation as any).getValidity().length).toEqual(0); }); it('should not show errors when the row is deleted', () => { const grid = fixture.componentInstance.grid as IgxGridComponent; const cell = grid.gridAPI.get_cell_by_visible_index(1, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('IG'); fixture.detectChanges(); grid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(1); GridFunctions.verifyCellValid(cell, false); grid.deleteRow(2); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(0); GridFunctions.verifyCellValid(cell, true); grid.transactions.undo(); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(1); GridFunctions.verifyCellValid(cell, false); grid.transactions.redo(); fixture.detectChanges(); expect(grid.validation.getInvalid().length).toEqual(0); GridFunctions.verifyCellValid(cell, true); }); }); describe('TreeGrid integration - ', () => { let fixture; beforeEach(fakeAsync(() => { fixture = TestBed.createComponent(IgxTreeGridValidationTestComponent); fixture.componentInstance.batchEditing = true; fixture.detectChanges(); })); it('should allow setting built-in validators via template-driven and mark cell invalid', () => { const treeGrid = fixture.componentInstance.treeGrid as IgxTreeGridComponent; const cell = treeGrid.gridAPI.get_cell_by_visible_index(4, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('IG'); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); treeGrid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); }); it('should allow setting custom validators via template-driven and mark cell invalid', () => { const treeGrid = fixture.componentInstance.treeGrid as IgxTreeGridComponent; const cell = treeGrid.gridAPI.get_cell_by_visible_index(4, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('bob'); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); treeGrid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); GridFunctions.verifyCellValid(cell, false); }); it('should update validation status when using undo/redo/delete api', () => { const treeGrid = fixture.componentInstance.treeGrid as IgxTreeGridComponent; const cell = treeGrid.gridAPI.get_cell_by_visible_index(4, 1); UIInteractions.simulateDoubleClickAndSelectEvent(cell.element); cell.editMode = true; cell.update('IG'); fixture.detectChanges(); treeGrid.gridAPI.crudService.endEdit(true); fixture.detectChanges(); treeGrid.transactions.undo(); fixture.detectChanges(); expect(treeGrid.validation.getInvalid().length).toEqual(0); GridFunctions.verifyCellValid(cell, true); treeGrid.transactions.redo(); fixture.detectChanges(); expect(treeGrid.validation.getInvalid().length).toEqual(1); GridFunctions.verifyCellValid(cell, false); treeGrid.deleteRow(711); fixture.detectChanges(); expect(treeGrid.validation.getInvalid().length).toEqual(0); GridFunctions.verifyCellValid(cell, true); }); }); });