From 0a2f634ec8621411c9b3bebd9c0f80c8f98960c3 Mon Sep 17 00:00:00 2001 From: Tacho Date: Wed, 14 Aug 2019 14:57:02 +0300 Subject: [PATCH 1/7] fix(tooltip): del prevDefault in touchStart #5577 --- .../src/lib/directives/tooltip/tooltip.directive.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts index f2110901c18..70f4f02858f 100644 --- a/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts +++ b/projects/igniteui-angular/src/lib/directives/tooltip/tooltip.directive.ts @@ -350,7 +350,6 @@ export class IgxTooltipTargetDirective extends IgxToggleActionDirective implemen return; } - event.preventDefault(); this.showTooltip(); } From 029e69931dd12ff19b826c6c86a4f250f062b934 Mon Sep 17 00:00:00 2001 From: Nadia Robakova Date: Tue, 20 Aug 2019 16:11:03 +0300 Subject: [PATCH 2/7] chore(*): add base files for hgrid selection --- .../hierarchical-grid.integration.spec.ts | 19 -- .../hierarchical-grid.selection.spec.ts | 165 ++++++++++++++++++ .../hierarhical-grid-components.spec.ts | 91 ++++++++++ .../lib/test-utils/sample-test-data.spec.ts | 17 ++ 4 files changed, 273 insertions(+), 19 deletions(-) create mode 100644 projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts create mode 100644 projects/igniteui-angular/src/lib/test-utils/hierarhical-grid-components.spec.ts diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.integration.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.integration.spec.ts index 4a7a74b736d..7e16fd02209 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.integration.spec.ts @@ -387,25 +387,6 @@ describe('IgxHierarchicalGrid Integration', () => { expect(fChildCell.selected).toBe(true); })); - it('should retain selected row when filtering', fakeAsync(() => { - hierarchicalGrid.rowSelection = GridSelectionMode.multiple; - fixture.detectChanges(); - - const firstRow = hierarchicalGrid.getRowByIndex(0); - const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - - targetCheckbox.click(); - fixture.detectChanges(); - - hierarchicalGrid.filter('ID', '0', IgxStringFilteringOperand.instance().condition('contains'), true); - fixture.detectChanges(); - - expect(hierarchicalGrid.getRowByIndex(0).isSelected).toBeTruthy(); - const headerRow: HTMLElement = fixture.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckbox: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); - expect(headerCheckbox.indeterminate).toBeTruthy(); - })); - it('should show empty filter message when there are no records matching the filter', fakeAsync(() => { fixture.componentInstance.data = []; fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts new file mode 100644 index 00000000000..469561dd4df --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts @@ -0,0 +1,165 @@ +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { async, TestBed, tick, fakeAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { IgxHierarchicalGridModule } from './index'; +import { Component, ViewChild } from '@angular/core'; +import { IgxHierarchicalGridComponent } from './hierarchical-grid.component'; +import { IgxRowIslandComponent } from './row-island.component'; +import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec'; +import { SortingDirection } from '../../data-operations/sorting-expression.interface'; +import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; +import { IgxGridGroupByRowComponent, IgxColumnMovingDragDirective, IgxColumnComponent, GridSelectionMode } from '../grid'; +import { IgxHierarchicalRowComponent } from './hierarchical-row.component'; +import { IgxChildGridRowComponent } from './child-grid-row.component'; +import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; +import { take } from 'rxjs/operators'; +import { IgxHierarchicalTransactionServiceFactory } from './hierarchical-grid-base.component'; +import { IgxIconModule } from '../../icon'; +import { IgxHierarchicalGridTestBaseComponent, + IgxHierarchicalGridRowSelectionComponent } from '../../test-utils/hierarhical-grid-components.spec'; + +describe('IgxHierarchicalGrid selection', () => { + configureTestSuite(); + let fix; + let hierarchicalGrid: IgxHierarchicalGridComponent; + let rowIsland1; + let rowIsland2; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + IgxHierarchicalGridTestBaseComponent, + IgxHierarchicalGridRowSelectionComponent + ], + imports: [ + NoopAnimationsModule, IgxHierarchicalGridModule, IgxIconModule] + }).compileComponents(); + })); + + describe('Cell selection', () => { + beforeEach(async(() => { + fix = TestBed.createComponent(IgxHierarchicalGridTestBaseComponent); + fix.detectChanges(); + hierarchicalGrid = fix.componentInstance.hgrid; + rowIsland1 = fix.componentInstance.rowIsland; + rowIsland2 = fix.componentInstance.rowIsland2; + })); + + it('should select cells only in current grid with mouse drag.', () => { + + }); + + it('should select cells only in current grid keyboard.', () => { + + }); + + it('should be able to change cellSelection per rowIsland', () => { + + }); + + it('should be able to change cellSelection per rowIsland', () => { + + }); + + it('should be able to change cellSelection', () => { + + }); + + it('should allow only one cell to be selected in the whole hierarchical grid.', (async () => { + hierarchicalGrid.height = '500px'; + hierarchicalGrid.reflow(); + fix.detectChanges(); + + let firstRow = hierarchicalGrid.dataRowList.toArray()[0] as IgxHierarchicalRowComponent; + firstRow.nativeElement.children[0].click(); + fix.detectChanges(); + expect(firstRow.expanded).toBeTruthy(); + + let fCell = firstRow.cells.toArray()[0]; + + // select parent cell + fCell.nativeElement.focus(); + await wait(100); + fix.detectChanges(); + + expect(fCell.selected).toBeTruthy(); + + const childGrid = hierarchicalGrid.hgridAPI.getChildGrids(false)[0]; + const firstChildRow = childGrid.dataRowList.toArray()[0]; + const fChildCell = firstChildRow.cells.toArray()[0]; + + // select child cell + fChildCell.nativeElement.focus(); + await wait(100); + fix.detectChanges(); + + expect(fChildCell.selected).toBeTruthy(); + expect(fCell.selected).toBeFalsy(); + + // select parent cell + firstRow = hierarchicalGrid.dataRowList.toArray()[0] as IgxHierarchicalRowComponent; + fCell = firstRow.cells.toArray()[0]; + fCell.nativeElement.focus(); + await wait(100); + fix.detectChanges(); + expect(fChildCell.selected).toBeFalsy(); + expect(fCell.selected).toBeTruthy(); + })); + + }); + + describe('Row Selection', () => { + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(IgxHierarchicalGridRowSelectionComponent); + fix.detectChanges(); + hierarchicalGrid = fix.componentInstance.hgrid; + rowIsland1 = fix.componentInstance.rowIsland; + rowIsland2 = fix.componentInstance.rowIsland2; + })); + + it('should retain selected row when filtering', fakeAsync(() => { + hierarchicalGrid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + const firstRow = hierarchicalGrid.getRowByIndex(0); + const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); + + targetCheckbox.click(); + fix.detectChanges(); + + hierarchicalGrid.filter('ID', '0', IgxStringFilteringOperand.instance().condition('contains'), true); + fix.detectChanges(); + + expect(hierarchicalGrid.getRowByIndex(0).isSelected).toBeTruthy(); + const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); + const headerCheckbox: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); + expect(headerCheckbox.indeterminate).toBeTruthy(); + })); + + it('should be able to change rowSelection per rowIsland', () => { + + }); + + it('should be able to change rowSelection', () => { + + }); + + it('should be able to select/deselect all rows', () => { + + }); + + it('should have correct header checkbox state when selecting rows', () => { + + }); + + it('should remain selected rows when change page', () => { + + }); + + it('should be able to hideRowSelectors ', () => { + + }); + }); +}); diff --git a/projects/igniteui-angular/src/lib/test-utils/hierarhical-grid-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/hierarhical-grid-components.spec.ts new file mode 100644 index 00000000000..7f84f180e20 --- /dev/null +++ b/projects/igniteui-angular/src/lib/test-utils/hierarhical-grid-components.spec.ts @@ -0,0 +1,91 @@ +import { Component, ViewChild, OnInit } from '@angular/core'; +import { IgxTreeGridComponent } from '../grids/tree-grid/tree-grid.component'; +import { SampleTestData } from './sample-test-data.spec'; +import { IgxNumberSummaryOperand, IgxSummaryResult, IgxColumnComponent } from '../grids'; +import { IgxGridTransaction } from '../grids/grid-base.component'; +import { IgxTransactionService } from '../services/transaction/igx-transaction'; +import { IgxHierarchicalTransactionService } from '../services/transaction/igx-hierarchical-transaction'; +import { DisplayDensity } from '../core/displayDensity'; +import { IgxHierarchicalTransactionServiceFactory, IgxHierarchicalGridComponent, IgxRowIslandComponent } from 'igniteui-angular'; + +@Component({ + template: ` + + + + + + + + + +
+ ID + lock +
+
+
+ + + + + + + + + + + +
+
`, + providers: [ IgxHierarchicalTransactionServiceFactory ] +}) +export class IgxHierarchicalGridTestBaseComponent { + public data; + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hgrid: IgxHierarchicalGridComponent; + @ViewChild('rowIsland', { read: IgxRowIslandComponent, static: true }) public rowIsland: IgxRowIslandComponent; + @ViewChild('rowIsland2', { read: IgxRowIslandComponent, static: true }) public rowIsland2: IgxRowIslandComponent; + + constructor() { + // 3 level hierarchy + this.data = SampleTestData.generateHGridData(40, 3); + } + + pinColumn(column: IgxColumnComponent) { + column.pinned ? column.unpin() : column.pin(); + } +} + +@Component({ + template: ` + + + + + + + + + + + + + + + `, + providers: [ IgxHierarchicalTransactionServiceFactory ] +}) +export class IgxHierarchicalGridRowSelectionComponent { + public data; + @ViewChild('hierarchicalGrid', { read: IgxHierarchicalGridComponent, static: true }) public hgrid: IgxHierarchicalGridComponent; + @ViewChild('rowIsland', { read: IgxRowIslandComponent, static: true }) public rowIsland: IgxRowIslandComponent; + @ViewChild('rowIsland2', { read: IgxRowIslandComponent, static: true }) public rowIsland2: IgxRowIslandComponent; + + constructor() { + // 3 level hierarchy + this.data = SampleTestData.generateHGridData(40, 3); + } +} diff --git a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts index dce75b476cc..22f58bf9fc0 100644 --- a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts @@ -1557,6 +1557,23 @@ export class SampleTestData { return undefined; } } + + /* Generates hierahical data */ + public static generateHGridData(count: number, level: number, parendID?) { + const prods = []; + const currLevel = level; + let children; + for (let i = 0; i < count; i++) { + const rowID = parendID ? parendID + i : i.toString(); + if (level > 0 ) { + children = this.generateData(count / 2 , currLevel - 1, rowID); + } + prods.push({ + ID: rowID, ChildLevels: currLevel, ProductName: 'Product: A' + i, 'Col1': i, + 'Col2': i, 'Col3': i, childData: children, childData2: children }); + } + return prods; + } } // tslint:enable:quotemark From e0de23e7386874415fc913a60118d24a7e76ac08 Mon Sep 17 00:00:00 2001 From: ddincheva Date: Tue, 20 Aug 2019 17:15:46 +0300 Subject: [PATCH 3/7] chore(*): refactor grid-selection service --- .../src/lib/core/grid-selection.ts | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/grid-selection.ts b/projects/igniteui-angular/src/lib/core/grid-selection.ts index 0e474f9d7fb..2963f17ef77 100644 --- a/projects/igniteui-angular/src/lib/core/grid-selection.ts +++ b/projects/igniteui-angular/src/lib/core/grid-selection.ts @@ -549,23 +549,17 @@ export class IgxGridSelectionService { } clearRowSelection(event?) { - this.allRowsSelected = false; const removedRec = this.isFilteringApplied() ? this.getRowIDs(this.allData).filter(rID => this.isRowSelected(rID)) : this.getSelectedRows(); const newSelection = this.isFilteringApplied() ? this.getSelectedRows().filter(x => !removedRec.includes(x)) : []; - if (this.emitRowSelectionEvent(newSelection, [], removedRec, event)) { return; } - - this.isFilteringApplied() ? this.deselectRowsWithNoEvent(removedRec) : this.rowSelection.clear(); + this.emitRowSelectionEvent(newSelection, [], removedRec, event); } selectAllRows(event?) { - this.allRowsSelected = true; - const allRowIDs = this.getRowIDs(this.allData); const addedRows = this.rowSelection.size ? allRowIDs.filter((rID) => !this.isRowSelected(rID)) : allRowIDs; - if (this.emitRowSelectionEvent(allRowIDs, addedRows, [], event)) { return; } - this.selectRowsWithNoEvent(addedRows); + this.emitRowSelectionEvent(allRowIDs, addedRows, [], event); } selectRowbyID(rowID, clearPrevSelection?, event?) { @@ -574,20 +568,14 @@ export class IgxGridSelectionService { const newSelection = clearPrevSelection ? [rowID] : [...this.getSelectedRows(), rowID]; const removed = clearPrevSelection ? this.getSelectedRows() : []; - if (this.emitRowSelectionEvent(newSelection, [rowID], removed, event)) { return; } - - if (clearPrevSelection) { this.rowSelection.clear(); } - this.rowSelection.add(rowID); - this.allRowsSelected = undefined; + this.emitRowSelectionEvent(newSelection, [rowID], removed, event); } deselectRow(rowID, event?) { if (!this.isRowSelected(rowID)) { return; } const newSelection = this.getSelectedRows().filter(r => r !== rowID); if (this.rowSelection.size && this.rowSelection.has(rowID)) { - if (this.emitRowSelectionEvent(newSelection, [], [rowID], event)) { return; } - this.rowSelection.delete(rowID); - this.allRowsSelected = undefined; + this.emitRowSelectionEvent(newSelection, [], [rowID], event); } } @@ -603,7 +591,7 @@ export class IgxGridSelectionService { } isRowSelected(rowID) { - return this.rowSelection.has(rowID); + return this.rowSelection.size > 0 && this.rowSelection.has(rowID); } selectMultipleRows(rowID, rowData, event?) { @@ -617,19 +605,19 @@ export class IgxGridSelectionService { const currIndex = gridData.indexOf(this.getRowDataByID(lastSelectedRowID)); const newIndex = gridData.indexOf(rowData); const rows = gridData.slice(Math.min(currIndex, newIndex), Math.max(currIndex, newIndex) + 1); - const added = this.getRowIDs(rows).filter(rID => !this.isRowSelected(rID)); - if (this.emitRowSelectionEvent(this.getSelectedRows().concat(added), added, [], event)) { return; } - added.forEach(id => this.rowSelection.add(id)); + const added = this.getRowIDs(rows).filter(rID => !this.isRowSelected(rID)); + const newSelection = this.getSelectedRows().concat(added); + this.emitRowSelectionEvent(newSelection, added, [], event); } areAllRowSelected() { - if (!this.hasData() ) { return false; } + if (!this.grid.data) { return false; } if (this.allRowsSelected !== undefined) { return this.allRowsSelected; } const dataItemsID = this.getRowIDs(this.allData); - return this.allRowsSelected = this.rowSelection.size >= this.allData.length && + return this.allRowsSelected = Math.min(this.rowSelection.size, dataItemsID.length) > 0 && new Set(Array.from(this.rowSelection.values()).concat(dataItemsID)).size === this.rowSelection.size; } @@ -642,16 +630,14 @@ export class IgxGridSelectionService { public emitRowSelectionEvent(newSelection, added, removed, event?): boolean { const currSelection = this.getSelectedRows(); if (this.areEquelCollections(currSelection, newSelection)) { return; } - const copy = Array.from(newSelection); + const args = {oldSelection: currSelection, newSelection: newSelection, added: added, removed: removed, event: event, cancel: false}; this.grid.onRowSelectionChange.emit(args); if (args.cancel && event.checkbox) { event.checkbox.checked = !event.checkbox.checked; } - if (!this.areEquelCollections(copy, newSelection)) { - this.selectRowsWithNoEvent(newSelection, true); - return true; - } - return args.cancel; + if (args.cancel) { return; } + this.rowSelection.clear(); + this.selectRowsWithNoEvent(newSelection); } @@ -684,10 +670,6 @@ export class IgxGridSelectionService { return this.grid.filteringExpressionsTree.filteringOperands.length > 0; } - private hasData() { - return this.grid.isDefined(this.grid.data) && this.allData.length > 0; - } - private isRowDeleted(rowID) { return this.grid.gridAPI.row_deleted_transaction(rowID); } From d7d449005ed57a12bfecf65f6e860f66dce13832 Mon Sep 17 00:00:00 2001 From: ddincheva Date: Wed, 21 Aug 2019 10:07:18 +0300 Subject: [PATCH 4/7] refactor(IgxGrid): change headerCB visibility according rowSelectionMode #4989 --- .../src/lib/core/grid-selection.ts | 32 +++++++++---------- .../src/lib/grids/grid/grid.component.html | 4 +-- .../hierarchical-grid.component.html | 2 +- .../grids/tree-grid/tree-grid.component.html | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/projects/igniteui-angular/src/lib/core/grid-selection.ts b/projects/igniteui-angular/src/lib/core/grid-selection.ts index 2963f17ef77..e7dc3a49ea2 100644 --- a/projects/igniteui-angular/src/lib/core/grid-selection.ts +++ b/projects/igniteui-angular/src/lib/core/grid-selection.ts @@ -1,7 +1,6 @@ import { Injectable, EventEmitter, NgZone } from '@angular/core'; import { IGridEditEventArgs } from '../grids/grid-base.component'; - export interface GridSelectionRange { rowStart: number; rowEnd: number; @@ -560,7 +559,7 @@ export class IgxGridSelectionService { const addedRows = this.rowSelection.size ? allRowIDs.filter((rID) => !this.isRowSelected(rID)) : allRowIDs; this.emitRowSelectionEvent(allRowIDs, addedRows, [], event); - } + } selectRowbyID(rowID, clearPrevSelection?, event?) { if (this.grid.rowSelection === 'none' || this.isRowDeleted(rowID)) { return; } @@ -601,8 +600,8 @@ export class IgxGridSelectionService { return; } const gridData = this.allData; - const lastSelectedRowID = this.getSelectedRows()[this.rowSelection.size - 1]; - const currIndex = gridData.indexOf(this.getRowDataByID(lastSelectedRowID)); + const lastRowID = this.getSelectedRows()[this.rowSelection.size - 1]; + const currIndex = gridData.indexOf(this.getRowDataByID(lastRowID)); const newIndex = gridData.indexOf(rowData); const rows = gridData.slice(Math.min(currIndex, newIndex), Math.max(currIndex, newIndex) + 1); @@ -623,7 +622,7 @@ export class IgxGridSelectionService { hasSomeRowSelected() { const filteredData = this.isFilteringApplied() ? - this.getRowIDs(this.grid.filteredData).some(rID => this.isRowSelected(rID)) : true; + this.getRowIDs(this.grid.filteredData).some(rID => this.isRowSelected(rID)) : true; return this.rowSelection.size > 0 && filteredData && !this.areAllRowSelected(); } @@ -631,16 +630,18 @@ export class IgxGridSelectionService { const currSelection = this.getSelectedRows(); if (this.areEquelCollections(currSelection, newSelection)) { return; } - const args = {oldSelection: currSelection, newSelection: newSelection, - added: added, removed: removed, event: event, cancel: false}; + const args = { + oldSelection: currSelection, newSelection: newSelection, + added: added, removed: removed, event: event, cancel: false + }; this.grid.onRowSelectionChange.emit(args); - if (args.cancel && event.checkbox) { event.checkbox.checked = !event.checkbox.checked; } - if (args.cancel) { return; } - this.rowSelection.clear(); - this.selectRowsWithNoEvent(newSelection); + if (args.cancel) { + if (args.event && args.event.checkbox) { event.checkbox.checked = !event.checkbox.checked; } + return; + } + this.selectRowsWithNoEvent(args.newSelection, true); } - public getRowDataByID(rowID) { if (!this.grid.primaryKey) { return rowID; } const rowIndex = this.getRowIDs(this.grid.gridAPI.get_all_data()).indexOf(rowID); @@ -656,14 +657,13 @@ export class IgxGridSelectionService { } public get allData() { - const gridAPI = this.grid.gridAPI; const allData = this.isFilteringApplied() || this.grid.sortingExpressions.length ? - this.grid.filteredSortedData : gridAPI.get_all_data(); - return allData.filter(rData => !this.isRowDeleted(gridAPI.get_row_id(rData))); + this.grid.filteredSortedData : this.grid.gridAPI.get_all_data(); + return allData.filter(rData => !this.isRowDeleted(this.grid.gridAPI.get_row_id(rData))); } private areEquelCollections(first, second) { - return first.length !== second.length || new Set(first.concat(second)).size !== first.length ? false : true; + return first.length !== second.length || new Set(first.concat(second)).size !== first.length ? false : true; } private isFilteringApplied() { diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html index 20e630bd94e..bed9a6d88c9 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -67,8 +67,8 @@
- +
diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html index c28d5086e15..1973608c1b9 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.html @@ -34,7 +34,7 @@
-
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html index 429704041be..2f37fcad80d 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.html @@ -24,7 +24,7 @@
-
From d12ff010a7902584f4daa491fdb64740ceb624c6 Mon Sep 17 00:00:00 2001 From: IvayloG Date: Wed, 21 Aug 2019 13:26:48 +0300 Subject: [PATCH 5/7] Add combo required asterisk #4918 --- .../src/lib/combo/combo.component.html | 4 +- .../src/lib/combo/combo.component.spec.ts | 96 ++++++++++++++++++- .../src/lib/combo/combo.component.ts | 78 ++++++++------- .../src/lib/select/select.component.ts | 4 +- src/app/combo/combo.sample.html | 24 +++++ src/app/combo/combo.sample.ts | 33 ++++++- 6 files changed, 196 insertions(+), 43 deletions(-) diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.html b/projects/igniteui-angular/src/lib/combo/combo.component.html index fbd4ee6c3ff..6584ac0695d 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.html +++ b/projects/igniteui-angular/src/lib/combo/combo.component.html @@ -14,7 +14,7 @@ {{ item[key] }} - + @@ -81,4 +81,4 @@ - \ No newline at end of file + diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts index f5699784a5d..d41d57a1e21 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.spec.ts @@ -5,7 +5,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SortingDirection } from '../data-operations/sorting-expression.interface'; import { IgxToggleModule } from '../directives/toggle/toggle.directive'; import { IgxComboItemComponent } from './combo-item.component'; -import { IgxComboComponent, IgxComboModule, IgxComboState, IComboSelectionChangeEventArgs } from './combo.component'; +import { IgxComboComponent, IgxComboModule, IComboSelectionChangeEventArgs, IgxComboState } from './combo.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { FormGroup, FormControl, Validators, FormBuilder, ReactiveFormsModule, FormsModule } from '@angular/forms'; import { IForOfState } from '../directives/for-of/for_of.directive'; @@ -17,6 +17,7 @@ import { configureTestSuite } from '../test-utils/configure-suite'; import { IgxDropDownItemBase } from '../drop-down/drop-down-item.base'; import { DisplayDensity, DisplayDensityToken } from '../core/density'; import { AbsoluteScrollStrategy, ConnectedPositioningStrategy } from '../services/index'; +import { IgxInputState } from '../directives/input/input.directive'; const CSS_CLASS_COMBO = 'igx-combo'; const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down'; @@ -40,6 +41,7 @@ const CSS_CLASS_INPUTGROUP = 'igx-input-group'; const CSS_CLASS_INPUTGROUP_WRAPPER = 'igx-input-group__wrapper'; const CSS_CLASS_INPUTGROUP_BUNDLE = 'igx-input-group__bundle'; const CSS_CLASS_INPUTGROUP_MAINBUNDLE = 'igx-input-group__bundle-main'; +const CSS_CLASS_INPUTGROUP_REQUIRED = 'igx-input-group--required'; const CSS_CLASS_INPUTGROUP_BORDER = 'igx-input-group__border'; const CSS_CLASS_HEADER = 'header-class'; const CSS_CLASS_FOOTER = 'footer-class'; @@ -76,7 +78,8 @@ describe('igxCombo', () => { IgxComboFormComponent, SimpleBindComboComponent, DensityParentComponent, - DensityInputComponent + DensityInputComponent, + IgxComboInTemplatedFormComponent ], imports: [ IgxComboModule, @@ -3048,7 +3051,6 @@ describe('igxCombo', () => { }); describe('Form control tests: ', () => { - it('Should properly initialize when used as a form control', fakeAsync(() => { const fix = TestBed.createComponent(IgxComboFormComponent); fix.detectChanges(); @@ -3060,21 +3062,26 @@ describe('igxCombo', () => { expect(combo.selectedItems().length).toEqual(1); expect(combo.selectedItems()[0].field).toEqual('Connecticut'); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); const clearButton = fix.debugElement.query(By.css('.' + CSS_CLASS_CLEARBUTTON)).nativeElement; clearButton.click(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INVALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID); combo.onBlur(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INVALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID); combo.selectItems([combo.dropdown.items[0], combo.dropdown.items[1]]); expect(combo.valid).toEqual(IgxComboState.VALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.VALID); combo.onBlur(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); })); it('Should properly initialize when used as a form control - without validators', fakeAsync(() => { @@ -3090,21 +3097,26 @@ describe('igxCombo', () => { expect(combo.selectedItems().length).toEqual(1); expect(combo.selectedItems()[0].field).toEqual('Connecticut'); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); const clearButton = fix.debugElement.query(By.css('.' + CSS_CLASS_CLEARBUTTON)).nativeElement; clearButton.click(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); combo.onBlur(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); combo.selectItems([combo.dropdown.items[0], combo.dropdown.items[1]]); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); combo.onBlur(); fix.detectChanges(); expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); })); it('Should be possible to be enabled/disabled when used as a form control', () => { @@ -3196,6 +3208,36 @@ describe('igxCombo', () => { fixture.detectChanges(); expect(fixture.componentInstance.comboSelectedItems).toEqual([...data].splice(1, 3)); })); + + it('Should properly initialize when used in a Template form control', fakeAsync (() => { + const fix = TestBed.createComponent(IgxComboInTemplatedFormComponent); + fix.detectChanges(); + tick(); + + const combo = fix.componentInstance.testCombo; + expect(combo.valid).toEqual(IgxComboState.INITIAL); + expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL); + const inputGroupRequired = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUTGROUP_REQUIRED)); + expect(inputGroupRequired).toBeDefined(); + combo.onBlur(); + fix.detectChanges(); + tick(); + expect(combo.valid).toEqual(IgxComboState.INVALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID); + + combo.selectAllItems(); + fix.detectChanges(); + tick(); + expect(combo.valid).toEqual(IgxComboState.VALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.VALID); + + const clearButton = fix.debugElement.query(By.css('.' + CSS_CLASS_CLEARBUTTON)).nativeElement; + clearButton.click(); + fix.detectChanges(); + tick(); + expect(combo.valid).toEqual(IgxComboState.INVALID); + expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID); + })); }); describe('Combo - Display Density', () => { @@ -3553,6 +3595,54 @@ class IgxComboFormComponent { onSubmitTemplateBased() { } } +@Component({ + template: ` +
+ + + +
+` +}) +class IgxComboInTemplatedFormComponent { + @ViewChild('testCombo', { read: IgxComboComponent, static: true }) testCombo: IgxComboComponent; + public items: any[] = []; + public values: Array; + + constructor() { + const division = { + 'New England 01': ['Connecticut', 'Maine', 'Massachusetts'], + 'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'], + 'Mid-Atlantic': ['New Jersey', 'New York', 'Pennsylvania'], + 'East North Central 02': ['Michigan', 'Ohio', 'Wisconsin'], + 'East North Central 01': ['Illinois', 'Indiana'], + 'West North Central 01': ['Missouri', 'Nebraska', 'North Dakota', 'South Dakota'], + 'West North Central 02': ['Iowa', 'Kansas', 'Minnesota'], + 'South Atlantic 01': ['Delaware', 'Florida', 'Georgia', 'Maryland'], + 'South Atlantic 02': ['North Carolina', 'South Carolina', 'Virginia'], + 'South Atlantic 03': ['District of Columbia', 'West Virginia'], + 'East South Central 01': ['Alabama', 'Kentucky'], + 'East South Central 02': ['Mississippi', 'Tennessee'], + 'West South Central': ['Arkansas', 'Louisiana', 'Oklahome', 'Texas'], + 'Mountain': ['Arizona', 'Colorado', 'Idaho', 'Montana', 'Nevada', 'New Mexico', 'Utah', 'Wyoming'], + 'Pacific 01': ['Alaska', 'California'], + 'Pacific 02': ['Hawaii', 'Oregon', 'Washington'] + }; + const keys = Object.keys(division); + for (const key of keys) { + division[key].map((e) => { + this.items.push({ + field: e, + region: key.substring(0, key.length - 3) + }); + }); + } + } +} @Injectable() export class LocalService { public getData() { diff --git a/projects/igniteui-angular/src/lib/combo/combo.component.ts b/projects/igniteui-angular/src/lib/combo/combo.component.ts index f0e3ee40a5c..0cc3722f68a 100644 --- a/projects/igniteui-angular/src/lib/combo/combo.component.ts +++ b/projects/igniteui-angular/src/lib/combo/combo.component.ts @@ -2,7 +2,7 @@ import { ConnectedPositioningStrategy } from './../services/overlay/position/con import { CommonModule } from '@angular/common'; import { AfterViewInit, ChangeDetectorRef, Component, ContentChild, ElementRef, EventEmitter, HostBinding, HostListener, - Input, NgModule, OnInit, OnDestroy, Output, TemplateRef, ViewChild, Optional, Inject, Injector, forwardRef + Input, NgModule, OnInit, OnDestroy, Output, TemplateRef, ViewChild, Optional, Inject, Injector, forwardRef, Type } from '@angular/core'; import { IgxComboItemDirective, @@ -14,7 +14,7 @@ import { IgxComboToggleIconDirective, IgxComboClearIconDirective } from './combo.directives'; -import { FormsModule, ReactiveFormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms'; import { IgxCheckboxModule } from '../checkbox/checkbox.component'; import { IgxSelectionAPIService } from '../core/selection'; import { cloneArray, CancelableEventArgs, CancelableBrowserEventArgs } from '../core/utils'; @@ -27,7 +27,7 @@ import { IgxRippleModule } from '../directives/ripple/ripple.directive'; import { IgxToggleModule } from '../directives/toggle/toggle.directive'; import { IgxButtonModule } from '../directives/button/button.directive'; import { IgxDropDownModule } from '../drop-down/index'; -import { IgxInputGroupModule } from '../input-group/input-group.component'; +import { IgxInputGroupModule, IgxInputGroupComponent } from '../input-group/input-group.component'; import { IgxComboItemComponent } from './combo-item.component'; import { IgxComboDropDownComponent } from './combo-dropdown.component'; import { IgxComboFilterConditionPipe, IgxComboFilteringPipe, IgxComboGroupingPipe, IgxComboSortingPipe } from './combo.pipes'; @@ -41,6 +41,7 @@ import { IgxComboAddItemComponent } from './combo-add-item.component'; import { IgxComboAPIService } from './combo.api'; import { EditorProvider } from '../core/edit-provider'; import { take } from 'rxjs/operators'; +import { IgxInputState, IgxInputDirective } from '../directives/input/input.directive'; /** * @hidden @@ -72,15 +73,15 @@ export enum IgxComboState { /** * Combo with initial state. */ - INITIAL, + INITIAL = IgxInputState.INITIAL, /** * Combo with valid state. */ - VALID, + VALID = IgxInputState.VALID, /** * Combo with invalid state. */ - INVALID + INVALID = IgxInputState.INVALID } /** Event emitted when an igx-combo's selection is changing */ @@ -163,13 +164,14 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas excludePositionTarget: true }; private _value = ''; + private _valid = IgxComboState.INITIAL; constructor( protected elementRef: ElementRef, protected cdr: ChangeDetectorRef, protected selection: IgxSelectionAPIService, protected comboAPI: IgxComboAPIService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions, - @Optional() private injector: Injector) { + @Optional() private _injector: Injector) { super(_displayDensityOptions); this.comboAPI.register(this); } @@ -197,6 +199,12 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas @Input() public overlaySettings: OverlaySettings = null; + /** @hidden @internal */ + @ViewChild('inputGroup', { read: IgxInputGroupComponent, static: true }) public inputGroup: IgxInputGroupComponent; + + /** @hidden @internal */ + @ViewChild('comboInput', { read: IgxInputDirective, static: true }) public comboInput: IgxInputDirective; + /** * @hidden @internal */ @@ -209,12 +217,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas @ViewChild('searchInput', { static: false }) public searchInput: ElementRef = null; - /** - * @hidden @internal - */ - @ViewChild('comboInput', { static: true }) - public comboInput: ElementRef = null; - /** * @hidden @internal */ @@ -530,22 +532,6 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas @Input() public width: string; - /** - * @hidden @internal - */ - @HostBinding('class.igx-input-group--valid') - public get validClass(): boolean { - return this.valid === IgxComboState.VALID; - } - - /** - * @hidden @internal - */ - @HostBinding('class.igx-input-group--invalid') - public get invalidClass(): boolean { - return this.valid === IgxComboState.INVALID; - } - /** * @hidden @internal */ @@ -832,18 +818,29 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas public type = 'box'; /** - * Gets/Sets if control is valid, when used in a form + * Gets if control is valid, when used in a form * * ```typescript * // get * let valid = this.combo.valid; * ``` + * */ + public get valid(): IgxComboState { + return this._valid; + } + + /** + * Sets if control is valid, when used in a form + * * ```typescript * // set * this.combo.valid = IgxComboState.INVALID; * ``` */ - public valid: IgxComboState = IgxComboState.INITIAL; + public set valid(valid: IgxComboState) { + this._valid = valid; + this.comboInput.valid = IgxInputState[IgxComboState[valid]]; + } /** * @hidden @internal @@ -1245,7 +1242,16 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas protected onStatusChanged = () => { if ((this.ngControl.control.touched || this.ngControl.control.dirty) && (this.ngControl.control.validator || this.ngControl.control.asyncValidator)) { - this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID; + this.valid = this.ngControl.valid ? IgxComboState.VALID : IgxComboState.INVALID; + } + this.manageRequiredAsterisk(); + } + + protected manageRequiredAsterisk(): void { + if (this.ngControl && this.ngControl.control.validator) { + // Run the validation with empty object to check if required is enabled. + const error = this.ngControl.control.validator({} as AbstractControl); + this.inputGroup.isRequired = error && error.required; } } @@ -1256,9 +1262,9 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas if (this.collapsed) { if (this.ngControl && !this.ngControl.valid) { this.valid = IgxComboState.INVALID; - } else { + } else { this.valid = IgxComboState.INITIAL; - } + } } } @@ -1274,7 +1280,7 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas * @hidden @internal */ public ngOnInit() { - this.ngControl = this.injector.get(NgControl, null); + this.ngControl = this._injector.get(NgControl as Type, null); this._overlaySettings.positionStrategy.settings.target = this.elementRef.nativeElement; this.selection.set(this.id, new Set()); } @@ -1287,6 +1293,8 @@ export class IgxComboComponent extends DisplayDensityBase implements IgxComboBas if (this.ngControl) { this.ngControl.statusChanges.pipe(takeUntil(this.destroy$)).subscribe(this.onStatusChanged); + this.manageRequiredAsterisk(); + this.cdr.detectChanges(); } } diff --git a/projects/igniteui-angular/src/lib/select/select.component.ts b/projects/igniteui-angular/src/lib/select/select.component.ts index b2aa8744479..7898ac83838 100644 --- a/projects/igniteui-angular/src/lib/select/select.component.ts +++ b/projects/igniteui-angular/src/lib/select/select.component.ts @@ -2,7 +2,7 @@ import { IgxInputDirective, IgxInputState } from './../directives/input/input.di import { Component, ContentChildren, forwardRef, QueryList, ViewChild, Input, ContentChild, AfterContentInit, HostBinding, Directive, TemplateRef, ElementRef, ChangeDetectorRef, Optional, - Injector, OnInit, AfterViewInit, OnDestroy, Inject + Injector, OnInit, AfterViewInit, OnDestroy, Inject, Type } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl, AbstractControl } from '@angular/forms'; @@ -355,7 +355,7 @@ export class IgxSelectComponent extends IgxDropDownComponent implements IgxSelec * @hidden @internal */ public ngOnInit() { - this.ngControl = this._injector.get(NgControl, null); + this.ngControl = this._injector.get(NgControl as Type, null); } /** diff --git a/src/app/combo/combo.sample.html b/src/app/combo/combo.sample.html index cfe5faf4c45..76c55210802 100644 --- a/src/app/combo/combo.sample.html +++ b/src/app/combo/combo.sample.html @@ -66,6 +66,30 @@
In Region {{display.region}}
+
+
Template Form
+
+ + Please select the states you've visited + +
+
+
+
Reactive Form
+
+
+ + + +
+
+

Display Density

diff --git a/src/app/combo/combo.sample.ts b/src/app/combo/combo.sample.ts index 75d8276ee43..d309a18f93a 100644 --- a/src/app/combo/combo.sample.ts +++ b/src/app/combo/combo.sample.ts @@ -5,6 +5,7 @@ import { IgxComboComponent, IComboSelectionChangeEventArgs, } from 'igniteui-angular'; import { take } from 'rxjs/operators'; import { cloneDeep } from 'lodash'; +import { FormGroup, Validators, FormControl, FormBuilder } from '@angular/forms'; const primitive = ['1', '2', '3', '4', '5', '6']; const complex = [{ @@ -43,6 +44,9 @@ export class ComboSampleComponent implements OnInit, AfterViewInit { public filterableFlag = true; public customValuesFlag = true; public items: any[] = []; + public values1: Array; + public values2: Array; + public valueKeyVar = 'field'; public currentDataType = ''; @ViewChild('customItemTemplate', { read: TemplateRef, static: true }) @@ -53,8 +57,35 @@ export class ComboSampleComponent implements OnInit, AfterViewInit { cosy = DisplayDensity.cosy; compact = DisplayDensity.compact; + public genres = []; + public user: FormGroup; + constructor(fb: FormBuilder) { + this.user = fb.group({ + date: [''], + dateTime: [''], + email: ['', Validators.required], + fullName: new FormControl('', Validators.required), + genres: ['', Validators.required], + movie: ['', Validators.required], + phone: [''] + }); + + this.genres = [ + { type: 'Action' , movies: ['The Matrix', 'Kill Bill: Vol.1', 'The Dark Knight Rises']}, + { type: 'Adventure' , movies: ['Interstellar', 'Inglourious Basterds', 'Inception']}, + // tslint:disable-next-line:object-literal-sort-keys + { type: 'Comedy' , movies: ['Wild Tales', 'In Bruges', 'Three Billboards Outside Ebbing, Missouri', + 'Untouchable', '3 idiots']}, + { type: 'Crime' , movies: ['Training Day', 'Heat', 'American Gangster']}, + { type: 'Drama' , movies: ['Fight Club', 'A Beautiful Mind', 'Good Will Hunting', 'City of God']}, + { type: 'Biography' , movies: ['Amadeus', 'Bohemian Rhapsody']}, + { type: 'Mystery' , movies: ['The Prestige', 'Memento', 'Cloud Atlas']}, + { type: 'Musical' , movies: ['All That Jazz']}, + { type: 'Romance' , movies: ['Love Actually', 'In The Mood for Love']}, + { type: 'Sci-Fi' , movies: ['The Fifth Element']}, + { type: 'Thriller' , movies: ['The Usual Suspects']}, + { type: 'Western' , movies: ['Django Unchained']}]; - constructor() { const division = { 'New England 01': ['Connecticut', 'Maine', 'Massachusetts'], 'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'], From 7fafe914a7b9981e491dca3271871caf59a4f5fe Mon Sep 17 00:00:00 2001 From: Nadia Robakova Date: Wed, 21 Aug 2019 13:40:58 +0300 Subject: [PATCH 6/7] chore(*): update rowSelection tests --- .../lib/grids/grid/grid-row-selection.spec.ts | 21 ++- .../hierarchical-grid.selection.spec.ts | 14 +- .../tree-grid/tree-grid-expanding.spec.ts | 1 + .../tree-grid/tree-grid-integration.spec.ts | 56 -------- .../tree-grid/tree-grid-selection.spec.ts | 121 +++++++++++++++++- 5 files changed, 136 insertions(+), 77 deletions(-) diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts index ff99a75cb0e..e30848013fb 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts @@ -1257,8 +1257,9 @@ describe('IgxGrid - Row Selection', () => { grid = fix.componentInstance.grid; })); - it('Paging: Should persist through paging', () => { + it('Paging: Should persist through paging', fakeAsync(() => { grid.paging = true; + tick(); fix.detectChanges(); const firstRow = grid.getRowByIndex(0); @@ -1274,6 +1275,7 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); grid.nextPage(); + tick(); fix.detectChanges(); HelperUtils.verifyRowSelected(secondRow, false); @@ -1289,16 +1291,18 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); grid.previousPage(); + tick(); fix.detectChanges(); HelperUtils.verifyRowSelected(firstRow, false); HelperUtils.verifyRowSelected(secondRow); HelperUtils.verifyRowSelected(middleRow); HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); - }); + })); - it('Paging: Should persist all rows selection through paging', () => { + it('Paging: Should persist all rows selection through paging', fakeAsync(() => { grid.paging = true; + tick(); fix.detectChanges(); const secondRow = grid.getRowByIndex(1); @@ -1309,6 +1313,7 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); grid.nextPage(); + tick(); fix.detectChanges(); HelperUtils.verifyHeaderRowCheckboxState(fix, true); HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); @@ -1321,14 +1326,16 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyRowSelected(secondRow); grid.previousPage(); + tick(); fix.detectChanges(); HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); HelperUtils.verifyRowsArraySelected(grid.rowList.toArray(), false); - }); + })); - it('Paging: Should be able to select rows with Shift and Click', () => { + it('Paging: Should be able to select rows with Shift and Click', fakeAsync(() => { grid.paging = true; + tick(); fix.detectChanges(); const firstRow = grid.getRowByIndex(0); @@ -1344,6 +1351,7 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyRowSelected(firstRow); grid.nextPage(); + tick(); fix.detectChanges(); HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); @@ -1357,11 +1365,12 @@ describe('IgxGrid - Row Selection', () => { HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); grid.previousPage(); + tick(); fix.detectChanges(); HelperUtils.verifyHeaderRowCheckboxState(fix, true); HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); - }); + })); it('CRUD: Should handle the deselection on a selected row properly', () => { let firstRow = grid.getRowByKey(1); diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts index 469561dd4df..4a722a46676 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts @@ -18,6 +18,7 @@ import { IgxHierarchicalTransactionServiceFactory } from './hierarchical-grid-ba import { IgxIconModule } from '../../icon'; import { IgxHierarchicalGridTestBaseComponent, IgxHierarchicalGridRowSelectionComponent } from '../../test-utils/hierarhical-grid-components.spec'; +import { HelperUtils } from '../../test-utils/helper-utils.spec'; describe('IgxHierarchicalGrid selection', () => { configureTestSuite(); @@ -120,22 +121,15 @@ describe('IgxHierarchicalGrid selection', () => { })); it('should retain selected row when filtering', fakeAsync(() => { - hierarchicalGrid.rowSelection = GridSelectionMode.multiple; - fix.detectChanges(); - const firstRow = hierarchicalGrid.getRowByIndex(0); - const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - - targetCheckbox.click(); + HelperUtils.clickRowCheckbox(firstRow); fix.detectChanges(); hierarchicalGrid.filter('ID', '0', IgxStringFilteringOperand.instance().condition('contains'), true); fix.detectChanges(); - expect(hierarchicalGrid.getRowByIndex(0).isSelected).toBeTruthy(); - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckbox: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); - expect(headerCheckbox.indeterminate).toBeTruthy(); + HelperUtils.verifyRowSelected( hierarchicalGrid.getRowByIndex(0)); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); })); it('should be able to change rowSelection per rowIsland', () => { diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-expanding.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-expanding.spec.ts index 52d8d315012..99dbd6794c0 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-expanding.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-expanding.spec.ts @@ -934,6 +934,7 @@ describe('IgxTreeGrid - Expanding / Collapsing ', () => { indicatorDiv.triggerEventHandler('click', new Event('click')); await wait(1050); fix.detectChanges(); + await wait(50); rows = TreeGridFunctions.getAllRows(fix); expect(rows.length).toBe(5); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts index 78284a7e064..2e0a3ffcc93 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-integration.spec.ts @@ -1107,62 +1107,6 @@ describe('IgxTreeGrid - Integration ', () => { expect(trans.add).toHaveBeenCalledWith(transPasrams, null); })); - it('Should NOT select deleted rows through API - Hierarchical DS', fakeAsync(() => { - fix = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); - fix.detectChanges(); - treeGrid = fix.componentInstance.treeGrid; - - treeGrid.rowSelection = GridSelectionMode.multiple; - tick(16); - fix.detectChanges(); - /** Select deleted row */ - treeGrid.deleteRowById(663); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - treeGrid.selectRows([663]); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - /** Select row with deleted parent */ - treeGrid.deleteRowById(147); - tick(16); - fix.detectChanges(); - // 147 -> 475 - treeGrid.selectRows([475]); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - })); - - it('Should NOT select deleted rows through API - Flat DS', fakeAsync(() => { - fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); - fix.detectChanges(); - treeGrid = fix.componentInstance.treeGrid; - - treeGrid.rowSelection = GridSelectionMode.multiple; - tick(16); - fix.detectChanges(); - /** Select deleted row */ - treeGrid.deleteRowById(6); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - treeGrid.selectRows([6]); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - /** Select row with deleted parent */ - treeGrid.deleteRowById(10); - tick(16); - fix.detectChanges(); - // 10 -> 9 - treeGrid.selectRows([9]); - tick(16); - fix.detectChanges(); - expect(treeGrid.selectedRows()).toEqual([]); - })); - it('Should not add child row to deleted parent row - Hierarchical DS', fakeAsync(() => { const fixture = TestBed.createComponent(IgxTreeGridRowEditingHierarchicalDSTransactionComponent); const grid = fixture.componentInstance.treeGrid; diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-selection.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-selection.spec.ts index db92af395b4..6f44535aeab 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-selection.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-selection.spec.ts @@ -7,7 +7,8 @@ import { IgxTreeGridSimpleComponent, IgxTreeGridCellSelectionComponent, IgxTreeGridSelectionRowEditingComponent, - IgxTreeGridSelectionWithTransactionComponent + IgxTreeGridSelectionWithTransactionComponent, + IgxTreeGridRowEditingTransactionComponent } from '../../test-utils/tree-grid-components.spec'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { @@ -31,7 +32,8 @@ describe('IgxTreeGrid - Selection ', () => { IgxTreeGridSimpleComponent, IgxTreeGridCellSelectionComponent, IgxTreeGridSelectionRowEditingComponent, - IgxTreeGridSelectionWithTransactionComponent + IgxTreeGridSelectionWithTransactionComponent, + IgxTreeGridRowEditingTransactionComponent ], imports: [IgxTreeGridModule, NoopAnimationsModule] }) @@ -164,7 +166,7 @@ describe('IgxTreeGrid - Selection ', () => { treeGrid.filter('Age', 40, IgxNumberFilteringOperand.instance().condition('greaterThan')); fix.detectChanges(); - tick(100); + tick(); // Verification indices are different since the sorting changes rows' positions. TreeGridFunctions.verifyDataRowsSelection(fix, [0, 2, 4], true); @@ -172,11 +174,41 @@ describe('IgxTreeGrid - Selection ', () => { treeGrid.clearFilter(); fix.detectChanges(); - tick(100); + tick(); TreeGridFunctions.verifyDataRowsSelection(fix, [0, 5, 8], true); })); + it('should be able to select and select only filtered data', () => { + treeGrid.selectRows([299, 147]); + fix.detectChanges(); + + treeGrid.filter('Age', 40, IgxNumberFilteringOperand.instance().condition('greaterThan')); + fix.detectChanges(); + + expect(treeGrid.selectedRows()).toEqual([299, 147]); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + + treeGrid.selectAllRows(true); + fix.detectChanges(); + + expect(treeGrid.selectedRows()).toEqual([299, 147, 317, 998, 19, 847]); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.deselectAllRows(true); + fix.detectChanges(); + + expect(treeGrid.selectedRows()).toEqual([299]); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + + treeGrid.clearFilter(); + fix.detectChanges(); + + expect(treeGrid.selectedRows()).toEqual([299]); + TreeGridFunctions.verifyDataRowsSelection(fix, [6], true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + }); + it('should persist the selection after expand/collapse', () => { treeGrid.selectRows([treeGrid.getRowByIndex(0).rowID, treeGrid.getRowByIndex(3).rowID, treeGrid.getRowByIndex(5).rowID], true); @@ -413,7 +445,7 @@ describe('IgxTreeGrid - Selection ', () => { })); }); - describe('Row Selection with transactions', () => { + describe('Row Selection with transactions - Hierarchical DS', () => { // configureTestSuite(); beforeEach(fakeAsync(() => { fix = TestBed.createComponent(IgxTreeGridSelectionWithTransactionComponent); @@ -550,6 +582,85 @@ describe('IgxTreeGrid - Selection ', () => { TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); expect(treeGrid.selectedRows().includes(13)).toEqual(false); }); + + it('Should be able to select deleted rows through API - Hierarchical DS', () => { + treeGrid.deleteRowById(663); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([]); + treeGrid.selectRows([663]); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([663]); + /** Select row with deleted parent */ + treeGrid.deleteRowById(147); + fix.detectChanges(); + // 147 -> 475 + treeGrid.selectRows([475]); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([663, 475]); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + }); + + it('Should not be able to select deleted rows through API with selectAllRows - Hierarchical DS', () => { + treeGrid.deleteRowById(663); + treeGrid.deleteRowById(147); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([]); + + treeGrid.selectAllRows(); + fix.detectChanges(); + + expect(treeGrid.selectedRows().includes(663)).toBe(false); + expect(treeGrid.selectedRows().includes(147)).toBe(false); + expect(treeGrid.selectedRows().includes(475)).toBe(false); + expect(treeGrid.selectedRows().includes(19)).toBe(true); + expect(treeGrid.selectedRows().includes(847)).toBe(true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + }); + }); + + describe('Row Selection with transactions - flat DS', () => { + beforeEach(fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridRowEditingTransactionComponent); + fix.detectChanges(); + + treeGrid = fix.componentInstance.treeGrid; + treeGrid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + })); + + it('Should select deleted rows through API', () => { + treeGrid.deleteRowById(6); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([]); + treeGrid.selectRows([6]); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([6]); + /** Select row with deleted parent */ + treeGrid.deleteRowById(10); + fix.detectChanges(); + // 10 -> 9 + treeGrid.selectRows([9]); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([6, 9]); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + }); + + it('Should not be able to select deleted rows through API with selectAllRows', () => { + treeGrid.deleteRowById(6); + treeGrid.deleteRowById(10); + fix.detectChanges(); + expect(treeGrid.selectedRows()).toEqual([]); + + treeGrid.selectAllRows(); + fix.detectChanges(); + + expect(treeGrid.selectedRows().includes(6)).toBe(false); + expect(treeGrid.selectedRows().includes(10)).toBe(false); + expect(treeGrid.selectedRows().includes(9)).toBe(false); + expect(treeGrid.selectedRows().includes(1)).toBe(true); + expect(treeGrid.selectedRows().includes(2)).toBe(true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + }); }); describe('Cell Selection', () => { From f9cc69045180d7d2e6c20ca894940f864d340fb0 Mon Sep 17 00:00:00 2001 From: ddincheva Date: Wed, 21 Aug 2019 15:41:40 +0300 Subject: [PATCH 7/7] chore(*): refactor selection service --- projects/igniteui-angular/src/lib/core/grid-selection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/igniteui-angular/src/lib/core/grid-selection.ts b/projects/igniteui-angular/src/lib/core/grid-selection.ts index e7dc3a49ea2..57dc0631fe3 100644 --- a/projects/igniteui-angular/src/lib/core/grid-selection.ts +++ b/projects/igniteui-angular/src/lib/core/grid-selection.ts @@ -565,7 +565,8 @@ export class IgxGridSelectionService { if (this.grid.rowSelection === 'none' || this.isRowDeleted(rowID)) { return; } clearPrevSelection = this.grid.rowSelection === 'single' || clearPrevSelection; - const newSelection = clearPrevSelection ? [rowID] : [...this.getSelectedRows(), rowID]; + const newSelection = clearPrevSelection ? [rowID] : this.getSelectedRows().indexOf(rowID) !== -1 ? + this.getSelectedRows() : [...this.getSelectedRows(), rowID]; const removed = clearPrevSelection ? this.getSelectedRows() : []; this.emitRowSelectionEvent(newSelection, [rowID], removed, event); }