diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0d9a07c96..b02a0d3762f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes for each version of this project will be documented in this - `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid` - `uniqueColumnValuesStrategy` input is added. This property provides a callback for loading unique column values on demand. If this property is provided, the unique values it generates will be used by the Excel Style Filtering (instead of using the unique values from the data that is bound to the grid). - `igxExcelStyleLoading` directive is added, which can be used to provide a custom loading template for the Excel Style Filtering. If this property is not provided, a default loading template will be used instead. + - introduced new properties `cellSelection` and `rowSelection` which accept GridSelection mode enumeration. Grid selection mode could be none, single or multiple. Also `hideRowSelectors` property is added, which allows you to show and hide row selectors when row selection is enabled. - `IgxHierarchicalGrid` - Row Islands now emit child grid events with an additional argument - `owner`, which holds reference to the related child grid component instance. - `IgxDrag` @@ -24,9 +25,13 @@ All notable changes for each version of this project will be documented in this ### General - `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid` + - `isCellSelected` method has been deprecated. Now you can use `selected` property. + - `rowSelectable` property has been deprecated. Now you can use `rowSelection` property to enable row selection and also you can show and hide the row selectors by setting `hideRowSelectors` property to true or false (which is the default value). + - Removed deprecated event `OnFocusChange` - **Breaking Change** `igxExcelStyleSortingTemplate` directive is renamed to `igxExcelStyleSorting`. - **Breaking Change** `igxExcelStyleMovingTemplate` directive is renamed to `igxExcelStyleMoving`. - **Breaking Change** `igxExcelStyleHidingTemplate` directive is renamed to `igxExcelStyleHiding`. + - **Breaking Change** `onRowSelectionChange` event arguments are changed. The `row` property has been removed and the properties `added`, `removed` and `cancel` are newly added. - **Breaking Change** `igxExcelStylePinningTemplate` directive is renamed to `igxExcelStylePinning`. - **Breaking Change** `onRowDragEnd` and `onRowDragStart` event arguments are changed - `owner` now holds reference to the grid component instance, while `dragDirective` hold reference to the drag directive. - `IgxCombo` @@ -50,7 +55,7 @@ All notable changes for each version of this project will be documented in this - For more information, visit the component's [readme](https://github.com/IgniteUI/igniteui-angular/tree/master/projects/igniteui-angular/src/lib/combo/README.md) ## 8.1.4 -- `IgxDialog` new @Input `positionSettings` is now available. It provides the ability to get/set both position and animation settings of the Dialog component. +- `IgxDialog` new @Input `positionSettings` is now available. It provides the ability to get/set both position and animation settings of the Dialog component. - `IgxDrag` - Deprecated inputs - `hideBaseOnDrag`, `animateOnRelease`, `visible`. diff --git a/projects/igniteui-angular/src/lib/core/grid-selection.ts b/projects/igniteui-angular/src/lib/core/grid-selection.ts index 0f75bccba32..c33c10b3c67 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; @@ -202,7 +201,7 @@ export class IgxGridCRUDService { @Injectable() export class IgxGridSelectionService { - + grid; dragMode = false; activeElement: ISelectionNode | null; keyboardState = {} as ISelectionKeyboardState; @@ -213,7 +212,8 @@ export class IgxGridSelectionService { temp = new Map>(); _ranges: Set = new Set(); _selectionRange: Range; - + rowSelection: Set = new Set(); + private allRowsSelected: boolean; /** * Returns the current selected ranges in the grid from both @@ -227,7 +227,7 @@ export class IgxGridSelectionService { const ranges = Array.from(this._ranges).map(range => JSON.parse(range)); // No ranges but we have a focused cell -> add it - if (!ranges.length && this.activeElement) { + if (!ranges.length && this.activeElement && this.grid.isCellSelectable) { ranges.push(this.generateRange(this.activeElement)); } @@ -304,7 +304,7 @@ export class IgxGridSelectionService { } selected(node: ISelectionNode): boolean { - return this.isActiveNode(node) || this.isInMap(node); + return (this.isActiveNode(node) && this.grid.isCellSelectable) || this.isInMap(node); } isActiveNode(node: ISelectionNode, mrl = false): boolean { @@ -394,7 +394,6 @@ export class IgxGridSelectionService { } pointerDown(node: ISelectionNode, shift: boolean, ctrl: boolean): void { - this.addKeyboardRange(); this.initKeyboardState(); this.pointerState.ctrl = ctrl; @@ -506,7 +505,8 @@ export class IgxGridSelectionService { this.selectRange(node, state); } - clear(): void { + clear(clearAcriveEl = false): void { + if (clearAcriveEl) { this.activeElement = null; } this.selection.clear(); this.temp.clear(); this._ranges.clear(); @@ -541,6 +541,151 @@ export class IgxGridSelectionService { range.collapse(true); selection.addRange(range); } + + /** Returns array of the selected row id's. */ + getSelectedRows(): Array { + return this.rowSelection.size ? Array.from(this.rowSelection.keys()) : []; + } + + /** Clears row selection, if filtering is applied clears only selected rows from filtered data. */ + clearRowSelection(event?): void { + 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)) : []; + this.emitRowSelectionEvent(newSelection, [], removedRec, event); + } + + /** Select all rows, if filtering is applied select only from filtered data. */ + selectAllRows(event?) { + const allRowIDs = this.getRowIDs(this.allData); + const addedRows = allRowIDs.filter((rID) => !this.isRowSelected(rID)); + const newSelection = this.rowSelection.size ? allRowIDs.concat(this.getSelectedRows()) : addedRows; + + this.emitRowSelectionEvent(newSelection, addedRows, [], event); + } + + /** Select the specified row and emit event. */ + selectRowById(rowID, clearPrevSelection?, event?): void { + if (!this.grid.isRowSelectable || this.isRowDeleted(rowID)) { return; } + clearPrevSelection = !this.grid.isMultiRowSelectionEnabled || clearPrevSelection; + + 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); + } + + /** Deselect the specified row and emit event. */ + deselectRow(rowID, event?): void { + if (!this.isRowSelected(rowID)) { return; } + const newSelection = this.getSelectedRows().filter(r => r !== rowID); + if (this.rowSelection.size && this.rowSelection.has(rowID)) { + this.emitRowSelectionEvent(newSelection, [], [rowID], event); + } + } + + /** Select specified rows. No event is emitted. */ + selectRowsWithNoEvent(rowIDs: any[], clearPrevSelection?): void { + if (clearPrevSelection) { this.rowSelection.clear(); } + rowIDs.forEach(rowID => { this.rowSelection.add(rowID); }); + this.allRowsSelected = undefined; + } + + /** Deselect specified rows. No event is emitted. */ + deselectRowsWithNoEvent(rowIDs: any[]): void { + rowIDs.forEach(rowID => this.rowSelection.delete(rowID)); + this.allRowsSelected = undefined; + } + + isRowSelected(rowID): boolean { + return this.rowSelection.size > 0 && this.rowSelection.has(rowID); + } + + /** Select range from last selected row to the current specified row.*/ + selectMultipleRows(rowID, rowData, event?): void { + this.allRowsSelected = undefined; + if (!this.rowSelection.size || this.isRowDeleted(rowID)) { + this.selectRowById(rowID); + return; + } + const gridData = this.allData; + 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); + + const added = this.getRowIDs(rows).filter(rID => !this.isRowSelected(rID)); + const newSelection = this.getSelectedRows().concat(added); + + this.emitRowSelectionEvent(newSelection, added, [], event); + } + + areAllRowSelected(): boolean { + if (!this.grid.data) { return false; } + if (this.allRowsSelected !== undefined) { return this.allRowsSelected; } + + const dataItemsID = this.getRowIDs(this.allData); + return this.allRowsSelected = Math.min(this.rowSelection.size, dataItemsID.length) > 0 && + new Set(Array.from(this.rowSelection.values()).concat(dataItemsID)).size === this.rowSelection.size; + } + + hasSomeRowSelected(): boolean { + const filteredData = this.isFilteringApplied() ? + this.getRowIDs(this.grid.filteredData).some(rID => this.isRowSelected(rID)) : true; + return this.rowSelection.size > 0 && filteredData && !this.areAllRowSelected(); + } + + public emitRowSelectionEvent(newSelection, added, removed, event?): boolean { + const currSelection = this.getSelectedRows(); + if (this.areEqualCollections(currSelection, newSelection)) { return; } + + const args = { + oldSelection: currSelection, newSelection: newSelection, + added: added, removed: removed, event: event, cancel: false + }; + this.grid.onRowSelectionChange.emit(args); + if (args.cancel) { return; } + this.selectRowsWithNoEvent(args.newSelection, true); + } + + public getRowDataById(rowID): Object { + if (!this.grid.primaryKey) { return rowID; } + const rowIndex = this.getRowIDs(this.grid.gridAPI.get_all_data(true)).indexOf(rowID); + return rowIndex < 0 ? {} : this.grid.gridAPI.get_all_data(true)[rowIndex]; + } + + public getRowIDs(data): Array { + return this.grid.primaryKey && data.length ? data.map(rec => rec[this.grid.primaryKey]) : data; + } + + public clearHeaderCBState(): void { + this.allRowsSelected = undefined; + } + + /**Clear rowSelection and update checkbox state*/ + public clearAllSelectedRows(): void { + this.rowSelection.clear(); + this.clearHeaderCBState(); + } + + /** Returns all data in the grid, with applied filtering and sorting and without deleted rows. */ + public get allData(): Array { + const allData = this.isFilteringApplied() || this.grid.sortingExpressions.length ? + this.grid.filteredSortedData : this.grid.gridAPI.get_all_data(true); + return allData.filter(rData => !this.isRowDeleted(this.grid.gridAPI.get_row_id(rData))); + } + + private areEqualCollections(first, second): boolean { + return first.length === second.length && new Set(first.concat(second)).size === first.length; + } + + private isFilteringApplied(): boolean { + return this.grid.filteringExpressionsTree.filteringOperands.length > 0; + } + + private isRowDeleted(rowID): boolean { + return this.grid.gridAPI.row_deleted_transaction(rowID); + } } export function isChromium(): boolean { diff --git a/projects/igniteui-angular/src/lib/grids/api.service.ts b/projects/igniteui-angular/src/lib/grids/api.service.ts index 62003710cb9..77d6f9d5f22 100644 --- a/projects/igniteui-angular/src/lib/grids/api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/api.service.ts @@ -150,9 +150,9 @@ export class GridBaseAPIService { + value === GridSelectionMode.multiple ? + this.addPointerListeners(value) : this.removePointerListeners(this._cellSelection); + }); + this._cellSelection = value; + } + /** * @hidden * @internal @@ -411,7 +429,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { @HostBinding('attr.aria-selected') @HostBinding('class.igx-grid__td--selected') get selected() { - return this.isCellSelected(); + return this.selectionService.selected(this.selectionNode); } /** @@ -424,6 +442,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { set selected(val: boolean) { const node = this.selectionNode; val ? this.selectionService.add(node) : this.selectionService.remove(node); + this.grid.notifyChanges(); } @HostBinding('class.igx-grid__td--edited') @@ -523,18 +542,31 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { protected compositionStartHandler; protected compositionEndHandler; private _highlight: IgxTextHighlightDirective; + private _cellSelection = GridSelectionMode.multiple; constructor( protected selectionService: IgxGridSelectionService, protected crudService: IgxGridCRUDService, public gridAPI: GridBaseAPIService, - public selection: IgxSelectionAPIService, public cdr: ChangeDetectorRef, private element: ElementRef, protected zone: NgZone, private touchManager: HammerGesturesManager) { } + private addPointerListeners(selection) { + if (selection !== GridSelectionMode.multiple) { return; } + this.nativeElement.addEventListener('pointerdown', this.pointerdown); + this.nativeElement.addEventListener('pointerenter', this.pointerenter); + this.nativeElement.addEventListener('pointerup', this.pointerup); + } + + private removePointerListeners(selection) { + if (selection !== GridSelectionMode.multiple) { return; } + this.nativeElement.removeEventListener('pointerdown', this.pointerdown); + this.nativeElement.removeEventListener('pointerenter', this.pointerenter); + this.nativeElement.removeEventListener('pointerup', this.pointerup); + } /** * @hidden @@ -542,10 +574,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { */ ngOnInit() { this.zone.runOutsideAngular(() => { - this.nativeElement.addEventListener('pointerdown', this.pointerdown); - this.nativeElement.addEventListener('pointerenter', this.pointerenter); - this.nativeElement.addEventListener('pointerup', this.pointerup); - + this.addPointerListeners(this.cellSelectionMode); // IE 11 workarounds if (isIE()) { this.compositionStartHandler = () => this.isInCompositionMode = true; @@ -566,10 +595,7 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { */ ngOnDestroy() { this.zone.runOutsideAngular(() => { - this.nativeElement.removeEventListener('pointerdown', this.pointerdown); - this.nativeElement.removeEventListener('pointerenter', this.pointerenter); - this.nativeElement.removeEventListener('pointerup', this.pointerup); - + this.removePointerListeners(this.cellSelectionMode); if (isIE()) { this.nativeElement.removeEventListener('compositionstart', this.compositionStartHandler); this.nativeElement.removeEventListener('compositionend', this.compositionEndHandler); @@ -609,12 +635,14 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { } /** + * @deprecated * Gets whether the cell is selected. * ```typescript * let isCellSelected = thid.cell.isCellSelected(); * ``` * @memberof IgxGridCellComponent */ + @DeprecateMethod(`'isCellSelected' is deprecated. Use 'selected' property instead.`) public isCellSelected() { return this.selectionService.selected(this.selectionNode); } @@ -771,13 +799,12 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { if (this.focused) { return; } - - const node = this.selectionNode; - const mrl = this.grid.hasColumnLayouts; this.focused = true; this.row.focused = true; + const node = this.selectionNode; + const mrl = this.grid.hasColumnLayouts; - if (!this.selectionService.isActiveNode(node, mrl)) { + if (this.grid.isCellSelectable && !this.selectionService.isActiveNode(node, mrl)) { this.grid.onSelection.emit({ cell: this, event }); } @@ -792,7 +819,9 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { } this.selectionService.primaryButton = true; - this.selectionService.keyboardStateOnFocus(node, this.grid.onRangeSelection, this.nativeElement); + if (this.cellSelectionMode === GridSelectionMode.multiple) { + this.selectionService.keyboardStateOnFocus(node, this.grid.onRangeSelection, this.nativeElement); + } } /** @@ -892,13 +921,6 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { event.preventDefault(); } - // TODO: to be deleted when onFocusChange event is removed #4054 - const args = { cell: this, groupRow: null, event: event, cancel: false }; - this.grid._onFocusChange.emit(args); - if (args.cancel) { - return; - } - switch (key) { case 'tab': this.handleTab(shift); @@ -952,8 +974,9 @@ export class IgxGridCellComponent implements OnInit, OnChanges, OnDestroy { case ' ': case 'spacebar': case 'space': - if (this.row.rowSelectable) { - this.row.checkboxElement.toggle(); + if (this.grid.isRowSelectable) { + this.row.selected ? this.selectionService.deselectRow(this.row.rowID, event) : + this.selectionService.selectRowById(this.row.rowID, false, event); } break; default: diff --git a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts index bc5e2a6721c..42b42357fcd 100644 --- a/projects/igniteui-angular/src/lib/grids/grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid-base.component.ts @@ -29,7 +29,6 @@ import { import ResizeObserver from 'resize-observer-polyfill'; import { Subject, combineLatest, pipe } from 'rxjs'; import { takeUntil, first, filter, throttleTime, map } from 'rxjs/operators'; -import { IgxSelectionAPIService } from '../core/selection'; import { cloneArray, isEdge, isNavigationKey, CancelableEventArgs, flatten, mergeObjects, isIE, IBaseEventArgs } from '../core/utils'; import { DataType } from '../data-operations/data-util'; import { FilteringLogic, IFilteringExpression } from '../data-operations/filtering-expression.interface'; @@ -148,10 +147,11 @@ export interface IColumnResizeEventArgs extends IBaseEventArgs { newWidth: string; } -export interface IRowSelectionEventArgs extends IBaseEventArgs { +export interface IRowSelectionEventArgs extends CancelableEventArgs, IBaseEventArgs { oldSelection: any[]; newSelection: any[]; - row?: IgxRowComponent; + added: any[]; + removed: any[]; event?: Event; } @@ -184,13 +184,6 @@ export interface IColumnMovingEndEventArgs extends IBaseEventArgs { target: IgxColumnComponent; } -// TODO: to be deleted when onFocusChange event is removed #4054 -export interface IFocusChangeEventArgs extends IBaseEventArgs { - cell: IgxGridCellComponent; - event: Event; - cancel: boolean; -} - export interface IGridKeydownEventArgs extends IBaseEventArgs { targetType: GridKeydownTargetType; target: Object; @@ -241,6 +234,12 @@ export enum GridKeydownTargetType { hierarchicalRow = 'hierarchicalRow' } +export enum GridSelectionMode { + none = 'none', + single = 'single', + multiple = 'multiple', +} + export abstract class IgxGridBaseComponent extends DisplayDensityBase implements OnInit, DoCheck, OnDestroy, AfterContentInit, AfterViewInit { private _scrollWidth: number; @@ -377,6 +376,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } this.filteringService.refreshExpressions(); + this.selectionService.clearHeaderCBState(); this.summaryService.clearSummaryCache(); this.notifyChanges(); } @@ -448,7 +448,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements if (val === this._page || val < 0 || val > this.totalPages - 1) { return; } - + this.selectionService.clear(true); this.onPagingDone.emit({ previous: this._page, current: val }); this._page = val; this.notifyChanges(); @@ -478,8 +478,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements if (val < 0) { return; } - - this.selectionService.clear(); + this.selectionService.clear(true); this._perPage = val; this.page = 0; this.endEdit(true); @@ -526,36 +525,35 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } } - /** - * Sets whether the `IgxGridRowComponent` selection is enabled. - * By default it is set to false. - * ```typescript - * let rowSelectable = this.grid.rowSelectable; - * ``` - * @memberof IgxGridBaseComponent - */ + @DeprecateProperty('rowSelectable property is deprecated. Use rowSelection property instead.') @WatchChanges() @Input() get rowSelectable(): boolean { - return this._rowSelection; + return this.isRowSelectable; + } + + set rowSelectable(val: boolean) { + this.rowSelection = val ? GridSelectionMode.multiple : GridSelectionMode.none; } /** - * Sets whether rows can be selected. - * ```html - * - * ``` - * @memberof IgxGridBaseComponent + * Returns if the row selectors are hidden + * @memberof IgxGridBaseComponent */ - set rowSelectable(val: boolean) { - this._rowSelection = val; - if (!this._init) { + @WatchChanges() + @Input() + get hideRowSelectors() { + return this._hideRowSelectors; + } - // should selection persist? - this.allRowsSelected = false; - this.deselectAllRows(); - this.notifyChanges(true); - } + /** + * Allows you to change the visibility of the row selectors + * By default row selectors are shown + * @memberof IgxGridBaseComponent + */ + set hideRowSelectors(value: boolean) { + this._hideRowSelectors = value; + this.notifyChanges(true); } @Input() @@ -1088,8 +1086,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * } * ``` * ```html - * + * * * * @@ -1122,7 +1119,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * ``` * ```html * + * [primaryKey]="'ProductID'"> * * * @@ -1157,7 +1154,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * ``` * ```html * + * [primaryKey]="'ProductID'"> * * * @@ -1181,7 +1178,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * Bind to the event in markup as follows: * ```html * + * [primaryKey]="'ProductID'" [rowEditable]="true"> * * * @@ -1216,7 +1213,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * Bind to the event in markup as follows: * ```html * + * [primaryKey]="'ProductID'" [rowEditable]="true"> * * * @@ -1252,7 +1249,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * Bind to the event in markup as follows: * ```html * + * [primaryKey]="'ProductID'" [rowEditable]="true"> * * * @@ -1487,19 +1484,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements @Output() public onColumnMovingEnd = new EventEmitter(); - /** - * @deprecated you should use onGridKeydown event - */ - @Output() - @DeprecateProperty('onFocusChange event is deprecated. Use onGridKeydown event instead.') - public get onFocusChange(): EventEmitter { - return this._onFocusChange; - } - - public set onFocusChange(val: EventEmitter) { - this._onFocusChange = val; - } - /** * Emitted when keydown is triggered over element inside grid's body. * This event is fired only if the key combination is supported in the grid. @@ -2337,6 +2321,54 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements separator: '\t' }; + /** + * Returns the current cell selection state, which can be none, single or multiple + * @memberof IgxGridBaseComponent + */ + @WatchChanges() + @Input() + get cellSelection() { + return this._cellSelectionMode; + } + + /** + * Allows you to set cell selection mode + * By default the cell selection mode is multiple + * @param selectionMode: GridSelectionMode + * @memberof IgxGridBaseComponent + */ + set cellSelection(selectionMode: GridSelectionMode) { + this._cellSelectionMode = selectionMode; + if (this.gridAPI.grid) { + this.selectionService.clear(true); + this.notifyChanges(); + } + } + + /** + * Returns the current row selection state, which can be none, single or multiple + * @memberof IgxGridBaseComponent + */ + @WatchChanges() + @Input() + get rowSelection() { + return this._rowSelectionMode; + } + + /** + * Allows you to set row selection mode + * By default the row selection mode is none + * @param selectionMode: GridSelectionMode + * @memberof IgxGridBaseComponent + */ + set rowSelection(selectionMode: GridSelectionMode) { + this._rowSelectionMode = selectionMode; + if (this.gridAPI.grid && this.columnList) { + this.selectionService.clearAllSelectedRows(); + this.notifyChanges(true); + } + } + /** * @hidden */ @@ -2393,10 +2425,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements */ public draggedColumn: IgxColumnComponent; - /** - * @hidden - */ - public allRowsSelected = false; /** * @hidden @@ -2442,7 +2470,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements /** * @hidden */ - protected _rowSelection = false; + protected _hideRowSelectors = false; /** * @hidden */ @@ -2523,10 +2551,11 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements private _columnWidth: string; protected _defaultTargetRecordNumber = 10; - protected _onFocusChange = new EventEmitter(); private _summaryPosition = GridSummaryPosition.bottom; private _summaryCalculationMode = GridSummaryCalculationMode.rootAndChildLevels; + private _cellSelectionMode = GridSelectionMode.multiple; + private _rowSelectionMode = GridSelectionMode.none; private rowEditPositioningStrategy = new ContainerPositioningStrategy({ horizontalDirection: HorizontalAlignment.Right, @@ -2612,7 +2641,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements public selectionService: IgxGridSelectionService, public crudService: IgxGridCRUDService, private gridAPI: GridBaseAPIService, - public selection: IgxSelectionAPIService, @Inject(IgxGridTransaction) protected _transactions: TransactionService, private elementRef: ElementRef, private zone: NgZone, @@ -2633,6 +2661,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements _setupServices() { this.gridAPI.grid = this; this.crudService.grid = this; + this.selectionService.grid = this; this.navigation.grid = this; this.filteringService.grid = this; this.summaryService.grid = this; @@ -2648,6 +2677,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements }); this.transactions.onStateUpdate.pipe(destructor).subscribe(() => { + this.selectionService.clearHeaderCBState(); this.summaryService.clearSummaryCache(); this._pipeTrigger++; this.notifyChanges(); @@ -2664,8 +2694,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements this.onPagingDone.pipe(destructor).subscribe(() => { this.endEdit(true); - this.selectionService.clear(); - this.selectionService.activeElement = null; + this.selectionService.clear(true); }); this.onColumnMoving.pipe(destructor).subscribe(() => this.endEdit(true)); @@ -3143,7 +3172,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } /** - * Returns the `IgxColumnComponent` by index. + * Returns the `IgxRowComponent` by index. * ```typescript * const myRow = this.grid1.getRowByIndex(1); * ``` @@ -3309,7 +3338,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } get showRowCheckboxes(): boolean { - return this.rowSelectable && this.hasVisibleColumns; + return this.isRowSelectable && this.hasVisibleColumns && !this.hideRowSelectors; } get showDragIcons(): boolean { @@ -3757,6 +3786,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements */ public refreshGridState(args?) { this.endEdit(true); + this.selectionService.clearHeaderCBState(); this.summaryService.clearSummaryCache(args); } @@ -4445,7 +4475,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements public getFeatureColumnsWidth() { let width = 0; - if (this.rowSelectable) { + if (this.isRowSelectable) { width += this.headerCheckboxContainer ? this.headerCheckboxContainer.nativeElement.getBoundingClientRect().width : 0; } if (this.rowDraggable) { @@ -4647,18 +4677,9 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements /** * @hidden */ - public onHeaderCheckboxClick(event, filteredData) { - this.allRowsSelected = event.checked; - const newSelection = - event.checked ? - filteredData ? - this.selection.add_items(this.id, this.selection.get_all_ids(filteredData, this.primaryKey)) : - this.selection.get_all_ids(this.gridAPI.get_all_data(true), this.primaryKey) : - filteredData ? - this.selection.delete_items(this.id, this.selection.get_all_ids(filteredData, this.primaryKey)) : - this.selection.get_empty(); - this.triggerRowSelectionChange(newSelection, null, event, event.checked); - this.checkHeaderCheckboxStatus(event.checked); + public onHeaderSelectorClick(event) { + this.selectionService.areAllRowSelected() ? + this.selectionService.clearRowSelection(event) : this.selectionService.selectAllRows(event); } /** @@ -4666,94 +4687,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements */ get headerCheckboxAriaLabel() { return this._filteringExpressionsTree.filteringOperands.length > 0 ? - this.headerCheckbox && this.headerCheckbox.checked ? 'Deselect all filtered' : 'Select all filtered' : - this.headerCheckbox && this.headerCheckbox.checked ? 'Deselect all' : 'Select all'; - } - - /** - * @hidden - */ - public checkHeaderCheckboxStatus(headerStatus?: boolean) { - if (headerStatus === undefined) { - const filteredData = this.filteringService.filteredData; - const dataLength = filteredData ? filteredData.length : this.dataLength; - this.allRowsSelected = this.selection.are_all_selected(this.id, dataLength); - if (this.headerCheckbox) { - this.headerCheckbox.indeterminate = !this.allRowsSelected && !this.selection.are_none_selected(this.id); - if (!this.headerCheckbox.indeterminate) { - this.headerCheckbox.checked = - this.allRowsSelected; - } - } - this.notifyChanges(); - } else if (this.headerCheckbox) { - this.headerCheckbox.checked = headerStatus !== undefined ? headerStatus : false; - } - } - - /** - * @hidden - */ - public filteredItemsStatus(componentID: string, filteredData: any[], primaryKey?) { - const currSelection = this.selection.get(componentID); - let atLeastOneSelected = false; - let notAllSelected = false; - if (currSelection) { - for (const key of Object.keys(filteredData)) { - const dataItem = primaryKey ? filteredData[key][primaryKey] : filteredData[key]; - if (currSelection.has(dataItem)) { - atLeastOneSelected = true; - if (notAllSelected) { - return 'indeterminate'; - } - } else { - notAllSelected = true; - if (atLeastOneSelected) { - return 'indeterminate'; - } - } - } - } - return atLeastOneSelected ? 'allSelected' : 'noneSelected'; - } - - /** - * @hidden - */ - public updateHeaderCheckboxStatusOnFilter(data) { - if (!data || !this.hasVisibleColumns || !this.headerCheckbox) { - this.checkHeaderCheckboxStatus(); - return; - } - switch (this.filteredItemsStatus(this.id, data, this.primaryKey)) { - case 'allSelected': { - if (!this.allRowsSelected) { - this.allRowsSelected = true; - } - if (this.headerCheckbox.indeterminate) { - this.headerCheckbox.indeterminate = false; - } - break; - } - case 'noneSelected': { - if (this.allRowsSelected) { - this.allRowsSelected = false; - } - if (this.headerCheckbox.indeterminate) { - this.headerCheckbox.indeterminate = false; - } - break; - } - default: { - if (!this.headerCheckbox.indeterminate) { - this.headerCheckbox.indeterminate = true; - } - if (this.allRowsSelected) { - this.allRowsSelected = false; - } - break; - } - } + this.headerCheckbox && this.selectionService.areAllRowSelected() ? 'Deselect all filtered' : 'Select all filtered' : + this.headerCheckbox && this.selectionService.areAllRowSelected() ? 'Deselect all' : 'Select all'; } /** @@ -4765,9 +4700,7 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * @memberof IgxGridBaseComponent */ public selectedRows(): any[] { - let selection: Set; - selection = this.selection.get(this.id); - return selection ? Array.from(selection) : []; + return this.selectionService.getSelectedRows(); } /** @@ -4780,15 +4713,8 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * @memberof IgxGridBaseComponent */ public selectRows(rowIDs: any[], clearCurrentSelection?: boolean) { - let newSelection: Set; - let selectableRows = []; - if (this.transactions.enabled) { - selectableRows = rowIDs.filter(e => !this.gridAPI.row_deleted_transaction(e)); - } else { - selectableRows = rowIDs; - } - newSelection = this.selection.add_items(this.id, selectableRows, clearCurrentSelection); - this.triggerRowSelectionChange(newSelection); + this.selectionService.selectRowsWithNoEvent(rowIDs, clearCurrentSelection); + this.notifyChanges(); } /** @@ -4800,37 +4726,48 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements * @memberof IgxGridBaseComponent */ public deselectRows(rowIDs: any[]) { - let newSelection: Set; - newSelection = this.selection.delete_items(this.id, rowIDs); - this.triggerRowSelectionChange(newSelection); + this.selectionService.deselectRowsWithNoEvent(rowIDs); + this.notifyChanges(); } /** * Selects all rows - * Note: If filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows. + * Note: By default if filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows. + * If you set the parameter onlyFilterData to false that will select all rows in the grid exept deleted rows. * ```typescript * this.grid.selectAllRows(); + * this.grid.selectAllRows(false); * ``` + * @param onlyFilterData * @memberof IgxGridBaseComponent */ - public selectAllRows() { - this.triggerRowSelectionChange(this.selection.get_all_ids(this.gridAPI.get_all_data(true), this.primaryKey)); + public selectAllRows(onlyFilterData = true) { + const data = onlyFilterData && this.filteredData ? this.filteredData : this.gridAPI.get_all_data(true); + const rowIDs = this.selectionService.getRowIDs(data).filter(rID => !this.gridAPI.row_deleted_transaction(rID)); + this.selectRows(rowIDs); } /** * Deselects all rows + * Note: By default if filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows. + * If you set the parameter onlyFilterData to false that will select all rows in the grid exept deleted rows. * ```typescript * this.grid.deselectAllRows(); * ``` - * Note: If filtering is in place, selectAllRows() and deselectAllRows() select/deselect all filtered rows. + * @param onlyFilterData + * @memberof IgxGridBaseComponent */ - public deselectAllRows() { - this.triggerRowSelectionChange(this.selection.get_empty()); + public deselectAllRows(onlyFilterData = true) { + if (onlyFilterData && this.filteredData && this.filteredData.length > 0) { + this.deselectRows(this.selectionService.getRowIDs(this.filteredData)); + } else { + this.selectionService.clearAllSelectedRows(); + this.notifyChanges(); + } } clearCellSelection(): void { - this.selectionService.clear(); - this.selectionService.activeElement = null; + this.selectionService.clear(true); this.notifyChanges(); } @@ -4918,10 +4855,14 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements let columnsArray: IgxColumnComponent[]; let record = {}; const selectedData = []; + const activeEl = this.selectionService.activeElement; const selectionMap = Array.from(this.selectionService.selection) .filter((tuple) => tuple[0] < source.length); + if (this.cellSelection === GridSelectionMode.single && activeEl) { + selectionMap.push([activeEl.row, new Set().add(activeEl.column)]); + } for (const [row, set] of selectionMap) { if (!source[row]) { @@ -4972,23 +4913,6 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements return this.extractDataFromSelection(source, formatters, headers); } - /** - * @hidden - */ - public triggerRowSelectionChange(newSelectionAsSet: Set, row?: IgxRowComponent, - event?: Event, headerStatus?: boolean) { - const oldSelectionAsSet = this.selection.get(this.id); - const oldSelection = oldSelectionAsSet ? Array.from(oldSelectionAsSet) : []; - const newSelection = newSelectionAsSet ? Array.from(newSelectionAsSet) : []; - const args: IRowSelectionEventArgs = { oldSelection, newSelection, row, event }; - this.onRowSelectionChange.emit(args); - newSelectionAsSet = this.selection.get_empty(); - for (let i = 0; i < args.newSelection.length; i++) { - newSelectionAsSet.add(args.newSelection[i]); - } - this.selection.set(this.id, newSelectionAsSet); - this.checkHeaderCheckboxStatus(headerStatus); - } /** * @hidden @@ -5682,7 +5606,20 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements return rowData.summaries && (rowData.summaries instanceof Map); } + /** @hidden */ + public get isMultiRowSelectionEnabled(): boolean { + return this.rowSelection === GridSelectionMode.multiple; + } + + /** @hidden */ + public get isRowSelectable(): boolean { + return this.rowSelection !== GridSelectionMode.none; + } + /** @hidden */ + public get isCellSelectable() { + return this.cellSelection !== GridSelectionMode.none; + } /** * @hidden @@ -5714,5 +5651,3 @@ export abstract class IgxGridBaseComponent extends DisplayDensityBase implements } } } - - diff --git a/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts index f23be6f5f54..288756784ec 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/cell.spec.ts @@ -1189,7 +1189,7 @@ export class CellEditingScrollTestComponent { @Component({ template: ` - + { })); it('- When Hide All columns no rows should be rendered', fakeAsync(() => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; grid.paging = true; grid.rowDraggable = true; tick(30); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-multi-cell-selection.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-cell-selection.spec.ts similarity index 87% rename from projects/igniteui-angular/src/lib/grids/grid/grid-multi-cell-selection.spec.ts rename to projects/igniteui-angular/src/lib/grids/grid/grid-cell-selection.spec.ts index b8cc8acdd96..24572cff6bd 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-multi-cell-selection.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-cell-selection.spec.ts @@ -1,9 +1,12 @@ import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxGridModule, IgxGridGroupByRowComponent } from './index'; +import { IgxGridModule, IgxGridGroupByRowComponent, IgxGridComponent, GridSelectionMode } from './index'; import { configureTestSuite } from '../../test-utils/configure-suite'; -import { SelectionWithScrollsComponent, SelectionWithTransactionsComponent } from '../../test-utils/grid-samples.spec'; +import { SelectionWithScrollsComponent, + SelectionWithTransactionsComponent, + CellSelectionNoneComponent, + CellSelectionSingleComponent} from '../../test-utils/grid-samples.spec'; import { SortingDirection } from '../../data-operations/sorting-expression.interface'; import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; @@ -11,14 +14,16 @@ import { HelperUtils, setupGridScrollDetection, resizeObserverIgnoreError } from import { DefaultSortingStrategy } from 'igniteui-angular'; -describe('IgxGrid - Multi Cell selection #grid', () => { +describe('IgxGrid - Cell selection #grid', () => { configureTestSuite(); beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ SelectionWithScrollsComponent, - SelectionWithTransactionsComponent + SelectionWithTransactionsComponent, + CellSelectionNoneComponent, + CellSelectionSingleComponent ], imports: [NoopAnimationsModule, IgxGridModule] }).compileComponents(); @@ -383,6 +388,67 @@ describe('IgxGrid - Multi Cell selection #grid', () => { HelperUtils.verifyCellSelected(secondCell, false); HelperUtils.verifyCellSelected(thirdCell, false); }); + + it('Should not be possible to select a range when change cellSelection to none', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(0, 'Name'); + const endCell = grid.getCellByColumn(2, 'ParentID'); + + expect(grid.cellSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + HelperUtils.verifyCellsRegionSelected(grid, 0, 2, 1, 2); + HelperUtils.verifySelectedRange(grid, 0, 2, 1, 2); + + grid.cellSelection = GridSelectionMode.none; + fix.detectChanges(); + + HelperUtils.verifyCellsRegionSelected(grid, 0, 2, 1, 2, false); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + + // Try to select a range + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + HelperUtils.verifyCellsRegionSelected(grid, 0, 2, 1, 2, false); + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + expect(grid.selectedCells.length).toBe(0); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + }); + + it('Should not be possible to select a range when change cellSelection to single', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(0, 'ID'); + const endCell = grid.getCellByColumn(1, 'ParentID'); + + expect(grid.cellSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + HelperUtils.verifyCellsRegionSelected(grid, 0, 1, 0, 1); + HelperUtils.verifySelectedRange(grid, 0, 1, 0, 1); + + grid.cellSelection = GridSelectionMode.single; + fix.detectChanges(); + + expect(grid.cellSelection).toEqual(GridSelectionMode.single); + HelperUtils.verifyCellsRegionSelected(grid, 0, 1, 0, 1, false); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + + // Try to select a range + HelperUtils.selectCellsRangeNoWait(fix, endCell, startCell); + detect(); + HelperUtils.verifyCellsRegionSelected(grid, 0, 0, 0, 1, false); + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{ParentID: 147}]); + HelperUtils.verifySelectedRange(grid, 1, 1, 1, 1); + }); }); describe('API', () => { @@ -2529,7 +2595,7 @@ describe('IgxGrid - Multi Cell selection #grid', () => { }); it('Row Selection: the selection range should not change when select row', () => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; const range = { rowStart: 2, rowEnd: 4, columnStart: 'ID', columnEnd: 'HireDate' }; grid.selectRange(range); fix.detectChanges(); @@ -2546,14 +2612,14 @@ describe('IgxGrid - Multi Cell selection #grid', () => { grid.selectRows([row.rowID]); fix.detectChanges(); - expect(row.isSelected).toBeTruthy(); + expect(row.selected).toBeTruthy(); HelperUtils.verifySelectedRange(grid, 2, 4, 0, 3); HelperUtils.verifyCellsRegionSelected(grid, 2, 4, 0, 3); expect(grid.getSelectedData()).toEqual(selectedData); }); it('Row Selection: selected range should be preserved when select row with space', () => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; const range = { rowStart: 2, rowEnd: 4, columnStart: 'ID', columnEnd: 'HireDate' }; grid.selectRange(range); fix.detectChanges(); @@ -2571,14 +2637,14 @@ describe('IgxGrid - Multi Cell selection #grid', () => { UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true, false, false); fix.detectChanges(); - expect(grid.getRowByIndex(2).isSelected).toBeTruthy(); + expect(grid.getRowByIndex(2).selected).toBeTruthy(); HelperUtils.verifySelectedRange(grid, 2, 4, 0, 3); HelperUtils.verifyCellsRegionSelected(grid, 2, 4, 0, 3); expect(grid.getSelectedData()).toEqual(selectedData); }); it('Row Selection: selected range with mouse interaction should be preserved when select row with space', () => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; const firstCell = grid.getCellByColumn(2, 'ID'); const secondCell = grid.getCellByColumn(4, 'HireDate'); HelperUtils.selectCellsRangeNoWait(fix, firstCell, secondCell); @@ -2596,7 +2662,7 @@ describe('IgxGrid - Multi Cell selection #grid', () => { UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true, false, false); fix.detectChanges(); - expect(grid.getRowByIndex(2).isSelected).toBeTruthy(); + expect(grid.getRowByIndex(2).selected).toBeTruthy(); HelperUtils.verifySelectedRange(grid, 2, 4, 0, 3); HelperUtils.verifyCellsRegionSelected(grid, 2, 4, 0, 3); expect(grid.getSelectedData()).toEqual(selectedData); @@ -2769,4 +2835,355 @@ describe('IgxGrid - Multi Cell selection #grid', () => { expect(grid.getSelectedData()).toEqual(selectedData); }); }); + + describe('None selection', () => { + let fix; + let grid: IgxGridComponent; + let detect; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(CellSelectionNoneComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + detect = () => grid.cdr.detectChanges(); + })); + + it('When click on cell it should not be selected', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + const firstCell = grid.getCellByColumn(1, 'ParentID'); + const secondCell = grid.getCellByColumn(2, 'Name'); + const thirdCell = grid.getCellByColumn(0, 'ID'); + + UIInteractions.simulateClickAndSelectCellEvent(firstCell); + fix.detectChanges(); + HelperUtils.verifyCellSelected(firstCell, false); + expect(firstCell.focused).toBeTruthy(); + expect(grid.selectedCells.length).toBe(0); + + UIInteractions.simulateClickAndSelectCellEvent(secondCell, false, true); + fix.detectChanges(); + HelperUtils.verifyCellSelected(firstCell, false); + HelperUtils.verifyCellSelected(secondCell, false); + expect(grid.selectedCells.length).toBe(0); + + UIInteractions.simulateClickAndSelectCellEvent(thirdCell, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(0); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + HelperUtils.verifyCellSelected(firstCell, false); + HelperUtils.verifyCellSelected(secondCell, false); + HelperUtils.verifyCellSelected(thirdCell, false); + expect(grid.selectedCells.length).toBe(0); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + }); + + it('When when navigate with keyboard cells should not be selected', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + let cell = grid.getCellByColumn(1, 'ParentID'); + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + + UIInteractions.triggerKeyDownWithBlur('arrowdown', cell.nativeElement, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(0); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + + cell = grid.getCellByColumn(2, 'ParentID'); + expect(cell.focused).toBeTruthy(); + HelperUtils.verifyCellSelected(cell, false); + expect(grid.selectedCells.length).toBe(0); + + UIInteractions.triggerKeyDownWithBlur('arrowright', cell.nativeElement, true, false, true); + fix.detectChanges(); + + HelperUtils.verifyCellsRegionSelected(grid, 1, 2, 1, 2, false); + cell = grid.getCellByColumn(2, 'Name'); + expect(cell.focused).toBeTruthy(); + + UIInteractions.triggerKeyDownWithBlur('arrowup', cell.nativeElement, true); + fix.detectChanges(); + + cell = grid.getCellByColumn(1, 'Name'); + expect(cell.focused).toBeTruthy(); + HelperUtils.verifyCellSelected(cell, false); + expect(grid.selectedCells.length).toBe(0); + + UIInteractions.triggerKeyDownWithBlur('arrowleft', cell.nativeElement, true, false, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(0); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + cell = grid.getCellByColumn(1, 'ParentID'); + HelperUtils.verifyCellSelected(cell, false); + expect(grid.selectedCells.length).toBe(0); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + }); + + it('Should not select select a range with mouse dragging', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(0, 'ID'); + const endCell = grid.getCellByColumn(3, 'ID'); + + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(startCell.focused).toBe(true); + HelperUtils.verifyCellsRegionSelected(grid, 0, 3, 0, 0, false); + HelperUtils.verifyCellSelected(startCell, false); + HelperUtils.verifyCellSelected(endCell, false); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + expect(grid.selectedCells.length).toBe(0); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + }); + + it('Should select a region from API', () => { + const range = { rowStart: 0, rowEnd: 2, columnStart: 'Name', columnEnd: 'ParentID' }; + const expectedData = [ + { ParentID: 147, Name: 'Michael Langdon' }, + { ParentID: 147, Name: 'Thomas Hardy' }, + { ParentID: 147, Name: 'Monica Reyes' } + ]; + grid.selectRange(range); + fix.detectChanges(); + + HelperUtils.verifyCellsRegionSelected(grid, 0, 2, 1, 2); + HelperUtils.verifySelectedRange(grid, 0, 2, 1, 2); + expect(grid.getSelectedData()).toEqual(expectedData); + }); + + it('Should select a cell from API', () => { + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + const cell = grid.getCellByColumn(1, 'Name'); + cell.selected = true; + fix.detectChanges(); + + HelperUtils.verifyCellSelected(cell); + expect(selectionChangeSpy).toHaveBeenCalledTimes(0); + expect(grid.getSelectedData()).toEqual([{Name: 'Thomas Hardy'}]); + expect(grid.selectedCells.length).toBe(1); + }); + + it('When change cell selection to multi it should be possible to select cells with mouse dragging', () => { + const selectionChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(0, 'ParentID'); + const endCell = grid.getCellByColumn(1, 'ParentID'); + const expectedData = [ + { ParentID: 147}, + { ParentID: 147 } + ]; + + expect(grid.cellSelection).toEqual(GridSelectionMode.none); + grid.cellSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(startCell.focused).toBe(true); + HelperUtils.verifyCellsRegionSelected(grid, 0, 1, 1, 1); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(1); + expect(grid.selectedCells.length).toBe(2); + expect(grid.getSelectedData()).toEqual(expectedData); + HelperUtils.verifySelectedRange(grid, 0, 1, 1, 1); + }); + }); + + describe('Single selection', () => { + let fix; + let grid: IgxGridComponent; + let detect; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(CellSelectionSingleComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + detect = () => grid.cdr.detectChanges(); + })); + + it('When click on cell it should selected', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + const firstCell = grid.getCellByColumn(1, 'ParentID'); + const secondCell = grid.getCellByColumn(2, 'Name'); + const thirdCell = grid.getCellByColumn(0, 'ID'); + + // Click on a cell + UIInteractions.simulateClickAndSelectCellEvent(firstCell); + fix.detectChanges(); + HelperUtils.verifyCellSelected(firstCell); + expect(grid.selectedCells.length).toBe(1); + expect(selectionChangeSpy).toHaveBeenCalledTimes(1); + + // Click on a cell holding Ctrl + UIInteractions.simulateClickAndSelectCellEvent(secondCell, false, true); + fix.detectChanges(); + HelperUtils.verifyCellSelected(firstCell, false); + HelperUtils.verifyCellSelected(secondCell); + expect(grid.selectedCells.length).toBe(1); + expect(selectionChangeSpy).toHaveBeenCalledTimes(2); + + // Click on a cell holding Shift + UIInteractions.simulateClickAndSelectCellEvent(thirdCell, true); + fix.detectChanges(); + + HelperUtils.verifyCellSelected(firstCell, false); + HelperUtils.verifyCellSelected(secondCell, false); + HelperUtils.verifyCellSelected(thirdCell); + expect(grid.selectedCells.length).toBe(1); + expect(selectionChangeSpy).toHaveBeenCalledTimes(3); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + expect(grid.getSelectedData()).toEqual([{ID: 475}]); + HelperUtils.verifySelectedRange(grid, 0, 0, 0, 0); + }); + + it('When when navigate with arrow keys cell selection should be changed', () => { + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + let cell = grid.getCellByColumn(1, 'Name'); + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + + UIInteractions.triggerKeyDownWithBlur('arrowdown', cell.nativeElement, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(2); + cell = grid.getCellByColumn(2, 'Name'); + expect(cell.focused).toBeTruthy(); + HelperUtils.verifyCellSelected(cell); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{Name: 'Monica Reyes'}]); + HelperUtils.verifySelectedRange(grid, 2, 2, 2, 2); + + UIInteractions.triggerKeyDownWithBlur('arrowleft', cell.nativeElement, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(3); + cell = grid.getCellByColumn(2, 'ParentID'); + HelperUtils.verifyCellSelected(cell); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{ParentID: 147}]); + HelperUtils.verifySelectedRange(grid, 2, 2, 1, 1); + }); + + it('When when navigate with arrow keys and holding Shift only one cell should be selected', () => { + const selectionChangeSpy = spyOn(grid.onSelection, 'emit').and.callThrough(); + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + let cell = grid.getCellByColumn(3, 'ParentID'); + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + + UIInteractions.triggerKeyDownWithBlur('arrowup', cell.nativeElement, true, false, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(2); + cell = grid.getCellByColumn(2, 'ParentID'); + HelperUtils.verifyCellSelected(cell); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{ParentID: 147}]); + HelperUtils.verifySelectedRange(grid, 2, 2, 1, 1); + + UIInteractions.triggerKeyDownWithBlur('arrowright', cell.nativeElement, true, false, true); + fix.detectChanges(); + + expect(selectionChangeSpy).toHaveBeenCalledTimes(3); + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + cell = grid.getCellByColumn(2, 'Name'); + HelperUtils.verifyCellSelected(cell); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{Name: 'Monica Reyes'}]); + HelperUtils.verifySelectedRange(grid, 2, 2, 2, 2); + }); + + it('Should not select select a range with mouse dragging', () => { + const rangeChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(0, 'ID'); + const endCell = grid.getCellByColumn(1, 'ParentID'); + + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(startCell.focused).toBe(true); + HelperUtils.verifyCellSelected(startCell); + HelperUtils.verifyCellSelected(endCell, false); + HelperUtils.verifyCellsRegionSelected(grid, 1, 1, 0, 1, false); + + expect(rangeChangeSpy).toHaveBeenCalledTimes(0); + expect(grid.selectedCells.length).toBe(1); + expect(grid.getSelectedData()).toEqual([{ID: 475}]); + HelperUtils.verifySelectedRange(grid, 0, 0, 0, 0); + }); + + it('Should select a region from API', () => { + const range = { rowStart: 0, rowEnd: 2, columnStart: 'Name', columnEnd: 'ParentID' }; + const expectedData = [ + { ParentID: 147, Name: 'Michael Langdon' }, + { ParentID: 147, Name: 'Thomas Hardy' }, + { ParentID: 147, Name: 'Monica Reyes' } + ]; + grid.selectRange(range); + fix.detectChanges(); + + HelperUtils.verifyCellsRegionSelected(grid, 0, 2, 1, 2); + HelperUtils.verifySelectedRange(grid, 0, 2, 1, 2); + expect(grid.getSelectedData()).toEqual(expectedData); + }); + + it('When change cell selection to multi it should be possible to select cells with mouse dragging', () => { + const selectionChangeSpy = spyOn(grid.onRangeSelection, 'emit').and.callThrough(); + const startCell = grid.getCellByColumn(3, 'ParentID'); + const endCell = grid.getCellByColumn(2, 'Name'); + const expectedData = [ + { ParentID: 147, Name: 'Monica Reyes' }, + { ParentID: 847, Name: 'Laurence Johnson'} + ]; + + expect(grid.cellSelection).toEqual(GridSelectionMode.single); + UIInteractions.simulateClickAndSelectCellEvent(startCell); + fix.detectChanges(); + HelperUtils.verifyCellSelected(startCell); + + grid.cellSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + expect(grid.cellSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.verifyCellSelected(startCell, false); + expect(grid.selectedCells.length).toBe(0); + + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + HelperUtils.verifyCellsRegionSelected(grid, 2, 3, 1, 2); + expect(grid.selectedCells.length).toBe(4); + expect(grid.getSelectedData()).toEqual(expectedData); + HelperUtils.verifySelectedRange(grid, 2, 3, 1, 2); + expect(selectionChangeSpy).toHaveBeenCalledTimes(1); + }); + + it('When change cell selection to none selected cells should be cleared', () => { + const cell = grid.getCellByColumn(2, 'Name'); + + expect(grid.cellSelection).toEqual(GridSelectionMode.single); + + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + HelperUtils.verifyCellSelected(cell); + + grid.cellSelection = GridSelectionMode.none; + fix.detectChanges(); + + expect(grid.cellSelection).toEqual(GridSelectionMode.none); + HelperUtils.verifyCellSelected(cell, false); + expect(grid.selectedCells.length).toBe(0); + expect(grid.getSelectedData()).toEqual([]); + expect(grid.getSelectedRanges()).toEqual([]); + }); + }); + }); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts index a8170175680..c7fd587fd92 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts @@ -5,7 +5,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Calendar } from '../../calendar/calendar'; import { IgxInputDirective } from '../../directives/input/input.directive'; import { IgxGridComponent } from './grid.component'; -import { IgxGridModule } from './index'; +import { IgxGridModule, GridSelectionMode } from './index'; import { IgxButtonDirective } from '../../directives/button/button.directive'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; import { configureTestSuite } from '../../test-utils/configure-suite'; @@ -44,7 +44,7 @@ import { IgxGridFilteringESFTemplatesComponent, IgxGridFilteringESFLoadOnDemandComponent } from '../../test-utils/grid-samples.spec'; -import { resizeObserverIgnoreError } from '../../test-utils/helper-utils.spec'; +import { HelperUtils, resizeObserverIgnoreError } from '../../test-utils/helper-utils.spec'; const FILTER_UI_ROW = 'igx-grid-filtering-row'; const FILTER_UI_CELL = 'igx-grid-filtering-cell'; @@ -2246,7 +2246,7 @@ describe('IgxGrid - Filtering Row UI actions #grid', () => { // Filtering + Row Selectors it('should display the Row Selector header checkbox above the filter row.', fakeAsync(() => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; fix.detectChanges(); const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL)); @@ -2258,8 +2258,7 @@ describe('IgxGrid - Filtering Row UI actions #grid', () => { const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent)); const frElem = filteringRow.nativeElement; - const chkBox = fix.debugElement.query(By.css('.igx-grid__cbx-selection')).query(By.directive(IgxCheckboxComponent)); - const chkBoxElem = chkBox.nativeElement; + const chkBoxElem = HelperUtils.getRowCheckboxInput(HelperUtils.getHeaderRow(fix)); expect(frElem.offsetTop).toBeGreaterThanOrEqual(chkBoxElem.offsetTop + chkBoxElem.clientHeight); })); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-keyBoardNav.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-keyBoardNav.spec.ts index d29961a579b..4c8afca6bea 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-keyBoardNav.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-keyBoardNav.spec.ts @@ -2,7 +2,12 @@ import { Component, ViewChild, TemplateRef } from '@angular/core'; import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxColumnComponent, IgxGridCellComponent, IgxGridModule, IgxGridRowComponent, IgxGridGroupByRowComponent, } from './index'; +import { IgxColumnComponent, + IgxGridCellComponent, + IgxGridModule, + IgxGridRowComponent, + IgxGridGroupByRowComponent, + GridSelectionMode, } from './index'; import { IgxGridComponent } from './grid.component'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { IGridCellEventArgs } from '../grid-base.component'; @@ -1182,7 +1187,7 @@ describe('IgxGrid - Keyboard navigation #grid', () => { })); it('should correct work when press tab and sft+tab on a grouped row when have row selectors', (async () => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; await wait(DEBOUNCETIME); fix.detectChanges(); 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 new file mode 100644 index 00000000000..bde533ce430 --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row-selection.spec.ts @@ -0,0 +1,1875 @@ +import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { SortingDirection } from '../../data-operations/sorting-expression.interface'; +import { IgxGridComponent } from './grid.component'; +import { IgxGridModule, GridSelectionMode, IRowSelectionEventArgs } from './index'; +import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec'; +import { IgxStringFilteringOperand, IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { + GridWithPrimaryKeyComponent, + RowSelectionComponent, + SelectionWithScrollsComponent, + SingleRowSelectionComponent, + RowSelectionWithoutPrimaryKeyComponent, + SelectionWithTransactionsComponent +} from '../../test-utils/grid-samples.spec'; +import { IgxHierarchicalGridModule } from '../hierarchical-grid/hierarchical-grid.module'; +import { HelperUtils } from '../../test-utils/helper-utils.spec'; +import { GridFunctions } from '../../test-utils/grid-functions.spec'; +import { SampleTestData } from '../../test-utils/sample-test-data.spec'; + +const DEBOUNCETIME = 30; + +describe('IgxGrid - Row Selection #grid', () => { + configureTestSuite(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + GridWithPrimaryKeyComponent, + RowSelectionComponent, + SelectionWithScrollsComponent, + RowSelectionWithoutPrimaryKeyComponent, + SingleRowSelectionComponent, + SelectionWithTransactionsComponent + ], + imports: [ + NoopAnimationsModule, + IgxGridModule, + IgxHierarchicalGridModule + ] + }) + .compileComponents(); + })); + + describe('Base tests', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(RowSelectionComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Should have checkbox on each row', (async () => { + HelperUtils.verifyHeaderRowHasCheckbox(fix); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + for (const row of grid.rowList.toArray()) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + } + + GridFunctions.setGridScrollTop(grid, 1000); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + for (const row of grid.rowList.toArray()) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + } + })); + + it('Should persist through scrolling vertical', (async () => { + const selectedRow = grid.getRowByIndex(0); + expect(selectedRow).toBeDefined(); + + HelperUtils.verifyRowSelected(selectedRow, false); + + HelperUtils.clickRowCheckbox(selectedRow); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(selectedRow); + expect(grid.selectedRows()).toEqual([1]); + + GridFunctions.setGridScrollTop(grid, 500); + await wait(100); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([1]); + HelperUtils.verifyRowSelected(grid.rowList.first, false); + + GridFunctions.setGridScrollTop(grid, 0); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(selectedRow); + expect(grid.selectedRows()).toEqual([1]); + })); + + it('Should have correct checkboxes position when scroll left', (async () => { + grid.width = '300px'; + fix.detectChanges(); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + GridFunctions.scrollLeft(grid, 1000); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + GridFunctions.scrollLeft(grid, 0); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + })); + + it('Header checkbox should select/deselect all rows', () => { + const allRowsArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + expect(grid.selectedRows()).toEqual(allRowsArray); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: allRowsArray, + cancel: false, + event: jasmine.anything(), + newSelection: allRowsArray, + oldSelection: [], + removed: [] + }); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, false); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray(), false); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + oldSelection: allRowsArray, + newSelection: [], + added: [], + removed: allRowsArray, + event: jasmine.anything(), + cancel: false + }); + }); + + it('Header checkbox should deselect all rows - scenario when clicking first row, while header checkbox is clicked', () => { + const firstRow = grid.getRowByIndex(0); + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(firstRow.selected).toBeTruthy(); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); + }); + + it('Checkbox should select/deselect row', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [1], + cancel: false, + event: jasmine.anything(), + newSelection: [1], + oldSelection: [], + removed: [] + }); + + expect(grid.selectedRows()).toEqual([1]); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.selectedRows()).toEqual([1, 2]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [2], + cancel: false, + event: jasmine.anything(), + newSelection: [1, 2], + oldSelection: [1], + removed: [] + }); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.selectedRows()).toEqual([2]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [], + cancel: false, + event: jasmine.anything(), + newSelection: [2], + oldSelection: [1, 2], + removed: [1] + }); + + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.selectedRows()).toEqual([]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [], + cancel: false, + event: jasmine.anything(), + newSelection: [], + oldSelection: [2], + removed: [2] + }); + }); + + it('Should select the row with mouse click ', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(1); + const secondRow = grid.getRowByIndex(2); + const mockEvent = new MouseEvent('click'); + + firstRow.nativeElement.dispatchEvent(mockEvent); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([2]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [2], + cancel: false, + event: mockEvent, + newSelection: [2], + oldSelection: [], + removed: [] + }); + + // Click again on same row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + + // Click on a different row + secondRow.nativeElement.dispatchEvent(mockEvent); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.selectedRows()).toEqual([3]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [3], + cancel: false, + event: mockEvent, + newSelection: [3], + oldSelection: [2], + removed: [2] + }); + }); + + it('Should select multiple rows with clicking and holding Ctrl', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(2); + const secondRow = grid.getRowByIndex(0); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + HelperUtils.verifyRowSelected(firstRow); + + // Click again on this row holding Ctrl + UIInteractions.simulateClickEvent(firstRow.nativeElement, false, true); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + + // Click on a different row + UIInteractions.simulateClickEvent(secondRow.nativeElement, false, true); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + }); + + it('Should select multiple rows with clicking Space on a cell', (async () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + let cell = grid.getCellByColumn(0, 'ProductName'); + + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + HelperUtils.verifyCellSelected(cell); + HelperUtils.verifyRowSelected(firstRow, false); + + // Press Space key on the cell + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + UIInteractions.triggerKeyDownWithBlur('arrowdown', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + cell = grid.getCellByColumn(1, 'ProductName'); + HelperUtils.verifyCellSelected(cell); + HelperUtils.verifyRowSelected(firstRow); + + // Click Space on the cell + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow); + + // Click again Space on the cell + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + })); + + it('Should select multiple rows with Shift + Click', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(1); + const secondRow = grid.getRowByIndex(4); + const mockEvent = new MouseEvent('click', { shiftKey: true }); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + // Click on other row holding Shift key + secondRow.nativeElement.dispatchEvent(mockEvent); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([2, 3, 4, 5]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [3, 4, 5], + cancel: false, + event: mockEvent, + newSelection: [2, 3, 4, 5], + oldSelection: [2], + removed: [] + }); + + for (let index = 1; index < 5; index++) { + const row = grid.getRowByIndex(index); + HelperUtils.verifyRowSelected(row); + } + }); + + it('Should hide/show checkboxes when change hideRowSelectors', () => { + const firstRow = grid.getRowByIndex(1); + + expect(grid.hideRowSelectors).toBe(false); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.hideRowSelectors = true; + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, true, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement, false, false); + + grid.hideRowSelectors = false; + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyHeaderRowHasCheckbox(fix); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement); + }); + + it('Should be able to change RowSelection to none', () => { + const firstRow = grid.getRowByIndex(0); + expect(grid.rowSelection).toEqual(GridSelectionMode.multiple); + + grid.selectRows([1]); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.rowSelection = GridSelectionMode.none; + fix.detectChanges(); + + expect(grid.rowSelection).toEqual(GridSelectionMode.none); + HelperUtils.verifyRowSelected(firstRow, false, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement, false, false); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false, false); + }); + + it('Should be able to change RowSelection to single', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + expect(grid.rowSelection).toEqual(GridSelectionMode.multiple); + + grid.selectRows([1]); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.rowSelection = GridSelectionMode.single; + fix.detectChanges(); + + expect(grid.rowSelection).toEqual(GridSelectionMode.single); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + // Click on another row holding Ctrl + UIInteractions.simulateClickEvent(secondRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyRowSelected(firstRow, false); + }); + + it('Should be able to cancel onRowSelectionChange event', () => { + const firstRow = grid.getRowByIndex(0); + grid.onRowSelectionChange.subscribe((e: IRowSelectionEventArgs) => { + e.cancel = true; + }); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow, false); + + // Click on a row checkbox + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow, false); + + // Click on header checkbox + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix); + HelperUtils.verifyRowSelected(firstRow, false); + + // Select rows from API + grid.selectRows([2, 3]); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(grid.getRowByIndex(1)); + HelperUtils.verifyRowSelected(grid.getRowByIndex(2)); + + // Click on header checkbox + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(grid.getRowByIndex(1)); + HelperUtils.verifyRowSelected(grid.getRowByIndex(2)); + + // Select all rows from API + grid.selectAllRows(); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(grid.getRowByIndex(1)); + HelperUtils.verifyRowSelected(grid.getRowByIndex(2)); + + // Click on header checkbox + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(grid.getRowByIndex(1)); + HelperUtils.verifyRowSelected(grid.getRowByIndex(2)); + }); + + it('Should be able to programmatically overwrite the selection using onRowSelectionChange event', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + const thirdRow = grid.getRowByIndex(2); + grid.onRowSelectionChange.subscribe((e: IRowSelectionEventArgs) => { + if (e.added.length > 0 && (e.added[0]) % 2 === 0) { + e.newSelection = e.oldSelection || []; + } + }); + + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow], false); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + expect(firstRow.selected).toBeTruthy(); + expect(secondRow.selected).toBeFalsy(); + expect(thirdRow.selected).toBeFalsy(); + + HelperUtils.clickRowCheckbox(firstRow); + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + + expect(firstRow.selected).toBeFalsy(); + expect(secondRow.selected).toBeFalsy(); + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow], false); + }); + + it('ARIA support', () => { + const firstRow = grid.getRowByIndex(0).nativeElement; + const headerCheckbox = fix.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__input'); + expect(firstRow.getAttribute('aria-selected')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all'); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(firstRow.getAttribute('aria-selected')).toMatch('true'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('true'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Deselect all'); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(firstRow.getAttribute('aria-selected')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all'); + }); + + it('ARIA support when there is filtered data', () => { + grid.filter('ProductName', 'Ca', IgxStringFilteringOperand.instance().condition('contains'), true); + fix.detectChanges(); + + const firstRow = grid.getRowByIndex(0).nativeElement; + const headerCheckbox = fix.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__input'); + expect(firstRow.getAttribute('aria-selected')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all filtered'); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(firstRow.getAttribute('aria-selected')).toMatch('true'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('true'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Deselect all filtered'); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(firstRow.getAttribute('aria-selected')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all filtered'); + + grid.clearFilter(); + fix.detectChanges(); + + expect(firstRow.getAttribute('aria-selected')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); + expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all'); + }); + }); + + describe('RowSelection none', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(SelectionWithScrollsComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Change RowSelection to multiple ', () => { + HelperUtils.verifyHeaderRowHasCheckbox(fix, false, false); + HelperUtils.verifyRowHasCheckbox(grid.getRowByIndex(0).nativeElement, false, false); + + grid.selectRows([475]); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(grid.getRowByIndex(0), true, false); + + grid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + HelperUtils.verifyRowSelected(grid.getRowByIndex(0), false, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + HelperUtils.verifyHeaderRowHasCheckbox(fix); + HelperUtils.verifyRowHasCheckbox(grid.getRowByIndex(0).nativeElement); + }); + }); + + describe('RowSelection single', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(SingleRowSelectionComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Should have checkbox on each row nd do not have header checkbox', (async () => { + HelperUtils.verifyHeaderRowHasCheckbox(fix, false); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + for (const row of grid.rowList.toArray()) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + } + + GridFunctions.setGridScrollTop(grid, 1000); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowHasCheckbox(fix, false); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + for (const row of grid.rowList.toArray()) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + } + })); + + it('Should be able to select only one row when click on a checkbox', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [1], + cancel: false, + event: jasmine.anything(), + newSelection: [1], + oldSelection: [], + removed: [] + }); + + expect(grid.selectedRows()).toEqual([1]); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + + // Click other row checkbox + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + expect(grid.selectedRows()).toEqual([2]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [2], + cancel: false, + event: jasmine.anything(), + newSelection: [2], + oldSelection: [1], + removed: [1] + }); + }); + + it('Should not select multiple rows with clicking and holding Ctrl', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(2); + const secondRow = grid.getRowByIndex(0); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.selectedRows()).toEqual([3]); + HelperUtils.verifyRowSelected(firstRow); + + // Click on a different row holding Ctrl + UIInteractions.simulateClickEvent(secondRow.nativeElement, false, true); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + expect(grid.selectedRows()).toEqual([1]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + }); + + it('Should not select multiple rows with clicking Space on a cell', (async () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + let cell = grid.getCellByColumn(0, 'ProductName'); + + UIInteractions.simulateClickAndSelectCellEvent(cell); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + HelperUtils.verifyCellSelected(cell); + HelperUtils.verifyRowSelected(firstRow, false); + + // Press Space key on the cell + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.selectedRows()).toEqual([1]); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + + UIInteractions.triggerKeyDownWithBlur('arrowdown', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + // Click Space on the cell + cell = grid.getCellByColumn(1, 'ProductName'); + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.selectedRows()).toEqual([2]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + + // Click again Space on the cell + UIInteractions.triggerKeyDownEvtUponElem('space', cell.nativeElement, true); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow, false); + })); + + it('Should not select multiple rows with Shift + Click', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(1); + const secondRow = grid.getRowByIndex(4); + const mockEvent = new MouseEvent('click', { shiftKey: true }); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([2]); + HelperUtils.verifyRowSelected(firstRow); + + // Click on other row holding Shift key + secondRow.nativeElement.dispatchEvent(mockEvent); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([5]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [5], + cancel: false, + event: mockEvent, + newSelection: [5], + oldSelection: [2], + removed: [2] + }); + + HelperUtils.verifyRowSelected(secondRow); + for (let index = 1; index < 4; index++) { + const row = grid.getRowByIndex(index); + HelperUtils.verifyRowSelected(row, false); + } + }); + + it('Should hide/show checkboxes when change hideRowSelectors', () => { + const firstRow = grid.getRowByIndex(1); + + expect(grid.hideRowSelectors).toBe(false); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.hideRowSelectors = true; + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, true, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement, false, false); + + grid.hideRowSelectors = false; + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement); + }); + + it('Should be able to select multiple rows from API', () => { + grid.selectRows([1, 3, 5], true); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected([grid.getRowByIndex(0), grid.getRowByIndex(2), grid.getRowByIndex(4)]); + expect(grid.selectedRows()).toEqual([1, 3, 5]); + + grid.selectRows([1, 2, 4], false); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected([grid.getRowByIndex(0), + grid.getRowByIndex(1), grid.getRowByIndex(2), grid.getRowByIndex(3), grid.getRowByIndex(4)]); + expect(grid.selectedRows()).toEqual([1, 3, 5, 2, 4]); + }); + + it('Should be able to cancel onRowSelectionChange event', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([1]); + + // Cancel the event + grid.onRowSelectionChange.subscribe((e: IRowSelectionEventArgs) => { + e.cancel = true; + }); + + // Click on a row checkbox + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([1]); + + // Click on other row checkbox + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + expect(grid.selectedRows()).toEqual([1]); + + // Click on other row + UIInteractions.simulateClickEvent(secondRow.nativeElement); + fix.detectChanges(); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + expect(grid.selectedRows()).toEqual([1]); + }); + + it('Should be able to change RowSelection to none', () => { + const firstRow = grid.getRowByIndex(0); + expect(grid.rowSelection).toEqual(GridSelectionMode.single); + + grid.selectRows([1]); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.rowSelection = GridSelectionMode.none; + fix.detectChanges(); + + expect(grid.rowSelection).toEqual(GridSelectionMode.none); + HelperUtils.verifyRowSelected(firstRow, false, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix, false, false); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement, false, false); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false, false); + }); + + it('Should be able to change RowSelection to multiple', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(2); + expect(grid.rowSelection).toEqual(GridSelectionMode.single); + + grid.selectRows([1]); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + grid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + expect(grid.rowSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowHasCheckbox(fix); + HelperUtils.verifyRowHasCheckbox(firstRow.nativeElement); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, grid); + + // Click on a row + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + + // Click on another row holding Ctrl + UIInteractions.simulateClickEvent(secondRow.nativeElement, false, true); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyRowSelected(firstRow); + }); + }); + + describe('API test', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(RowSelectionComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Should be able to programmatically select all rows and keep the header checkbox intact, #1298', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + grid.selectAllRows(); + grid.cdr.detectChanges(); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.selectAllRows(); + grid.cdr.detectChanges(); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deselectAllRows(); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray(), false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); + }); + + it('Should be able to select/deselect rows programmatically', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + const thirdRow = grid.getRowByIndex(2); + const forthRow = grid.getRowByIndex(3); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray(), false); + + grid.deselectRows([1, 2, 3]); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + grid.selectRows([1, 2, 3], false); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow]); + expect(grid.selectedRows()).toEqual([1, 2, 3]); + + grid.deselectRows([1, 3]); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected([firstRow, thirdRow], false); + HelperUtils.verifyRowSelected(secondRow); + + grid.selectRows([1, 2, 3, 4], true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow, forthRow]); + expect(grid.selectedRows()).toEqual([1, 2, 3, 4]); + + grid.selectRows([1], true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected([secondRow, thirdRow, forthRow], false); + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([1]); + + grid.deselectRows([2, 3, 100]); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected([secondRow, thirdRow, forthRow], false); + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([1]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); + + grid.deselectRows([1]); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow, forthRow], false); + expect(grid.selectedRows()).toEqual([]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); + }); + + it('Should be able to correctly select all rows programmatically', fakeAsync(() => { + const firstRow = grid.getRowByIndex(0); + const rowsToCheck = [firstRow, grid.getRowByIndex(1)]; + HelperUtils.verifyHeaderRowCheckboxState(fix, false, false); + + grid.selectAllRows(); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(rowsToCheck); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false, true); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + })); + + it('Should be able to select a row', fakeAsync(() => { + const firstRow = grid.getRowByIndex(0); + firstRow.selected = true; + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([1]); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + firstRow.selected = false; + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + })); + }); + + describe('Selection without primaryKey', () => { + let fix; + let grid: IgxGridComponent; + const gridData = SampleTestData.personIDNameRegionData(); + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(RowSelectionWithoutPrimaryKeyComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Verify event parameters', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const firstRow = grid.getRowByIndex(1); + const secondRow = grid.getRowByIndex(4); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([gridData[1]]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [gridData[1]], + cancel: false, + event: jasmine.anything(), + newSelection: [gridData[1]], + oldSelection: [], + removed: [] + }); + + UIInteractions.simulateClickEvent(secondRow.nativeElement, true); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([gridData[1], gridData[2], gridData[3], gridData[4]]); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledWith({ + added: [gridData[2], gridData[3], gridData[4]], + cancel: false, + event: jasmine.anything(), + newSelection: [gridData[1], gridData[2], gridData[3], gridData[4]], + oldSelection: [gridData[1]], + removed: [] + }); + }); + + it('Should persist through scrolling vertical', (async () => { + const selectedRow = grid.getRowByIndex(0); + + grid.height = '200px'; + fix.detectChanges(); + + HelperUtils.clickRowCheckbox(selectedRow); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(selectedRow); + expect(grid.selectedRows()).toEqual([gridData[0]]); + + GridFunctions.setGridScrollTop(grid, 500); + await wait(100); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([gridData[0]]); + HelperUtils.verifyRowsArraySelected(grid.rowList, false); + + GridFunctions.setGridScrollTop(grid, 0); + await wait(100); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(selectedRow); + })); + + it('Should be able to select and deselect rows from API', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(2); + const thirdRow = grid.getRowByIndex(5); + + grid.selectAllRows(); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual(gridData); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deselectRows([firstRow.rowID, secondRow.rowID, thirdRow.rowID]); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([gridData[1], gridData[3], gridData[4], gridData[6]]); + HelperUtils.verifyRowsArraySelected([firstRow, secondRow, thirdRow], false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.selectRows([firstRow.rowID, secondRow.rowID, thirdRow.rowID], false); + fix.detectChanges(); + + expect(grid.selectedRows()) + .toEqual([gridData[1], gridData[3], gridData[4], gridData[6], gridData[0], gridData[2], gridData[5]]); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + }); + + describe('Selection with primaryKey', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(GridWithPrimaryKeyComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Should be able to select row through primaryKey and index', () => { + expect(grid.primaryKey).toBeTruthy(); + expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); + expect(grid.getRowByKey(2).rowData['Name']).toMatch('Gilberto Todd'); + expect(grid.getRowByIndex(1).rowData['Name']).toMatch('Gilberto Todd'); + }); + + it('Should be able to update a cell in a row through primaryKey', () => { + expect(grid.primaryKey).toBeTruthy(); + expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); + expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Director'); + grid.updateCell('Vice President', 2, 'JobTitle'); + fix.detectChanges(); + expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Vice President'); + }); + + it('Should be able to update row through primaryKey', () => { + spyOn(grid.cdr, 'markForCheck').and.callThrough(); + expect(grid.primaryKey).toBeTruthy(); + expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); + expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Director'); + grid.updateRow({ ID: 2, Name: 'Gilberto Todd', JobTitle: 'Vice President' }, 2); + expect(grid.cdr.markForCheck).toHaveBeenCalledTimes(1); + fix.detectChanges(); + expect(grid.getRowByIndex(1).rowData['JobTitle']).toMatch('Vice President'); + expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Vice President'); + }); + + it('Should be able to delete a row through primaryKey', () => { + expect(grid.primaryKey).toBeTruthy(); + expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); + expect(grid.getRowByKey(2)).toBeDefined(); + grid.deleteRow(2); + fix.detectChanges(); + expect(grid.getRowByKey(2)).toBeUndefined(); + expect(grid.getRowByIndex(2)).toBeDefined(); + }); + + it('Should handle update by not overwriting the value in the data column specified as primaryKey', () => { + expect(grid.primaryKey).toBeTruthy(); + expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); + expect(grid.getRowByKey(2)).toBeDefined(); + grid.updateRow({ ID: 7, Name: 'Gilberto Todd', JobTitle: 'Vice President' }, 2); + fix.detectChanges(); + expect(grid.getRowByKey(7)).toBeDefined(); + expect(grid.getRowByIndex(1)).toBeDefined(); + expect(grid.getRowByIndex(1).rowData[grid.primaryKey]).toEqual(7); + }); + + it('Should be able to programatically select all rows with a correct reference, #1297', () => { + grid.selectAllRows(); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + }); + }); + + describe('Integration tests', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(RowSelectionComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + })); + + it('Paging: Should persist through paging', fakeAsync(() => { + grid.paging = true; + tick(); + fix.detectChanges(); + + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + const middleRow = grid.getRowByIndex(3); + + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + HelperUtils.clickRowCheckbox(middleRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyRowSelected(middleRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.nextPage(); + tick(); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyRowSelected(middleRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyRowSelected(middleRow, false); + 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', fakeAsync(() => { + grid.paging = true; + tick(); + fix.detectChanges(); + + const secondRow = grid.getRowByIndex(1); + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + + grid.nextPage(); + tick(); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray()); + + // Click on a single row + UIInteractions.simulateClickEvent(secondRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + 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', fakeAsync(() => { + grid.paging = true; + tick(); + fix.detectChanges(); + + const firstRow = grid.getRowByIndex(0); + const thirdRow = grid.getRowByIndex(3); + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + // Select first row on first page + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowSelected(firstRow); + + grid.nextPage(); + tick(); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + HelperUtils.verifyRowsArraySelected(grid.rowList.toArray(), false); + + // Click on the last row in page holding Shifth + UIInteractions.simulateClickEvent(thirdRow.nativeElement, true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + 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', (async () => { + let firstRow = grid.getRowByKey(1); + grid.selectRows([1]); + + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.deleteRow(1); + fix.detectChanges(); + await wait(DEBOUNCETIME); + fix.detectChanges(); + + expect(grid.getRowByKey(1)).toBeUndefined(); + expect(grid.selectedRows().includes(1)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + grid.selectAllRows(); + fix.detectChanges(); + + firstRow = grid.getRowByKey(2); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowSelected(firstRow); + + grid.deleteRow(2); + fix.detectChanges(); + await wait(DEBOUNCETIME); + fix.detectChanges(); + + expect(grid.getRowByKey(2)).toBeUndefined(); + expect(grid.selectedRows().includes(2)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deselectRows([3]); + fix.detectChanges(); + + expect(grid.selectedRows().includes(3)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.deleteRow(3); + fix.detectChanges(); + await wait(DEBOUNCETIME); + fix.detectChanges(); + + expect(grid.getRowByKey(3)).toBeUndefined(); + expect(grid.selectedRows().includes(3)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + })); + + it('CRUD: Should handle the adding new row properly', (async () => { + grid.selectAllRows(); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + grid.addRow({ ProductID: 20, ProductName: 'test', InStock: true, UnitsInStock: 1, OrderDate: new Date('2019-03-01') }); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + expect(grid.selectedRows().includes(20)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + })); + + it('CRUD: Should update selected row when update cell', () => { + let firstRow = grid.getRowByIndex(1); + firstRow.selected = true; + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([2]); + grid.updateCell(102, 2, 'ProductID'); + fix.detectChanges(); + + firstRow = grid.getRowByIndex(1); + expect(firstRow.rowID).toEqual(102); + HelperUtils.verifyRowSelected(firstRow); + expect(grid.selectedRows()).toEqual([102]); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + }); + + it('CRUD: Should update selected row when update row', () => { + grid.selectAllRows(); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.updateRow({ ProductID: 103, ProductName: 'test', InStock: true, UnitsInStock: 1, OrderDate: new Date('2019-03-01') }, 3); + fix.detectChanges(); + + const row = grid.getRowByIndex(2); + HelperUtils.verifyRowSelected(row); + expect(row.rowID).toEqual(103); + expect(grid.selectedRows().includes(3)).toBe(false); + expect(grid.selectedRows().includes(103)).toBe(true); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + + it('Sorting: Should have persistent selection through data operations', fakeAsync(() => { + const rowsToCheck = [grid.getRowByIndex(0), grid.getRowByIndex(1)]; + HelperUtils.verifyRowsArraySelected(rowsToCheck, false); + + grid.selectRows([1, 2], false); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(rowsToCheck, true); + + grid.sort({ fieldName: 'UnitsInStock', dir: SortingDirection.Desc, ignoreCase: true }); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(rowsToCheck, false); + + grid.clearSort('UnitsInStock'); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(rowsToCheck, true); + })); + + it('Summaries integration', () => { + grid.getColumnByName('ProductID').hasSummary = true; + fix.detectChanges(); + + expect(grid.summariesMargin).toBe(grid.featureColumnsWidth); + }); + + it('Filtering: Should properly check the header checkbox state when filtering, #2469', () => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + + grid.filter('ProductID', 10, IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo'), true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); + expect(grid.selectedRows()).toEqual([]); + + grid.clearFilter('ProductID'); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.filter('ProductID', 0, IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo'), true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + expect(grid.selectedRows().length).toBe(19); + + grid.filter('ProductID', 100, IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo'), true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.rowList.length).toBe(0); + expect(grid.selectedRows().length).toBe(19); + + grid.clearFilter(); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + expect(grid.selectedRows().length).toBe(19); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + }); + + it('Filtering: Should select correct rows when filter is applied', fakeAsync(() => { + spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); + const secondRow = grid.getRowByIndex(1); + + HelperUtils.clickRowCheckbox(secondRow); + fix.detectChanges(); + + expect(secondRow.selected).toBeTruthy(); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + + grid.filter('ProductName', 'Ca', IgxStringFilteringOperand.instance().condition('contains'), true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + + grid.clearFilter('ProductName'); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.getRowByIndex(1).selected).toBeTruthy(); + expect(grid.getRowByIndex(2).selected).toBeTruthy(); + expect(grid.getRowByIndex(6).selected).toBeTruthy(); + + grid.filter('ProductName', 'Ca', IgxStringFilteringOperand.instance().condition('contains'), true); + fix.detectChanges(); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); + + grid.clearFilter('ProductName'); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + expect(grid.getRowByIndex(1).selected).toBeTruthy(); + expect(grid.getRowByIndex(2).selected).toBeFalsy(); + expect(grid.getRowByIndex(6).selected).toBeFalsy(); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); + + HelperUtils.clickRowCheckbox(grid.getRowByIndex(2)); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); + + grid.filter('ProductName', 'Ca', IgxStringFilteringOperand.instance().condition('contains'), true); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyHeaderRowCheckboxState(fix); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(6); + + grid.clearFilter('ProductName'); + fix.detectChanges(); + + expect(grid.getRowByIndex(2).selected).toBeFalsy(); + expect(grid.getRowByIndex(1).selected).toBeTruthy(); + expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(6); + })); + }); + + describe('Integration with CRUD and transactions', () => { + let fix; + let grid: IgxGridComponent; + + beforeEach(fakeAsync(/** height/width setter rAF */() => { + fix = TestBed.createComponent(SelectionWithTransactionsComponent); + fix.detectChanges(); + grid = fix.componentInstance.grid; + grid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + })); + + it('Should unselect row when delete it', () => { + const firstRow = grid.getRowByIndex(0); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.deleteRowById(firstRow.rowID); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + }); + + it('Should not allow selecting rows that are deleted', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + const thirdRow = grid.getRowByIndex(2); + + grid.deleteRowById(firstRow.rowID); + grid.deleteRowById(secondRow.rowID); + fix.detectChanges(); + + grid.selectAllRows(); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(false); + expect(grid.selectedRows().includes(secondRow.rowID)).toBe(false); + expect(grid.selectedRows().includes(thirdRow.rowID)).toBe(true); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.selectRows([firstRow.rowID, secondRow.rowID, thirdRow.rowID]); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(true); + expect(grid.selectedRows().includes(secondRow.rowID)).toBe(true); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + + it('Should have correct header checkbox when delete a row', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(1); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + HelperUtils.verifyRowsArraySelected(grid.rowList); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deleteRowById(firstRow.rowID); + fix.detectChanges(); + + expect(grid.selectedRows().length).toEqual(7); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deselectRows([secondRow.rowID]); + fix.detectChanges(); + + expect(grid.selectedRows().length).toEqual(6); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.deleteRowById(secondRow.rowID); + fix.detectChanges(); + + expect(grid.selectedRows().length).toEqual(6); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + + it('Should not be possible to select deleted row', () => { + const firstRow = grid.getRowByIndex(0); + const secondRow = grid.getRowByIndex(3); + + grid.deleteRowById(firstRow.rowID); + fix.detectChanges(); + + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + UIInteractions.simulateClickEvent(firstRow.nativeElement); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + UIInteractions.simulateClickEvent(secondRow.nativeElement); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([secondRow.rowID]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + UIInteractions.simulateClickEvent(firstRow.nativeElement, true); + fix.detectChanges(); + + expect(grid.selectedRows()).toEqual([secondRow.rowID]); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(false); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + + it('Should have correct header checkbox when undo row deleting', () => { + const firstRow = grid.getRowByIndex(0); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(true); + HelperUtils.verifyRowSelected(firstRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.deleteRowById(firstRow.rowID); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(false); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.transactions.undo(); + fix.detectChanges(); + + expect(grid.selectedRows().length).toBe(7); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + grid.transactions.redo(); + fix.detectChanges(); + + expect(grid.selectedRows().includes(firstRow.rowID)).toBe(false); + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + }); + + it('Should have correct header checkbox when add row', (async () => { + grid.height = '800px'; + fix.detectChanges(); + await wait(DEBOUNCETIME); + + grid.selectAllRows(); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + + grid.addRow({ ID: 112, ParentID: 177, Name: 'Ricardo Matias', HireDate: new Date('Dec 27, 2017'), Age: 55, OnPTO: false }); + await wait(DEBOUNCETIME); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + const addedRow = grid.getRowByKey(112); + HelperUtils.verifyRowSelected(addedRow, false); + expect(grid.selectedRows().includes(112)).toBe(false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + + HelperUtils.clickRowCheckbox(addedRow); + fix.detectChanges(); + + expect(grid.selectedRows().includes(112)).toBe(true); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowSelected(addedRow); + })); + + it('Should be able to select added row', (async () => { + grid.height = '800px'; + fix.detectChanges(); + await wait(DEBOUNCETIME); + + grid.addRow({ ID: 112, ParentID: 177, Name: 'Ricardo Matias', HireDate: new Date('Dec 27, 2017'), Age: 55, OnPTO: false }); + await wait(DEBOUNCETIME); + fix.detectChanges(); + await wait(DEBOUNCETIME); + + const addedRow = grid.getRowByKey(112); + HelperUtils.verifyRowSelected(addedRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + HelperUtils.clickHeaderRowCheckbox(fix); + fix.detectChanges(); + + expect(grid.selectedRows().includes(112)).toBe(true); + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + HelperUtils.verifyRowSelected(addedRow); + })); + }); +}); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html index 300803d6328..58c296c29a4 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.html @@ -2,13 +2,13 @@
-
+
- -
- + +
+
@@ -30,7 +30,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate"> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection"> @@ -49,7 +50,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate"> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection"> @@ -75,7 +77,8 @@ [rowData]="rowData" [visibleColumnIndex]="child.visibleIndex" [value]="rowData[child.field]" - [cellTemplate]="child.bodyTemplate"> + [cellTemplate]="child.bodyTemplate" + [cellSelectionMode]="grid.cellSelection">
@@ -96,7 +99,8 @@ [rowData]="rowData" [visibleColumnIndex]="child.visibleIndex" [value]="rowData[child.field]" - [cellTemplate]="child.bodyTemplate"> + [cellTemplate]="child.bodyTemplate" + [cellSelectionMode]="grid.cellSelection">
diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.ts index cad905c0a09..6f29f93393a 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-row.component.ts @@ -2,7 +2,6 @@ import { Component, forwardRef, ChangeDetectionStrategy, ElementRef, ChangeDetec import { IgxGridComponent } from './grid.component'; import { IgxRowComponent } from '../row.component'; import { GridBaseAPIService } from '../api.service'; -import { IgxSelectionAPIService } from '../../core/selection'; import { IgxGridSelectionService, IgxGridCRUDService } from '../../core/grid-selection'; @Component({ @@ -17,11 +16,10 @@ export class IgxGridRowComponent extends IgxRowComponent { public gridAPI: GridBaseAPIService, public crudService: IgxGridCRUDService, public selectionService: IgxGridSelectionService, - selection: IgxSelectionAPIService, public element: ElementRef, public cdr: ChangeDetectorRef) { // D.P. constructor duplication due to es6 compilation, might be obsolete in the future - super(gridAPI, crudService, selectionService, selection, element, cdr); + super(gridAPI, crudService, selectionService, element, cdr); } @HostBinding('class.igx-grid__tr--mrl') diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-selection.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-selection.spec.ts deleted file mode 100644 index ce9895cd459..00000000000 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-selection.spec.ts +++ /dev/null @@ -1,1158 +0,0 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { By } from '@angular/platform-browser'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { Calendar } from '../../calendar'; -import { SortingDirection } from '../../data-operations/sorting-expression.interface'; -import { IgxGridComponent } from './grid.component'; -import { IgxGridModule, IgxColumnComponent } from './index'; -import { wait } from '../../test-utils/ui-interactions.spec'; -import { IgxStringFilteringOperand, IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; -import { configureTestSuite } from '../../test-utils/configure-suite'; -import { ScrollsComponent, GridWithPrimaryKeyComponent, SelectionComponent } from '../../test-utils/grid-samples.spec'; -import { SampleTestData } from '../../test-utils/sample-test-data.spec'; -import { IgxHierarchicalGridMultiLayoutComponent } from '../hierarchical-grid/hierarchical-grid.spec'; -import { IgxHierarchicalGridModule } from '../hierarchical-grid/hierarchical-grid.module'; - -describe('IgxGrid - Row Selection #grid', () => { - configureTestSuite(); - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ - GridWithPrimaryKeyComponent, - GridWithPagingAndSelectionComponent, - SelectionComponent, - GridWithSelectionFilteringComponent, - ScrollsComponent, - GridSummaryComponent, - GridCancelableComponent, - GridFeaturesComponent, - HierarchicalGridRowSelectableIslandComponent - ], - imports: [ - NoopAnimationsModule, - IgxGridModule, - IgxHierarchicalGridModule - ] - }) - .compileComponents(); - })); - - it('Should be able to select row through primaryKey and index', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - - expect(grid.primaryKey).toBeTruthy(); - expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); - expect(grid.getRowByKey(2).rowData['Name']).toMatch('Gilberto Todd'); - expect(grid.getRowByIndex(1).rowData['Name']).toMatch('Gilberto Todd'); - }); - - it('Should be able to update a cell in a row through primaryKey', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - expect(grid.primaryKey).toBeTruthy(); - expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); - expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Director'); - grid.updateCell('Vice President', 2, 'JobTitle'); - fix.detectChanges(); - expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Vice President'); - }); - - it('Should be able to update row through primaryKey', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - // R.K. Why would the test care about the underlying implementation ? - // spyOn(grid.cdr, 'markForCheck').and.callThrough(); - expect(grid.primaryKey).toBeTruthy(); - expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); - expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Director'); - grid.updateRow({ID: 2, Name: 'Gilberto Todd', JobTitle: 'Vice President'}, 2); - // expect(grid.cdr.markForCheck).toHaveBeenCalledTimes(1); - fix.detectChanges(); - expect(grid.getRowByIndex(1).rowData['JobTitle']).toMatch('Vice President'); - expect(grid.getRowByKey(2).rowData['JobTitle']).toMatch('Vice President'); - }); - - it('Should be able to delete a row through primaryKey', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - expect(grid.primaryKey).toBeTruthy(); - expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); - expect(grid.getRowByKey(2)).toBeDefined(); - grid.deleteRow(2); - fix.detectChanges(); - expect(grid.getRowByKey(2)).toBeUndefined(); - expect(grid.getRowByIndex(2)).toBeDefined(); - }); - - it('Should handle update by not overwriting the value in the data column specified as primaryKey', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - expect(grid.primaryKey).toBeTruthy(); - expect(grid.rowList.length).toEqual(10, 'All 10 rows should initialized'); - expect(grid.getRowByKey(2)).toBeDefined(); - grid.updateRow({ID: 7, Name: 'Gilberto Todd', JobTitle: 'Vice President'}, 2); - fix.detectChanges(); - expect(grid.getRowByKey(7)).toBeDefined(); - expect(grid.getRowByIndex(1)).toBeDefined(); - expect(grid.getRowByIndex(1).rowData[grid.primaryKey]).toEqual(7); - }); - - it('Should persist through paging', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - const grid = fix.componentInstance.gridSelection2; - fix.detectChanges(); - const nextBtn: HTMLElement = fix.nativeElement.querySelector('.nextPageBtn'); - const prevBtn: HTMLElement = fix.nativeElement.querySelector('.prevPageBtn'); - const selectedRow = grid.getRowByIndex(5); - expect(selectedRow).toBeDefined(); - const checkboxElement: HTMLElement = selectedRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(selectedRow.isSelected).toBeFalsy(); - checkboxElement.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow.isSelected).toBeTruthy(); - nextBtn.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow.isSelected).toBeFalsy(); - prevBtn.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow.isSelected).toBeTruthy(); - })); - - it('Should persist through paging - multiple', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - const grid = fix.componentInstance.gridSelection2; - const nextBtn: HTMLElement = fix.nativeElement.querySelector('.nextPageBtn'); - const prevBtn: HTMLElement = fix.nativeElement.querySelector('.prevPageBtn'); - const firstRow = grid.getRowByIndex(0); - const middleRow = grid.getRowByIndex(4); - const lastRow = grid.getRowByIndex(9); - expect(firstRow).toBeDefined(); - expect(middleRow).toBeDefined(); - expect(lastRow).toBeDefined(); - const checkboxElement1: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - const checkboxElement2: HTMLElement = middleRow.nativeElement.querySelector('.igx-checkbox__input'); - const checkboxElement3: HTMLElement = lastRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(firstRow.isSelected).toBeFalsy(); - expect(middleRow.isSelected).toBeFalsy(); - expect(lastRow.isSelected).toBeFalsy(); - checkboxElement1.click(); - checkboxElement2.click(); - checkboxElement3.click(); - await wait(); - fix.detectChanges(); - expect(firstRow.isSelected).toBeTruthy(); - expect(middleRow.isSelected).toBeTruthy(); - expect(lastRow.isSelected).toBeTruthy(); - nextBtn.click(); - await wait(); - fix.detectChanges(); - expect(firstRow.isSelected).toBeFalsy(); - expect(middleRow.isSelected).toBeFalsy(); - expect(lastRow.isSelected).toBeFalsy(); - prevBtn.click(); - await wait(); - fix.detectChanges(); - expect(firstRow.isSelected).toBeTruthy(); - expect(middleRow.isSelected).toBeTruthy(); - expect(lastRow.isSelected).toBeTruthy(); - })); - - it('Should persist through paging - multiple selection', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - const grid = fix.componentInstance.gridSelection2; - const nextBtn: HTMLElement = fix.nativeElement.querySelector('.nextPageBtn'); - const prevBtn: HTMLElement = fix.nativeElement.querySelector('.prevPageBtn'); - const selectedRow1 = grid.getRowByIndex(5); - const selectedRow2 = grid.getRowByIndex(3); - const selectedRow3 = grid.getRowByIndex(0); - - expect(selectedRow1).toBeDefined(); - expect(selectedRow2).toBeDefined(); - expect(selectedRow3).toBeDefined(); - const checkboxElement1: HTMLElement = selectedRow1.nativeElement.querySelector('.igx-checkbox__input'); - const checkboxElement2: HTMLElement = selectedRow2.nativeElement.querySelector('.igx-checkbox__input'); - const checkboxElement3: HTMLElement = selectedRow3.nativeElement.querySelector('.igx-checkbox__input'); - - expect(selectedRow1.isSelected).toBeFalsy(); - expect(selectedRow2.isSelected).toBeFalsy(); - expect(selectedRow3.isSelected).toBeFalsy(); - checkboxElement1.click(); - checkboxElement2.click(); - checkboxElement3.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow1.isSelected).toBeTruthy(); - expect(selectedRow2.isSelected).toBeTruthy(); - expect(selectedRow3.isSelected).toBeTruthy(); - nextBtn.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow1.isSelected).toBeFalsy(); - expect(selectedRow2.isSelected).toBeFalsy(); - expect(selectedRow3.isSelected).toBeFalsy(); - prevBtn.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow1.isSelected).toBeTruthy(); - expect(selectedRow2.isSelected).toBeTruthy(); - expect(selectedRow3.isSelected).toBeTruthy(); - })); - - it('Should persist through scrolling', (async () => { - let selectedCell; - const fix = TestBed.createComponent(SelectionComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - const gridElement: HTMLElement = fix.nativeElement.querySelector('.igx-grid'); - const selectedRow = grid.getRowByIndex(0); - expect(selectedRow).toBeDefined(); - const checkboxElement: HTMLElement = selectedRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(selectedRow.isSelected).toBeFalsy(); - checkboxElement.click(); - await wait(); - fix.detectChanges(); - expect(selectedRow.isSelected).toBeTruthy(); - expect(grid.selectedRows()).toBeDefined(); - expect(grid.rowList.first).toBeDefined(); - expect(grid.rowList.first.isSelected).toBeTruthy(); - selectedCell = grid.getCellByKey('2_0', 'Column2'); - const scrollBar = gridElement.querySelector('.igx-vhelper--vertical'); - scrollBar.scrollTop = 500; - await wait(100); - fix.detectChanges(); - expect(grid.selectedRows()).toBeDefined(); - expect(grid.rowList.first).toBeDefined(); - expect(grid.rowList.first.isSelected).toBeFalsy(); - scrollBar.scrollTop = 0; - await wait(100); - fix.detectChanges(); - - expect(selectedRow.isSelected).toBeTruthy(); - expect(grid.selectedRows()).toBeDefined(); - expect(grid.rowList.first).toBeDefined(); - expect(grid.rowList.first.isSelected).toBeTruthy(); - // expect(selectedRow.nativeElement.class).toContain("igx-grid__tr--selected"); - })); - - it('Header checkbox should select/deselect all rows', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const firstRow = grid.getRowByIndex(0); - const middleRow = grid.getRowByIndex(5); - const lastRow = grid.getRowByIndex(9); - - expect(headerRow).toBeDefined(); - expect(firstRow).toBeDefined(); - expect(middleRow).toBeDefined(); - expect(lastRow).toBeDefined(); - - const headerCheckboxElement: HTMLElement = headerRow.querySelector('.igx-checkbox__input'); - expect(firstRow.isSelected).toBeFalsy(); - expect(middleRow.isSelected).toBeFalsy(); - expect(lastRow.isSelected).toBeFalsy(); - - headerCheckboxElement.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(middleRow.isSelected).toBeTruthy(); - expect(lastRow.isSelected).toBeTruthy(); - - headerCheckboxElement.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(middleRow.isSelected).toBeFalsy(); - expect(lastRow.isSelected).toBeFalsy(); - })); - - it('Should handle the deleteion on a selected row propertly', (async () => { - const fix = TestBed.createComponent(SelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const firstRow = grid.getRowByKey('0_0'); - const firstRowCheckbox: HTMLInputElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - const headerCheckboxElement: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); - - firstRowCheckbox.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(headerCheckboxElement.checked).toBeFalsy(); - expect(headerCheckboxElement.indeterminate).toBeTruthy(); - - grid.deleteRow('0_0'); - fix.detectChanges(); - - expect(grid.getRowByKey('0_0')).toBeUndefined(); - expect(headerCheckboxElement.checked).toBeFalsy(); - expect(headerCheckboxElement.indeterminate).toBeFalsy(); - })); - - it('Header checkbox should deselect all rows - scenario when clicking first row, while header checkbox is clicked', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const firstRow = grid.getRowByIndex(0); - - expect(headerRow).toBeDefined(); - expect(firstRow).toBeDefined(); - - const headerCheckboxElement: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); - - expect(firstRow.isSelected).toBeFalsy(); - - headerCheckboxElement.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(headerCheckboxElement.checked).toBeTruthy(); - expect(headerCheckboxElement.indeterminate).toBeFalsy(); - - const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - targetCheckbox.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(headerCheckboxElement.checked).toBeFalsy(); - expect(headerCheckboxElement.indeterminate).toBeTruthy(); - - targetCheckbox.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(headerCheckboxElement.checked).toBeTruthy(); - expect(headerCheckboxElement.indeterminate).toBeFalsy(); - - headerCheckboxElement.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(headerCheckboxElement.checked).toBeFalsy(); - expect(headerCheckboxElement.indeterminate).toBeFalsy(); - })); - - it('Checkbox should select/deselect row', (async () => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const firstRow = grid.getRowByIndex(0); - const secondRow = grid.getRowByIndex(1); - - spyOn(grid, 'triggerRowSelectionChange').and.callThrough(); - spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); - expect(firstRow).toBeDefined(); - expect(secondRow).toBeDefined(); - - const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - - targetCheckbox.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeFalsy(); - - targetCheckbox.click(); - await wait(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(grid.triggerRowSelectionChange).toHaveBeenCalledTimes(2); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); - })); - - it('Should have checkbox on each row if rowSelectable is true', (async () => { - const fix = TestBed.createComponent(ScrollsComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - - grid.rowSelectable = false; - fix.detectChanges(); - - for (const row of grid.rowList.toArray()) { - const checkBoxElement = row.nativeElement.querySelector('div.igx-grid__cbx-selection'); - expect(checkBoxElement).toBeNull(); - } - - grid.rowSelectable = true; - fix.detectChanges(); - - for (const row of grid.rowList.toArray()) { - const checkBoxElement = row.nativeElement.querySelector('div.igx-grid__cbx-selection'); - expect(checkBoxElement).toBeDefined(); - - const checkboxInputElement = checkBoxElement.querySelector('.igx-checkbox__input'); - expect(checkboxInputElement).toBeDefined(); - } - - const horScroll = grid.parentVirtDir.getHorizontalScroll(); - horScroll.scrollLeft = 1000; - await wait(100); - fix.detectChanges(); - - for (const row of grid.rowList.toArray()) { - - // ensure we were scroll - the first cell's column index should not be 0 - const firstCellColumnIndex = row.cells.toArray()[0].columnIndex; - expect(firstCellColumnIndex).not.toEqual(0); - - const checkBoxElement = row.nativeElement.querySelector('div.igx-grid__cbx-selection'); - expect(checkBoxElement).toBeDefined(); - - const checkboxInputElement = checkBoxElement.querySelector('.igx-checkbox__input'); - expect(checkboxInputElement).toBeDefined(); - } - })); - - // API Methods - - it('Simple row selection', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridWithSelectionFilteringComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection4; - const secondRow = grid.getRowByIndex(1); - const targetCheckbox: HTMLElement = secondRow.nativeElement.querySelector('.igx-checkbox__input'); - - targetCheckbox.click(); - fix.detectChanges(); - - expect(grid.getRowByIndex(1).isSelected).toBeTruthy(); - spyOn(grid.onRowSelectionChange, 'emit').and.callFake((args) => { - args.newSelection = args.oldSelection; - }); - - targetCheckbox.click(); - fix.detectChanges(); - - expect(grid.getRowByIndex(1).isSelected).toBeTruthy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); - })); - - it('Should be able to select/deselect rows programatically', fakeAsync(() => { - const fix = TestBed.createComponent(SelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid; - let rowsCollection = []; - const firstRow = grid.getRowByKey('0_0'); - const secondRow = grid.getRowByKey('0_1'); - const thirdRow = grid.getRowByKey('0_2'); - - spyOn(grid, 'triggerRowSelectionChange').and.callThrough(); - spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection).toEqual([]); - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - - grid.deselectRows(['0_0', '0_1', '0_2']); - tick(); - fix.detectChanges(); - - expect(rowsCollection).toEqual([]); - - grid.selectRows(['0_0', '0_1', '0_2'], false); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeTruthy(); - expect(thirdRow.isSelected).toBeTruthy(); - - rowsCollection = grid.selectedRows(); - expect(rowsCollection.length).toEqual(3); - - grid.deselectRows(['0_0', '0_1', '0_2']); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection.length).toEqual(0); - expect(grid.triggerRowSelectionChange).toHaveBeenCalledTimes(3); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); - })); - - it('Should be able to select/deselect ALL rows programatically', fakeAsync(() => { - const fix = TestBed.createComponent(SelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid; - let rowsCollection = []; - const firstRow = grid.getRowByKey('0_0'); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection).toEqual([]); - expect(firstRow.isSelected).toBeFalsy(); - spyOn(grid, 'triggerRowSelectionChange').and.callThrough(); - spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); - - grid.selectAllRows(); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection.length).toEqual(500); - - grid.deselectAllRows(); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection.length).toEqual(0); - expect(grid.triggerRowSelectionChange).toHaveBeenCalledTimes(2); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); - })); - - it('Filtering and row selection', fakeAsync(() => { - const fix = TestBed.createComponent(GridWithSelectionFilteringComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection4; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckbox: HTMLInputElement = headerRow.querySelector('.igx-checkbox__input'); - spyOn(grid.onRowSelectionChange, 'emit').and.callThrough(); - - const secondRow = grid.getRowByIndex(1); - expect(secondRow).toBeDefined(); - - const targetCheckbox: HTMLElement = secondRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(secondRow.isSelected).toBeFalsy(); - - let rowsCollection = []; - - rowsCollection = grid.selectedRows(); - expect(rowsCollection).toEqual([]); - - grid.filter('ProductName', 'Ignite', IgxStringFilteringOperand.instance().condition('contains'), true); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); - - rowsCollection = grid.selectedRows(); - - expect(rowsCollection).toEqual([]); - expect(headerCheckbox.getAttribute('aria-checked')).toMatch('false'); - expect(headerCheckbox.getAttribute('aria-label')).toMatch('Select all filtered'); - - grid.clearFilter('ProductName'); - fix.detectChanges(); - - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(0); - - targetCheckbox.click(); - fix.detectChanges(); - - expect(secondRow.isSelected).toBeTruthy(); - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeTruthy(); - expect(secondRow.isSelected).toBeTruthy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); - - grid.filter('ProductName', 'Ignite', IgxStringFilteringOperand.instance().condition('contains'), true); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(1); - - headerCheckbox.click(); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeTruthy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - expect(headerCheckbox.getAttribute('aria-checked')).toMatch('true'); - expect(headerCheckbox.getAttribute('aria-label')).toMatch('Deselect all filtered'); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(2); - - grid.clearFilter('ProductName'); - fix.detectChanges(); - // expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeTruthy(); - expect(grid.getRowByIndex(0).isSelected).toBeTruthy(); - expect(grid.getRowByIndex(1).isSelected).toBeTruthy(); - expect(grid.getRowByIndex(2).isSelected).toBeTruthy(); - - grid.filter('ProductName', 'Ignite', IgxStringFilteringOperand.instance().condition('contains'), true); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeTruthy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - - headerCheckbox.click(); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); - - grid.clearFilter('ProductName'); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeTruthy(); - expect(grid.getRowByIndex(0).isSelected).toBeFalsy(); - expect(grid.getRowByIndex(1).isSelected).toBeTruthy(); - expect(grid.getRowByIndex(2).isSelected).toBeFalsy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(3); - - grid.getRowByIndex(0).nativeElement.querySelector('.igx-checkbox__input').click(); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeTruthy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); - - grid.filter('ProductName', 'Ignite', IgxStringFilteringOperand.instance().condition('contains'), true); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeTruthy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(4); - - headerCheckbox.click(); - fix.detectChanges(); - - headerCheckbox.click(); - fix.detectChanges(); - - expect(headerCheckbox.checked).toBeFalsy(); - expect(headerCheckbox.indeterminate).toBeFalsy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(6); - - grid.clearFilter('ProductName'); - fix.detectChanges(); - - expect(grid.getRowByIndex(0).isSelected).toBeFalsy(); - expect(grid.getRowByIndex(1).isSelected).toBeTruthy(); - expect(grid.onRowSelectionChange.emit).toHaveBeenCalledTimes(6); - })); - - it('Should have persistent selection through data operations - sorting', fakeAsync(() => { - const fix = TestBed.createComponent(SelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const firstRow = grid.getRowByIndex(0); - const secondRow = grid.getRowByIndex(1); - - expect(firstRow).toBeDefined(); - expect(secondRow).toBeDefined(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - - let rowsCollection = []; - rowsCollection = grid.selectedRows(); - - expect(rowsCollection).toEqual([]); - - grid.selectRows(['0_0', '0_1'], false); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeTruthy(); - expect(grid.rowList.find((row) => row === firstRow)).toBeTruthy(); - - grid.sort({ fieldName: 'Column1', dir: SortingDirection.Desc, ignoreCase: true }); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - - grid.clearSort('Column1'); - tick(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeTruthy(); - })); - - it('Clicking any other cell is not selecting the row', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const firstRow = grid.getRowByIndex(0); - const rv = fix.debugElement.query(By.css('.igx-grid__td')); - - expect(firstRow).toBeDefined(); - expect(firstRow.isSelected).toBeFalsy(); - - rv.nativeElement.dispatchEvent(new Event('focus')); - fix.detectChanges(); - rv.triggerEventHandler('click', {}); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - })); - - it('Clicking any other cell is not deselecting the row', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const firstRow = grid.getRowByIndex(0); - const rv = fix.debugElement.query(By.css('.igx-grid__td')); - - expect(rv).toBeDefined(); - expect(firstRow).toBeDefined(); - - const targetCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - expect(firstRow.isSelected).toBeFalsy(); - - targetCheckbox.click(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - - rv.nativeElement.dispatchEvent(new Event('focus')); - rv.triggerEventHandler('click', {}); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - })); - - it('ARIA support', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridWithSelectionFilteringComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection4; - const firstRow = grid.getRowByIndex(0).nativeElement; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckboxElement: HTMLElement = headerRow.querySelector('.igx-checkbox__input'); - - expect(firstRow.getAttribute('aria-selected')).toMatch('false'); - expect(headerCheckboxElement.getAttribute('aria-checked')).toMatch('false'); - expect(headerCheckboxElement.getAttribute('aria-label')).toMatch('Select all'); - - headerCheckboxElement.click(); - fix.detectChanges(); - - expect(firstRow.getAttribute('aria-selected')).toMatch('true'); - expect(headerCheckboxElement.getAttribute('aria-checked')).toMatch('true'); - expect(headerCheckboxElement.getAttribute('aria-label')).toMatch('Deselect all'); - - headerCheckboxElement.click(); - fix.detectChanges(); - - expect(firstRow.getAttribute('aria-selected')).toMatch('false'); - expect(headerCheckboxElement.getAttribute('aria-checked')).toMatch('false'); - expect(headerCheckboxElement.getAttribute('aria-label')).toMatch('Select all'); - })); - - it('Summaries integration', () => { - const fixture = TestBed.createComponent(GridSummaryComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.gridSummaries; - expect(grid.summariesMargin).toBe(grid.featureColumnsWidth); - }); - - - - it('Should be able to programatically overwrite the selection using onRowSelectionChange event', () => { - const fixture = TestBed.createComponent(GridCancelableComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.gridCancelable; - const firstRow = grid.getRowByIndex(0); - const firstRowCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - const secondRow = grid.getRowByIndex(1); - const secondRowCheckbox: HTMLElement = secondRow.nativeElement.querySelector('.igx-checkbox__input'); - const thirdRow = grid.getRowByIndex(2); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - - firstRowCheckbox.dispatchEvent(new Event('click', {})); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - - firstRowCheckbox.dispatchEvent(new Event('click', {})); - secondRowCheckbox.dispatchEvent(new Event('click', {})); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(secondRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - }); - - it('Should be able to correctly select all rows programatically', fakeAsync(() => { - const fixture = TestBed.createComponent(SelectionComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid; - const firstRow = grid.getRowByIndex(0); - const secondRow = grid.getRowByIndex(1); - const firstRowCheckbox: HTMLElement = firstRow.nativeElement.querySelector('.igx-checkbox__input'); - - expect(firstRow.isSelected).toBeFalsy(); - - grid.selectAllRows(); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(secondRow.isSelected).toBeTruthy(); - - firstRowCheckbox.dispatchEvent(new Event('click', {})); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - })); - - it('Should be able to programatically select all rows with a correct reference, #1297', () => { - const fix = TestBed.createComponent(GridWithPrimaryKeyComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.grid; - grid.selectAllRows(); - fix.detectChanges(); - - expect(grid.selectedRows()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - }); - - it('Should be able to programatically select all rows and keep the header checkbox intact, #1298', - fakeAsync(/** height/width setter rAF */() => { - const fixture = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.gridSelection2; - const headerRow: HTMLElement = fixture.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckboxElement: HTMLElement = headerRow.querySelector('.igx-checkbox'); - const firstRow = grid.getRowByIndex(0); - const thirdRow = grid.getRowByIndex(2); - - expect(firstRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - - grid.selectAllRows(); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(thirdRow.isSelected).toBeTruthy(); - expect(headerCheckboxElement.classList.contains('igx-checkbox--checked')).toBeTruthy(); - - grid.selectAllRows(); - fixture.detectChanges(); - - expect(firstRow.isSelected).toBeTruthy(); - expect(thirdRow.isSelected).toBeTruthy(); - expect(headerCheckboxElement.classList.contains('igx-checkbox--checked')).toBeTruthy(); - })); - - it('Should be able to programatically get a collection of all selected rows', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridWithPagingAndSelectionComponent); - fix.detectChanges(); - - const grid = fix.componentInstance.gridSelection2; - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const firstRow = grid.getRowByIndex(0); - const thirdRow = grid.getRowByIndex(2); - const thirdRowCheckbox: HTMLElement = thirdRow.nativeElement.querySelector('.igx-checkbox__input'); - - expect(firstRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - expect(grid.selectedRows()).toEqual([]); - - thirdRowCheckbox.click(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeTruthy(); - expect(grid.selectedRows()).toEqual(['0_2']); - - thirdRowCheckbox.click(); - fix.detectChanges(); - - expect(firstRow.isSelected).toBeFalsy(); - expect(thirdRow.isSelected).toBeFalsy(); - expect(grid.selectedRows()).toEqual([]); - })); - - it('Should properly check the header checkbox state when filtering, #2469', fakeAsync(() => { - const fixture = TestBed.createComponent(GridWithSelectionFilteringComponent); - fixture.detectChanges(); - - const grid = fixture.componentInstance.grid; - const headerCheckbox: HTMLElement = fixture.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__input'); - grid.primaryKey = 'ID'; - fixture.detectChanges(); - tick(); - headerCheckbox.click(); - tick(); - fixture.detectChanges(); - tick(); - expect(headerCheckbox.parentElement.classList).toContain('igx-checkbox--checked'); - grid.filter('Downloads', 0, IgxNumberFilteringOperand.instance().condition('greaterThanOrEqualTo'), true); - tick(); - fixture.detectChanges(); - tick(); - expect(headerCheckbox.parentElement.classList).toContain('igx-checkbox--checked'); - })); - - it('Hide row checkboxes, when all columns are hidden', fakeAsync(/** height/width setter rAF */() => { - const fix = TestBed.createComponent(GridFeaturesComponent); - fix.detectChanges(); - const grid = fix.componentInstance.grid; - let headerCheck: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__input'); - let rowCheck: HTMLElement = grid.getRowByIndex(0).nativeElement.querySelector('.igx-checkbox__input'); - expect(headerCheck).toBeDefined(); - expect(rowCheck).toBeDefined(); - - grid.columns.forEach(c => c.hidden = true); - fix.detectChanges(); - headerCheck = fix.nativeElement.querySelector('.igx-checkbox__input'); - expect(headerCheck).toBeNull(); - - grid.columns.forEach(c => c.hidden = false); - fix.detectChanges(); - headerCheck = fix.nativeElement.querySelector('.igx-grid__thead').querySelector('.igx-checkbox__input'); - rowCheck = grid.getRowByIndex(0).nativeElement.querySelector('.igx-checkbox__input'); - expect(headerCheck).toBeDefined(); - expect(rowCheck).toBeDefined(); - })); - - it('Set rowSelectable on HGrid row island', fakeAsync(() => { - expect(() => { - const fix = TestBed.createComponent(HierarchicalGridRowSelectableIslandComponent); - fix.detectChanges(); - }).not.toThrow(); - })); - -}); - -@Component({ - template: ` - - - - - ` -}) -export class GridWithPagingAndSelectionComponent implements OnInit { - public data = []; - - @ViewChild('gridSelection2', { read: IgxGridComponent, static: true }) - public gridSelection2: IgxGridComponent; - - ngOnInit() { - const bigData = []; - for (let i = 0; i < 100; i++) { - for (let j = 0; j < 5; j++) { - bigData.push({ - ID: i.toString() + '_' + j.toString(), - Column1: i * j, - Column2: i * j * Math.pow(10, i), - Column3: i * j * Math.pow(100, i) - }); - } - } - this.data = bigData; - } - - public ChangePage(val) { - switch (val) { - case -1: - this.gridSelection2.previousPage(); - break; - case 1: - this.gridSelection2.nextPage(); - break; - default: - this.gridSelection2.paginate(val); - break; - } - } -} - -@Component({ - template: ` - - - - - - - - ` -}) -export class GridWithSelectionFilteringComponent { - - public timeGenerator: Calendar = new Calendar(); - public today: Date = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate(), 0, 0, 0); - - @ViewChild('gridSelection4', { read: IgxGridComponent, static: true }) - public gridSelection4: IgxGridComponent; - - public data = SampleTestData.productInfoData(); - - @ViewChild(IgxGridComponent, { static: true }) public grid: IgxGridComponent; -} - - - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class GridSummaryComponent { - public data = SampleTestData.foodProductData(); - @ViewChild('grid1', { read: IgxGridComponent, static: true }) - public gridSummaries: IgxGridComponent; -} - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class GridCancelableComponent { - public data = SampleTestData.foodProductData(); - @ViewChild('gridCancelable', { read: IgxGridComponent, static: true }) - public gridCancelable: IgxGridComponent; - - public cancelClick(evt) { - if (evt.row && (evt.row.index + 1) % 2 === 0) { - evt.newSelection = evt.oldSelection || []; - } - } -} - -@Component({ - template: ` - - - ` -}) -export class GridFeaturesComponent { - - @ViewChild('grid1', { read: IgxGridComponent, static: true }) public grid: IgxGridComponent; - public data = [ - { - Name: 'Alice', - Age: 25 - }, - { - Name: 'Bob', - Age: 23 - } - ]; - - public initColumns(column: IgxColumnComponent) { - column.filterable = true; - column.sortable = true; - column.editable = true; - column.resizable = true; - } -} - -@Component({ - template: ` - - - - - - - - - - - - - ` -}) -export class HierarchicalGridRowSelectableIslandComponent extends IgxHierarchicalGridMultiLayoutComponent { } 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 eabcd718962..1704ae78f20 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.html +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.html @@ -40,7 +40,7 @@
-
- +
@@ -117,7 +117,7 @@ [igxForItemSize]="hasColumnLayouts ? rowHeight * multiRowLayoutRowSize + 1 : renderedRowHeight" #verticalScrollContainer (onChunkPreload)="dataLoading($event)"> - + diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts index 922c3f10123..2b31dcb356e 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.spec.ts @@ -12,7 +12,7 @@ import { IgxRowComponent } from '../row.component'; import { IgxGridTransaction, IGridEditEventArgs } from '../grid-base.component'; import { IgxColumnComponent } from '../column.component'; import { IForOfState } from '../../directives/for-of/for_of.directive'; -import { IgxGridModule } from './index'; +import { IgxGridModule, GridSelectionMode } from './index'; import { DisplayDensity } from '../../core/displayDensity'; import { DataType } from '../../data-operations/data-util'; import { GridTemplateStrings } from '../../test-utils/template-strings.spec'; @@ -1339,7 +1339,7 @@ describe('IgxGrid Component Tests #grid', () => { fix.detectChanges(); const grid = fix.componentInstance.grid; const hScroll = fix.debugElement.query(By.css('.igx-grid__scroll')); - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; fix.detectChanges(); grid.columns[0].width = '70%'; @@ -4071,24 +4071,6 @@ describe('IgxGrid Component Tests #grid', () => { expect(grid.totalPages).toEqual(2); })); - it('Should not allow selecting rows that are deleted', fakeAsync(() => { - const fixture = TestBed.createComponent(IgxGridRowEditingTransactionComponent); - fixture.detectChanges(); - tick(16); - - const grid = fixture.componentInstance.grid; - grid.rowSelectable = true; - fixture.detectChanges(); - - grid.deleteRowById(2); - grid.deleteRowById(3); - - fixture.detectChanges(); - grid.selectRows([2, 3, 4]); - fixture.detectChanges(); - expect(grid.selectedRows()).toEqual([4]); - })); - it('Should not log transaction when exit edit mode on row with state and with no changes', fakeAsync(() => { const fixture = TestBed.createComponent(IgxGridRowEditingTransactionComponent); fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts index 5ca6be6beb7..fd91fc143e4 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.component.ts @@ -4,7 +4,7 @@ import { IterableDiffers, ViewContainerRef, Inject, AfterContentInit, HostBinding, forwardRef, OnInit, Optional } from '@angular/core'; import { GridBaseAPIService } from '../api.service'; -import { IgxGridBaseComponent, IgxGridTransaction, IFocusChangeEventArgs, IGridDataBindable, FilterMode } from '../grid-base.component'; +import { IgxGridBaseComponent, IgxGridTransaction, IGridDataBindable, FilterMode } from '../grid-base.component'; import { IgxGridNavigationService } from '../grid-navigation.service'; import { IgxGridAPIService } from './grid-api.service'; import { ISortingExpression } from '../../data-operations/sorting-expression.interface'; @@ -16,7 +16,6 @@ import { IDisplayDensityOptions, DisplayDensityToken } from '../../core/displayD import { IGroupByExpandState } from '../../data-operations/groupby-expand-state.interface'; import { IBaseChipEventArgs, IChipClickEventArgs, IChipKeyDownEventArgs } from '../../chips/chip.component'; import { IChipsAreaReorderEventArgs } from '../../chips/chips-area.component'; -import { IgxSelectionAPIService } from '../../core/selection'; import { TransactionService, Transaction, State } from '../../services/transaction/transaction'; import { DOCUMENT } from '@angular/common'; import { IgxColumnComponent } from '../column.component'; @@ -33,9 +32,6 @@ import { IgxGridMRLNavigationService } from '../grid-mrl-navigation.service'; let NEXT_ID = 0; -export interface IGridFocusChangeEventArgs extends IFocusChangeEventArgs { - groupRow: IgxGridGroupByRowComponent; -} export interface IGroupingDoneEventArgs extends IBaseEventArgs { expressions: Array | ISortingExpression; groupedColumns: Array | IgxColumnComponent; @@ -169,10 +165,6 @@ export class IgxGridComponent extends IgxGridBaseComponent implements IGridDataB */ set filteredData(value) { this._filteredData = value; - - if (this.rowSelectable) { - this.updateHeaderCheckboxStatusOnFilter(this._filteredData); - } } /** @@ -226,7 +218,6 @@ export class IgxGridComponent extends IgxGridBaseComponent implements IGridDataB crudService: IgxGridCRUDService, public colResizingService: IgxColumnResizingService, gridAPI: GridBaseAPIService, - selection: IgxSelectionAPIService, @Inject(IgxGridTransaction) _transactions: TransactionService, elementRef: ElementRef, zone: NgZone, @@ -241,7 +232,7 @@ export class IgxGridComponent extends IgxGridBaseComponent implements IGridDataB summaryService: IgxGridSummaryService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { super(selectionService, - crudService, gridAPI, selection, _transactions, elementRef, zone, document, cdr, resolver, differs, viewRef, navigation, + crudService, gridAPI, _transactions, elementRef, zone, document, cdr, resolver, differs, viewRef, navigation, filteringService, overlayService, summaryService, _displayDensityOptions); this._gridAPI = gridAPI; } diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts index 8ef1caa1eaa..759f841593c 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid.groupby.spec.ts @@ -8,7 +8,8 @@ import { IgxColumnComponent } from '../column.component'; import { IgxGridComponent } from './grid.component'; import { IgxGroupAreaDropDirective } from './grid.directives'; import { IgxColumnMovingDragDirective } from '../grid.common'; -import { IgxGridModule } from './index'; +import { IgxGridGroupByRowComponent } from './groupby-row.component'; +import { IgxGridModule, IgxGridCellComponent, GridSelectionMode } from './index'; import { IgxGridRowComponent } from './grid-row.component'; import { IgxChipComponent, IChipClickEventArgs } from '../../chips/chip.component'; import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec'; @@ -16,7 +17,7 @@ import { DefaultSortingStrategy } from '../../data-operations/sorting-strategy'; import { configureTestSuite } from '../../test-utils/configure-suite'; import { DataParent } from '../../test-utils/sample-test-data.spec'; import { MultiColumnHeadersWithGroupingComponent } from '../../test-utils/grid-samples.spec'; -import { resizeObserverIgnoreError } from '../../test-utils/helper-utils.spec'; +import { resizeObserverIgnoreError, HelperUtils } from '../../test-utils/helper-utils.spec'; describe('IgxGrid - GroupBy #grid', () => { configureTestSuite(); @@ -970,7 +971,7 @@ describe('IgxGrid - GroupBy #grid', () => { tick(); grid.columnWidth = '200px'; tick(); - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; tick(); fix.detectChanges(); @@ -983,12 +984,10 @@ describe('IgxGrid - GroupBy #grid', () => { const grRows = grid.groupsRowList.toArray(); const dataRows = grid.dataRowList.toArray(); for (const grRow of grRows) { - const checkBoxElement = grRow.element.nativeElement.querySelector('div.igx-grid__cbx-selection'); - expect(checkBoxElement).toBeNull(); + expect(HelperUtils.getRowCheckboxDiv(grRow.element.nativeElement)).toBeNull(); } for (const dRow of dataRows) { - const checkBoxElement = dRow.element.nativeElement.querySelector('div.igx-grid__cbx-selection'); - expect(checkBoxElement).toBeDefined(); + expect(HelperUtils.getRowCheckboxDiv(dRow.element.nativeElement)).toBeDefined(); } })); @@ -1000,50 +999,38 @@ describe('IgxGrid - GroupBy #grid', () => { tick(); grid.columnWidth = '200px'; tick(); - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; tick(); fix.detectChanges(); grid.groupBy({ fieldName: 'ProductName', dir: SortingDirection.Desc, ignoreCase: false }); - + tick(); fix.detectChanges(); grid.selectAllRows(); - tick(); - fix.detectChanges(); - let selRows = grid.selectedRows(); - tick(); - expect(selRows.length).toEqual(8); - + expect(grid.selectedRows().length).toEqual(8); let rows = fix.debugElement.queryAll(By.css('.igx-grid__tr--selected')); for (const r of rows) { expect(r.componentInstance instanceof IgxGridRowComponent).toBe(true); } grid.deselectAllRows(); - tick(); fix.detectChanges(); - selRows = grid.selectedRows(); - expect(selRows.length).toEqual(0); + expect(grid.selectedRows().length).toEqual(0); - const headerRow: HTMLElement = fix.nativeElement.querySelector('.igx-grid__thead'); - const headerCheckboxElement: Element = headerRow.querySelector('.igx-checkbox__input'); - headerCheckboxElement.dispatchEvent(new Event('click')); - tick(); + HelperUtils.clickHeaderRowCheckbox(fix); fix.detectChanges(); - selRows = grid.selectedRows(); - expect(selRows.length).toEqual(8); + expect(grid.selectedRows().length).toEqual(8); rows = fix.debugElement.queryAll(By.css('.igx-grid__tr--selected')); for (const r of rows) { expect(r.componentInstance instanceof IgxGridRowComponent).toBe(true); } - })); // GroupBy + Resizing @@ -1583,7 +1570,7 @@ describe('IgxGrid - GroupBy #grid', () => { it('should allow row selection after grouping, scrolling down to a new virtual frame and attempting to select a row.', (done) => { const fix = TestBed.createComponent(DefaultGridComponent); const grid = fix.componentInstance.instance; - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; fix.componentInstance.height = '200px'; fix.detectChanges(); @@ -1602,12 +1589,11 @@ describe('IgxGrid - GroupBy #grid', () => { setTimeout(() => { const rows = grid.dataRowList.toArray(); expect(rows.length).toEqual(1); - const checkBoxElement = rows[0].element.nativeElement.querySelector('.igx-checkbox__input'); - checkBoxElement.dispatchEvent(new Event('click')); - setTimeout(() => { + HelperUtils.clickRowCheckbox(rows[0].element); + setTimeout(() => { grid.cdr.detectChanges(); expect(grid.selectedRows().length).toEqual(1); - expect(rows[0].element.nativeElement.className).toEqual('igx-grid__tr igx-grid__tr--odd igx-grid__tr--selected'); + HelperUtils.verifyRowSelected(rows[0]); done(); }, 100); }, 100); diff --git a/projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.ts b/projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.ts index 05730d7428b..e8bc05e1d4d 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/groupby-row.component.ts @@ -210,10 +210,6 @@ export class IgxGridGroupByRowComponent { } return; } - // TODO: to be deleted when onFocusChange event is removed #4054 - const args = { cell: this, groupRow: null, event: event, cancel: false }; - this.grid._onFocusChange.emit(args); - if (args.cancel) { return; } const selection = this.gridSelection; selection.keyboardState.shift = event.shiftKey && !(key === 'tab'); diff --git a/projects/igniteui-angular/src/lib/grids/grid/row-drag.directive.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/row-drag.directive.spec.ts index 8dbfc0868d9..1d8f0467ee0 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/row-drag.directive.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/row-drag.directive.spec.ts @@ -14,7 +14,7 @@ import { IgxGridComponent } from './grid.component'; import { IgxColumnComponent } from '../column.component'; import { IgxGridRowComponent } from './grid-row.component'; import { IgxRowDragDirective } from '../row-drag.directive'; -import { IRowDragStartEventArgs, IgxGridBaseComponent, IRowDragEndEventArgs } from '../grid-base.component'; +import { IRowDragStartEventArgs, IgxGridBaseComponent, IRowDragEndEventArgs, GridSelectionMode } from '../grid-base.component'; import { IgxDropDirective } from '../../directives/drag-drop/drag-drop.directive'; import { SortingDirection } from '../../data-operations/sorting-expression.interface'; import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; @@ -216,7 +216,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { it('should align horizontal scrollbar with first column when column pinning is disabled', fakeAsync(() => { // has no draggable and selectable rows grid.width = '400px'; - grid.rowSelectable = false; + grid.rowSelection = GridSelectionMode.none; grid.rowDraggable = false; tick(); fixture.detectChanges(); @@ -227,7 +227,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { expect(dragIndicatorElement).toBeNull(); // has draggable rows and has no selectable rows - grid.rowSelectable = false; + grid.rowSelection = GridSelectionMode.none; grid.rowDraggable = true; tick(); fixture.detectChanges(); @@ -240,7 +240,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { expect(dragIndicatorRect.right).toBe(horizontalScrollbarRect.left); // has draggable and selectable rows - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; grid.rowDraggable = true; fixture.detectChanges(); horizontalScrollbarElement = fixture.debugElement.query(By.css(CSS_CLASS_VIRTUAL_HSCROLLBAR)); @@ -265,7 +265,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { expect(horizontalScrollbarRect.left).not.toBe(0); // selectable rows enabled - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; fixture.detectChanges(); horizontalScrollbarElement = fixture.debugElement.query(By.css(CSS_CLASS_VIRTUAL_HSCROLLBAR)); horizontalScrollbarRect = horizontalScrollbarElement.nativeElement.getBoundingClientRect(); @@ -585,7 +585,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { verifyDragAndDropRowCellValues(1, 0); })); it('should be able to drag selected grid row', (async () => { - dragGrid.rowSelectable = true; + dragGrid.rowSelection = GridSelectionMode.multiple; fixture.detectChanges(); dragGrid.selectRows([2], false); fixture.detectChanges(); @@ -617,7 +617,7 @@ describe('IgxGrid - Row Drag Tests #grid', () => { expect(row.isSelected).toBeTruthy(); })); it('should not apply selection class to ghost element when dragging selected grid row', (async () => { - dragGrid.rowSelectable = true; + dragGrid.rowSelection = GridSelectionMode.multiple; fixture.detectChanges(); dragGrid.selectRows([2], false); fixture.detectChanges(); diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/child-grid-row.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/child-grid-row.component.ts index 07519232f88..02b10d24da9 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/child-grid-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/child-grid-row.component.ts @@ -11,7 +11,6 @@ import { SimpleChanges, ComponentFactoryResolver } from '@angular/core'; -import { IgxSelectionAPIService } from '../../core/selection'; import { GridBaseAPIService } from '.././api.service'; import { IgxRowIslandComponent } from './row-island.component'; import { IgxGridComponent } from '../grid'; @@ -135,7 +134,6 @@ private resolver; } constructor(public gridAPI: GridBaseAPIService, - private selectionAPI: IgxSelectionAPIService, public element: ElementRef, resolver: ComponentFactoryResolver, public cdr: ChangeDetectorRef) { diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-cell.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-cell.component.ts index 06c0dbdba4c..0f562e68c08 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-cell.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-cell.component.ts @@ -29,7 +29,7 @@ export class IgxHierarchicalGridCellComponent extends IgxGridCellComponent imple protected zone: NgZone, touchManager: HammerGesturesManager ) { - super(selectionService, crudService, gridAPI, selection, cdr, helement, zone, touchManager); + super(selectionService, crudService, gridAPI, cdr, helement, zone, touchManager); this.hSelection = selection; } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid-base.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid-base.component.ts index f1dd5a3da4d..52a3677698f 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid-base.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid-base.component.ts @@ -21,7 +21,6 @@ import { IgxColumnComponent, IgxColumnGroupComponent } from '../column.component import { IgxSummaryOperand } from '../summaries/grid-summary'; import { IgxHierarchicalTransactionService, IgxOverlayService } from '../../services/index'; import { DOCUMENT } from '@angular/common'; -import { IgxHierarchicalSelectionAPIService } from './selection'; import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service'; import { IgxGridSummaryService } from '../summaries/grid-summary.service'; import { IgxGridSelectionService, IgxGridCRUDService } from '../../core/grid-selection'; @@ -92,7 +91,6 @@ export abstract class IgxHierarchicalGridBaseComponent extends IgxGridBaseCompon public selectionService: IgxGridSelectionService, crudService: IgxGridCRUDService, gridAPI: GridBaseAPIService, - selection: IgxHierarchicalSelectionAPIService, @Inject(IgxGridTransaction) protected transactionFactory: any, elementRef: ElementRef, zone: NgZone, @@ -110,7 +108,6 @@ export abstract class IgxHierarchicalGridBaseComponent extends IgxGridBaseCompon selectionService, crudService, gridAPI, - selection, typeof transactionFactory === 'function' ? transactionFactory() : transactionFactory, elementRef, zone, 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 852154b34d3..c1d1855b109 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 @@ -14,14 +14,14 @@
unfold_less
-
- +
diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts index f87679ed0e3..34437f2087a 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.component.ts @@ -32,7 +32,6 @@ import { IgxFilteringService } from '../filtering/grid-filtering.service'; import { IDisplayDensityOptions, DisplayDensityToken, DisplayDensity } from '../../core/displayDensity'; import { IGridDataBindable, IgxColumnComponent, } from '../grid/index'; import { DOCUMENT } from '@angular/common'; -import { IgxHierarchicalSelectionAPIService } from './selection'; import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service'; import { IgxGridSummaryService } from '../summaries/grid-summary.service'; import { IgxHierarchicalGridBaseComponent } from './hierarchical-grid-base.component'; @@ -152,9 +151,7 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseCompone public set filteredData(value) { this._filteredData = value; - if (this.rowSelectable) { - this.updateHeaderCheckboxStatusOnFilter(this._filteredData); - } + } /** @@ -288,7 +285,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseCompone crudService: IgxGridCRUDService, public colResizingService: IgxColumnResizingService, gridAPI: GridBaseAPIService, - selection: IgxHierarchicalSelectionAPIService, @Inject(IgxGridTransaction) protected transactionFactory: any, elementRef: ElementRef, zone: NgZone, @@ -306,7 +302,6 @@ export class IgxHierarchicalGridComponent extends IgxHierarchicalGridBaseCompone selectionService, crudService, gridAPI, - selection, typeof transactionFactory === 'function' ? transactionFactory() : transactionFactory, elementRef, zone, 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 2edb7c11971..14865488a34 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 @@ -9,7 +9,7 @@ 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 } from '../grid'; +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'; @@ -394,25 +394,6 @@ describe('IgxHierarchicalGrid Integration #hGrid', () => { expect(fChildCell.selected).toBe(true); })); - it('should retain selected row when filtering', fakeAsync(() => { - hierarchicalGrid.rowSelectable = true; - 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(); @@ -429,7 +410,7 @@ describe('IgxHierarchicalGrid Integration #hGrid', () => { })); it('should apply classes to the header when filter row is visible', fakeAsync(/** filter showHideArrowButtons rAF */() => { - hierarchicalGrid.rowSelectable = true; + hierarchicalGrid.rowSelection = GridSelectionMode.multiple; fixture.detectChanges(); const headerExpander: HTMLElement = fixture.nativeElement.querySelector('.igx-grid__hierarchical-expander'); const headerCheckbox: HTMLElement = fixture.nativeElement.querySelector('.igx-grid__cbx-selection'); @@ -486,7 +467,7 @@ describe('IgxHierarchicalGrid Integration #hGrid', () => { })); it('should size summaries with row selectors for parent and children grids correctly.', fakeAsync(/** row toggle rAF */() => { - hierarchicalGrid.rowSelectable = true; + hierarchicalGrid.rowSelection = GridSelectionMode.multiple; hierarchicalGrid.dataRowList.toArray()[0].nativeElement.children[0].click(); fixture.detectChanges(); @@ -775,7 +756,7 @@ describe('IgxHierarchicalGrid Integration #hGrid', () => { })); it('no rows, headers, paging or rowSelectors should be displayed when hideAll columns', fakeAsync(() => { - hierarchicalGrid.rowSelectable = true; + hierarchicalGrid.rowSelection = GridSelectionMode.multiple; hierarchicalGrid.rowDraggable = true; hierarchicalGrid.paging = true; tick(30); 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..e7e186640bb --- /dev/null +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.selection.spec.ts @@ -0,0 +1,146 @@ +import { configureTestSuite } from '../../test-utils/configure-suite'; +import { async, TestBed, fakeAsync } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { IgxHierarchicalGridModule } from './index'; +import { IgxHierarchicalGridComponent } from './hierarchical-grid.component'; +import { wait } from '../../test-utils/ui-interactions.spec'; +import { IgxHierarchicalRowComponent } from './hierarchical-row.component'; +import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; +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 #hGrid', () => { + 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 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 have checkboxes on each row', () => { + HelperUtils.verifyHeaderRowHasCheckbox(fix); + HelperUtils.verifyHeaderAndRowCheckBoxesAlignment(fix, hierarchicalGrid); + + for (const row of hierarchicalGrid.rowList.toArray()) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + } + }); + + it('should retain selected row when filtering', () => { + const firstRow = hierarchicalGrid.getRowByIndex(0); + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + hierarchicalGrid.filter('ID', '1', IgxStringFilteringOperand.instance().condition('doesNotContain'), true); + fix.detectChanges(); + + HelperUtils.verifyRowSelected( hierarchicalGrid.getRowByIndex(0)); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + }); + + it('should have correct header checkbox state when selecting rows', () => { + const firstRow = hierarchicalGrid.getRowByIndex(0); + const secondRow = hierarchicalGrid.getRowByIndex(1); + HelperUtils.verifyHeaderRowCheckboxState(fix); + + // Select all rows + hierarchicalGrid.rowList.toArray().forEach(row => { + HelperUtils.clickRowCheckbox(row); + fix.detectChanges(); + HelperUtils.verifyRowSelected(row); + }); + + HelperUtils.verifyHeaderRowCheckboxState(fix, true); + expect(hierarchicalGrid.selectedRows()).toEqual(['0', '1', '2', '3', '4']); + + // Unselect a row + HelperUtils.clickRowCheckbox(firstRow); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(firstRow, false); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(hierarchicalGrid.selectedRows()).toEqual(['1', '2', '3', '4']); + + // Click on a row + secondRow.nativeElement.dispatchEvent(new MouseEvent('click')); + fix.detectChanges(); + + HelperUtils.verifyRowSelected(secondRow); + HelperUtils.verifyHeaderRowCheckboxState(fix, false, true); + expect(hierarchicalGrid.selectedRows()).toEqual(['1']); + }); + }); +}); diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts index 5083851ae48..61b8f844242 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-grid.spec.ts @@ -1,7 +1,7 @@ import { configureTestSuite } from '../../test-utils/configure-suite'; import { async, TestBed, fakeAsync, tick, ComponentFixture } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxHierarchicalGridModule } from './index'; +import { IgxHierarchicalGridModule, GridSelectionMode } from './index'; import { ChangeDetectorRef, Component, ViewChild, AfterViewInit } from '@angular/core'; import { IgxHierarchicalGridComponent } from './hierarchical-grid.component'; import { wait, UIInteractions } from '../../test-utils/ui-interactions.spec'; @@ -503,14 +503,14 @@ describe('IgxHierarchicalGrid Row Islands #hGrid', () => { UIInteractions.clickElement(row.expander); fixture.detectChanges(); const ri1 = fixture.componentInstance.rowIsland1; - ri1.rowSelectable = true; + ri1.rowSelection = GridSelectionMode.multiple; fixture.detectChanges(); await wait(); // check rendered grid let childGrids = hierarchicalGrid.hgridAPI.getChildGrids(false); - expect(childGrids[0].rowSelectable).toBe(true); - expect(childGrids[1].rowSelectable).toBe(false); + expect(childGrids[0].rowSelection).toBe( GridSelectionMode.multiple); + expect(childGrids[1].rowSelection).toBe(GridSelectionMode.none); // expand new row and check newly generated grid const row2 = hierarchicalGrid.getRowByIndex(3) as IgxHierarchicalRowComponent; @@ -518,10 +518,10 @@ describe('IgxHierarchicalGrid Row Islands #hGrid', () => { fixture.detectChanges(); await wait(); childGrids = hierarchicalGrid.hgridAPI.getChildGrids(false); - expect(childGrids[0].rowSelectable).toBe(true); - expect(childGrids[1].rowSelectable).toBe(true); - expect(childGrids[2].rowSelectable).toBe(false); - expect(childGrids[3].rowSelectable).toBe(false); + expect(childGrids[0].rowSelection).toBe( GridSelectionMode.multiple); + expect(childGrids[1].rowSelection).toBe( GridSelectionMode.multiple); + expect(childGrids[2].rowSelection).toBe(GridSelectionMode.none); + expect(childGrids[3].rowSelection).toBe(GridSelectionMode.none); }); it('should apply column settings applied to the row island to all related child grids.', async() => { /** height/width setter rAF + row toggle rAF */ diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html index 0b8de977d82..58ed0210a26 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.html @@ -1,16 +1,16 @@ -
+
expand_more expand_less
-
+
- -
- -
+ +
+ +
@@ -30,7 +30,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate"> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection"> @@ -50,7 +51,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate"> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection"> diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.ts index 99558027563..6c2fa439748 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/hierarchical-row.component.ts @@ -11,7 +11,6 @@ import { } from '@angular/core'; import { IgxHierarchicalGridComponent } from './hierarchical-grid.component'; import { IgxRowComponent } from '../row.component'; -import { IgxHierarchicalSelectionAPIService } from './selection'; import { GridBaseAPIService } from '.././api.service'; import { IgxHierarchicalGridCellComponent } from './hierarchical-cell.component'; import { IgxGridCRUDService, IgxGridSelectionService } from '../../core/grid-selection'; @@ -73,7 +72,7 @@ export class IgxHierarchicalRowComponent extends IgxRowComponent, public crudService: IgxGridCRUDService, public selectionService: IgxGridSelectionService, - private hselection: IgxHierarchicalSelectionAPIService, public element: ElementRef, public cdr: ChangeDetectorRef) { - super(gridAPI, crudService, selectionService, hselection, element, cdr); + super(gridAPI, crudService, selectionService, element, cdr); } } diff --git a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts index 6e7506931d7..6e85166af68 100644 --- a/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts +++ b/projects/igniteui-angular/src/lib/grids/hierarchical-grid/row-island.component.ts @@ -31,7 +31,6 @@ import { IDisplayDensityOptions, DisplayDensityToken } from '../../core/displayD import { TransactionService, Transaction, State } from '../../services'; import { IgxGridSummaryService } from '../summaries/grid-summary.service'; import { IgxHierarchicalGridBaseComponent } from './hierarchical-grid-base.component'; -import { IgxHierarchicalSelectionAPIService } from './selection'; import { IgxHierarchicalGridNavigationService } from './hierarchical-grid-navigation.service'; import { IgxGridSelectionService, IgxGridCRUDService } from '../../core/grid-selection'; @@ -192,7 +191,6 @@ export class IgxRowIslandComponent extends IgxHierarchicalGridBaseComponent public selectionService: IgxGridSelectionService, crudService: IgxGridCRUDService, gridAPI: GridBaseAPIService, - selection: IgxHierarchicalSelectionAPIService, @Inject(IgxGridTransaction) protected transactionFactory: any, elementRef: ElementRef, zone: NgZone, @@ -211,7 +209,6 @@ export class IgxRowIslandComponent extends IgxHierarchicalGridBaseComponent selectionService, crudService, gridAPI, - selection, typeof transactionFactory === 'function' ? transactionFactory() : transactionFactory, elementRef, zone, diff --git a/projects/igniteui-angular/src/lib/grids/row.component.ts b/projects/igniteui-angular/src/lib/grids/row.component.ts index 6475e53d17a..5cb1f2164d0 100644 --- a/projects/igniteui-angular/src/lib/grids/row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/row.component.ts @@ -6,13 +6,13 @@ import { ElementRef, forwardRef, HostBinding, + HostListener, Input, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { IgxCheckboxComponent } from '../checkbox/checkbox.component'; -import { IgxSelectionAPIService } from '../core/selection'; import { IgxGridForOfDirective } from '../directives/for-of/for_of.directive'; import { GridBaseAPIService } from './api.service'; import { IgxGridCellComponent } from './cell.component'; @@ -20,6 +20,7 @@ import { IgxColumnComponent } from './column.component'; import { TransactionType, State } from '../services'; import { IgxGridBaseComponent, IGridDataBindable } from './grid-base.component'; import { IgxGridSelectionService, IgxGridCRUDService, IgxRow } from '../core/grid-selection'; +import { DeprecateProperty } from '../core/deprecateDecorators'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -108,6 +109,21 @@ export class IgxRowComponent return this.resolveClasses(); } + /** + * @hidden + */ + @Input() + @HostBinding('attr.aria-selected') + get selected(): boolean { + return this.selectionService.isRowSelected(this.rowID); + } + + set selected(value: boolean) { + value ? this.selectionService.selectRowsWithNoEvent([this.rowID]) : + this.selectionService.deselectRowsWithNoEvent([this.rowID]); + this.grid.cdr.markForCheck(); + } + /** * @hidden */ @@ -129,13 +145,6 @@ export class IgxRowComponent return this.grid.unpinnedColumns; } - /** - * @hidden - */ - public get rowSelectable(): boolean { - return this.grid.rowSelectable; - } - /** * @hidden */ @@ -143,12 +152,6 @@ export class IgxRowComponent return this.grid.showRowCheckboxes; } - /** - * @hidden - */ - @HostBinding('attr.aria-selected') - public isSelected: boolean; - /** @hidden */ public get dirty(): boolean { const row: State = this.grid.transactions.getState(this.rowID); @@ -159,6 +162,11 @@ export class IgxRowComponent return false; } + @DeprecateProperty('isSelected property is deprecated. Use selected property instead.') + public get isSelected() { + return this.selectionService.isRowSelected(this.rowID); + } + /** * @hidden */ @@ -255,27 +263,39 @@ export class IgxRowComponent */ public defaultCssClass = 'igx-grid__tr'; - /** - * @hidden - */ - protected _rowSelection = false; constructor(public gridAPI: GridBaseAPIService, public crudService: IgxGridCRUDService, public selectionService: IgxGridSelectionService, - private selection: IgxSelectionAPIService, public element: ElementRef, public cdr: ChangeDetectorRef) { } + /** + * @hidden + * @internal + */ + @HostListener('click', ['$event']) + public onClick(event: MouseEvent) { + if (this.grid.rowSelection === 'none' || this.deleted) { return; } + if (event.shiftKey && this.grid.rowSelection === 'multiple') { + this.selectionService.selectMultipleRows(this.rowID, this.rowData, event); + return; + } + this.selectionService.selectRowById(this.rowID, !event.ctrlKey, event); + } + /** * @hidden */ - public onCheckboxClick(event) { - const newSelection = (event.checked) ? - this.selection.add_item(this.gridID, this.rowID) : - this.selection.delete_item(this.gridID, this.rowID); - this.grid.triggerRowSelectionChange(newSelection, this, event); + public onRowSelectorClick(event) { + event.stopPropagation(); + if (event.shiftKey && this.grid.rowSelection === 'multiple') { + this.selectionService.selectMultipleRows(this.rowID, this.rowData, event); + return; + } + this.selected ? this.selectionService.deselectRow(this.rowID, event) : + this.selectionService.selectRowById(this.rowID, false, event); } /** @@ -316,21 +336,15 @@ export class IgxRowComponent */ get rowCheckboxAriaLabel() { return this.grid.primaryKey ? - this.isSelected ? 'Deselect row with key ' + this.rowID : 'Select row with key ' + this.rowID : - this.isSelected ? 'Deselect row' : 'Select row'; + this.selected ? 'Deselect row with key ' + this.rowID : 'Select row with key ' + this.rowID : + this.selected ? 'Deselect row' : 'Select row'; } /** * @hidden */ public ngDoCheck() { - this.isSelected = this.rowSelectable ? - this.grid.allRowsSelected ? true : this.selection.is_item_selected(this.gridID, this.rowID) : - this.selection.is_item_selected(this.gridID, this.rowID); this.cdr.markForCheck(); - if (this.checkboxElement) { - this.checkboxElement.checked = this.isSelected; - } } /** @@ -338,7 +352,7 @@ export class IgxRowComponent */ protected resolveClasses(): string { const indexClass = this.index % 2 ? this.grid.evenRowCSS : this.grid.oddRowCSS; - const selectedClass = this.isSelected ? 'igx-grid__tr--selected' : ''; + const selectedClass = this.selected ? 'igx-grid__tr--selected' : ''; const editClass = this.inEditMode ? 'igx-grid__tr--edit' : ''; const dirtyClass = this.dirty ? 'igx-grid__tr--edited' : ''; const deletedClass = this.deleted ? 'igx-grid__tr--deleted' : ''; diff --git a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts index e5d48639d32..5143258b3cd 100644 --- a/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts +++ b/projects/igniteui-angular/src/lib/grids/summaries/summary-cell.component.ts @@ -2,7 +2,7 @@ import { Component, Input, HostBinding, HostListener, ChangeDetectionStrategy, E import { IgxSummaryResult } from './grid-summary'; import { IgxColumnComponent } from '../column.component'; import { DataType } from '../../data-operations/data-util'; -import { IgxGridSelectionService, ISelectionNode } from '../../core/grid-selection'; +import { ISelectionNode } from '../../core/grid-selection'; import { SUPPORTED_KEYS } from '../../core/utils'; @Component({ @@ -28,7 +28,7 @@ export class IgxSummaryCellComponent { @Input() public density; - constructor(private element: ElementRef, private selectionService: IgxGridSelectionService) { + constructor(private element: ElementRef) { } @Input() @@ -89,7 +89,7 @@ export class IgxSummaryCellComponent { if (!this.isKeySupportedInCell(key, ctrl)) { return; } - this.selectionService.keyboardState.shift = shift && !(key === 'tab'); + this.grid.selectionService.keyboardState.shift = shift && !(key === 'tab'); const row = this.getRowElementByIndex(this.rowIndex); switch (key) { case 'tab': diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.ts index 7cc36bb10d1..152f48f76c9 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-cell.component.ts @@ -2,7 +2,6 @@ import { Component, ChangeDetectorRef, ElementRef, ViewChild, Inject, ChangeDete import { IgxGridCellComponent } from '../cell.component'; import { IgxTreeGridAPIService } from './tree-grid-api.service'; import { GridBaseAPIService } from '../api.service'; -import { IgxSelectionAPIService } from '../../core/selection'; import { getNodeSizeViaRange } from '../../core/utils'; import { DOCUMENT } from '@angular/common'; import { IgxGridBaseComponent, IGridDataBindable } from '../grid'; @@ -22,13 +21,12 @@ export class IgxTreeGridCellComponent extends IgxGridCellComponent implements On selectionService: IgxGridSelectionService, crudService: IgxGridCRUDService, gridAPI: GridBaseAPIService, - selection: IgxSelectionAPIService, cdr: ChangeDetectorRef, element: ElementRef, protected zone: NgZone, touchManager: HammerGesturesManager, @Inject(DOCUMENT) public document) { - super(selectionService, crudService, gridAPI, selection, cdr, element, zone, touchManager); + super(selectionService, crudService, gridAPI, cdr, element, zone, touchManager); this.treeGridAPI = gridAPI; } diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts index eb158fcca98..2948d362c1f 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-api.service.ts @@ -255,7 +255,7 @@ export class IgxTreeGridAPIService extends GridBaseAPIService { fix.detectChanges(); }); - it('should update current page when \'collapseAll\' ', fakeAsync (() => { + it('should update current page when \'collapseAll\' ', fakeAsync(() => { // Test prerequisites treeGrid.paging = true; treeGrid.perPage = 4; @@ -859,7 +859,7 @@ describe('IgxTreeGrid - Expanding / Collapsing #tGrid', () => { expect(treeGrid.totalPages).toBe(2); })); - it('Should update the paginator when a row of any level is collapsed', fakeAsync(() => { + it('Should update the paginator when a row of any level is collapsed', fakeAsync(() => { // Test prerequisites treeGrid.paging = true; treeGrid.perPage = 5; @@ -989,6 +989,37 @@ describe('IgxTreeGrid - Expanding / Collapsing #tGrid', () => { TreeGridFunctions.verifyTreeRowIndicator(row, false, false); expect(rows.length).toBe(3); }); + + it('check row selection when expand a row', async () => { + treeGrid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + + treeGrid.selectAllRows(); + fix.detectChanges(); + + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + expect(treeGrid.selectedRows()).toEqual([1, 6, 10]); + + let rows = TreeGridFunctions.getAllRows(fix); + const row = rows[0]; + expect(rows.length).toBe(3); + + const indicatorDiv = TreeGridFunctions.getExpansionIndicatorDiv(row); + indicatorDiv.triggerEventHandler('click', new Event('click')); + await wait(1050); + fix.detectChanges(); + await wait(50); + + rows = TreeGridFunctions.getAllRows(fix); + expect(rows.length).toBe(5); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 0, true); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 1, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 2, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, true); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 4, true); + expect(treeGrid.selectedRows()).toEqual([1, 6, 10]); + }); }); describe('ChildDataKey', () => { 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 e9d588bfe96..83e5afbe5ba 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 @@ -19,7 +19,7 @@ import { IgxToggleModule } from '../../directives/toggle/toggle.directive'; import { IgxNumberFilteringOperand } from '../../data-operations/filtering-condition'; import { IgxHierarchicalTransactionService } from '../../services/transaction/igx-hierarchical-transaction'; import { IgxGridTransaction } from '../grid-base.component'; -import { IgxGridCellComponent } from '../grid'; +import { IgxGridCellComponent, GridSelectionMode } from '../grid'; const CSS_CLASS_BANNER = 'igx-banner'; const CSS_CLASS_ROW_EDITED = 'igx-grid__tr--edited'; @@ -1126,62 +1126,6 @@ describe('IgxTreeGrid - Integration #tGrid', () => { 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.rowSelectable = true; - 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.rowSelectable = true; - 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-keyBoardNav.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-keyBoardNav.spec.ts index 95b9bf49c49..3e937039a6a 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-keyBoardNav.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-keyBoardNav.spec.ts @@ -1,6 +1,6 @@ import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxTreeGridModule, IgxTreeGridComponent } from './index'; +import { IgxTreeGridModule, IgxTreeGridComponent, GridSelectionMode } from './index'; import { IgxTreeGridWithNoScrollsComponent, IgxTreeGridWithScrollsComponent } from '../../test-utils/tree-grid-components.spec'; import { TreeGridFunctions } from '../../test-utils/tree-grid-functions.spec'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; @@ -346,7 +346,7 @@ describe('IgxTreeGrid - Key Board Navigation #tGrid', () => { }); it('should select row when press Space key on a cell', async () => { - treeGrid.rowSelectable = true; + treeGrid.rowSelection = GridSelectionMode.multiple; fix.detectChanges(); // Click Space on a treeGrid cell diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-multi-cell-selection.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-multi-cell-selection.spec.ts index 190bc90b1fd..49ba836f594 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-multi-cell-selection.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-multi-cell-selection.spec.ts @@ -8,7 +8,7 @@ import { IgxTreeGridFKeySelectionWithTransactionComponent } from '../../test-utils/tree-grid-components.spec'; import { IgxStringFilteringOperand } from '../../data-operations/filtering-condition'; -import { IgxTreeGridModule } from '.'; +import { IgxTreeGridModule, GridSelectionMode } from '.'; import { HelperUtils, setupGridScrollDetection } from '../../test-utils/helper-utils.spec'; import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec'; @@ -52,6 +52,69 @@ describe('IgxTreeGrid - Multi Cell selection #tGrid', () => { verifySelectingRangeWithMouseDrag(fix, treeGrid, detect); }); + it('Should not be possible to select a range when change cellSelection to none', () => { + const rangeChangeSpy = spyOn(treeGrid.onRangeSelection, 'emit').and.callThrough(); + const startCell = treeGrid.getCellByColumn(0, 'ID'); + const endCell = treeGrid.getCellByColumn(2, 'ID'); + + expect(treeGrid.cellSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + HelperUtils.verifyCellsRegionSelected(treeGrid, 0, 2, 0, 0); + HelperUtils.verifySelectedRange(treeGrid, 0, 2, 0, 0); + + treeGrid.cellSelection = GridSelectionMode.none; + fix.detectChanges(); + + HelperUtils.verifyCellsRegionSelected(treeGrid, 0, 2, 0, 0, false); + expect(treeGrid.getSelectedData()).toEqual([]); + expect(treeGrid.getSelectedRanges()).toEqual([]); + + // Try to select a range + HelperUtils.selectCellsRangeNoWait(fix, endCell, startCell); + detect(); + HelperUtils.verifyCellsRegionSelected(treeGrid, 0, 2, 0, 0, false); + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + expect(treeGrid.selectedCells.length).toBe(0); + expect(treeGrid.getSelectedData()).toEqual([]); + expect(treeGrid.getSelectedRanges()).toEqual([]); + }); + + it('Should not be possible to select a range when change cellSelection to single', () => { + const rangeChangeSpy = spyOn(treeGrid.onRangeSelection, 'emit').and.callThrough(); + const startCell = treeGrid.getCellByColumn(0, 'ID'); + const middleCell = treeGrid.getCellByColumn(1, 'ID'); + const endCell = treeGrid.getCellByColumn(2, 'ID'); + + expect(treeGrid.cellSelection).toEqual(GridSelectionMode.multiple); + HelperUtils.selectCellsRangeNoWait(fix, startCell, endCell); + detect(); + + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + HelperUtils.verifyCellsRegionSelected(treeGrid, 0, 2, 0, 0); + HelperUtils.verifySelectedRange(treeGrid, 0, 2, 0, 0); + + treeGrid.cellSelection = GridSelectionMode.single; + fix.detectChanges(); + + expect(treeGrid.cellSelection).toEqual(GridSelectionMode.single); + HelperUtils.verifyCellsRegionSelected(treeGrid, 0, 2, 0, 0, false); + expect(treeGrid.getSelectedData()).toEqual([]); + expect(treeGrid.getSelectedRanges()).toEqual([]); + + // Try to select a range + HelperUtils.selectCellsRangeNoWait(fix, endCell, startCell); + detect(); + HelperUtils.verifyCellSelected(startCell, false); + HelperUtils.verifyCellSelected(middleCell, false); + HelperUtils.verifyCellSelected(endCell); + expect(rangeChangeSpy).toHaveBeenCalledTimes(1); + expect(treeGrid.selectedCells.length).toBe(1); + expect(treeGrid.getSelectedData()).toEqual([{ ID: 957 }]); + }); + it('Should not change selection when expand collapse row with keyboard', (async () => { const expectedData1 = [ { ID: 19 }, @@ -857,7 +920,6 @@ describe('IgxTreeGrid - Multi Cell selection #tGrid', () => { }); }); - function verifySelectingRegion(fix, treeGrid) { const selectionChangeSpy = spyOn(treeGrid.onRangeSelection, 'emit').and.callThrough(); const range1 = { rowStart: 0, rowEnd: 6, columnStart: 'ID', columnEnd: 'Age' }; diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html index 59ee7fb461b..0cb896f6ac5 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.html @@ -1,11 +1,11 @@ -
+
- -
- + +
+
@@ -27,7 +27,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate" #treeCell> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection" #treeCell> @@ -51,7 +52,8 @@ [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" [isLoading]="isLoading" - [cellTemplate]="col.bodyTemplate" #treeCell> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection" #treeCell> @@ -74,7 +76,8 @@ [width]="col.getCellWidth()" [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" - [cellTemplate]="col.bodyTemplate" #treeCell> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection" #treeCell> @@ -97,7 +100,8 @@ [visibleColumnIndex]="col.visibleIndex" [value]="rowData[col.field]" [isLoading]="isLoading" - [cellTemplate]="col.bodyTemplate" #treeCell> + [cellTemplate]="col.bodyTemplate" + [cellSelectionMode]="grid.cellSelection" #treeCell> diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts index cb758364548..9dd2ff25164 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid-row.component.ts @@ -4,7 +4,6 @@ import { IgxRowComponent } from '../row.component'; import { ITreeGridRecord } from './tree-grid.interfaces'; import { IgxTreeGridAPIService } from './tree-grid-api.service'; import { GridBaseAPIService } from '../api.service'; -import { IgxSelectionAPIService } from '../../core/selection'; import { IgxGridSelectionService, IgxGridCRUDService } from '../../core/grid-selection'; @Component({ @@ -17,11 +16,10 @@ export class IgxTreeGridRowComponent extends IgxRowComponent, public crudService: IgxGridCRUDService, public selectionService: IgxGridSelectionService, - selection: IgxSelectionAPIService, public element: ElementRef, public cdr: ChangeDetectorRef) { // D.P. constructor duplication due to es6 compilation, might be obsolete in the future - super(gridAPI, crudService, selectionService, selection, element, cdr); + super(gridAPI, crudService, selectionService, element, cdr); } private _treeRow: ITreeGridRecord; @@ -112,6 +110,6 @@ export class IgxTreeGridRowComponent extends IgxRowComponent { configureTestSuite(); @@ -29,7 +31,9 @@ describe('IgxTreeGrid - Selection #tGrid', () => { declarations: [ IgxTreeGridSimpleComponent, IgxTreeGridCellSelectionComponent, - IgxTreeGridSelectionRowEditingComponent + IgxTreeGridSelectionRowEditingComponent, + IgxTreeGridSelectionWithTransactionComponent, + IgxTreeGridRowEditingTransactionComponent ], imports: [IgxTreeGridModule, NoopAnimationsModule] }) @@ -43,7 +47,7 @@ describe('IgxTreeGrid - Selection #tGrid', () => { fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid; - treeGrid.rowSelectable = true; + treeGrid.rowSelection = GridSelectionMode.multiple; await wait(); fix.detectChanges(); }); @@ -57,7 +61,7 @@ describe('IgxTreeGrid - Selection #tGrid', () => { expect(checkBoxElement).not.toBeNull(); }); - treeGrid.rowSelectable = false; + treeGrid.rowSelection = GridSelectionMode.none; fix.detectChanges(); expect(rows.length).toBe(10); @@ -72,12 +76,13 @@ describe('IgxTreeGrid - Selection #tGrid', () => { fix.detectChanges(); TreeGridFunctions.verifyDataRowsSelection(fix, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], true); + expect(treeGrid.selectedRows().length).toEqual(10); TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); treeGrid.deselectAllRows(); fix.detectChanges(); - TreeGridFunctions.verifyDataRowsSelection(fix, [], true); + expect(treeGrid.selectedRows()).toEqual([]); TreeGridFunctions.verifyDataRowsSelection(fix, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], false); TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); }); @@ -97,7 +102,7 @@ describe('IgxTreeGrid - Selection #tGrid', () => { treeGrid.deleteRowById(treeGrid.selectedRows()[0]); fix.detectChanges(); - // When deleting the last selected row, header checkbox will be unchecked. + // When deleting the last selected row, header checkbox will be unchecked. TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); }); @@ -163,7 +168,7 @@ describe('IgxTreeGrid - Selection #tGrid', () => { 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); @@ -171,11 +176,41 @@ describe('IgxTreeGrid - Selection #tGrid', () => { 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); @@ -233,12 +268,12 @@ describe('IgxTreeGrid - Selection #tGrid', () => { describe('UI Row Selection', () => { // configureTestSuite(); - beforeEach(async() => { + beforeEach(async () => { fix = TestBed.createComponent(IgxTreeGridSimpleComponent); fix.detectChanges(); treeGrid = fix.componentInstance.treeGrid; - treeGrid.rowSelectable = true; + treeGrid.rowSelection = GridSelectionMode.multiple; await wait(); fix.detectChanges(); }); @@ -345,7 +380,6 @@ describe('IgxTreeGrid - Selection #tGrid', () => { })); it('should update header checkbox when reselecting all filtered-in rows', fakeAsync(() => { - pending('General Grid Issue #2793'); treeGrid.filter('Age', 30, IgxNumberFilteringOperand.instance().condition('lessThan')); tick(100); @@ -417,6 +451,229 @@ describe('IgxTreeGrid - Selection #tGrid', () => { })); }); + describe('Row Selection with transactions - Hierarchical DS', () => { + // configureTestSuite(); + beforeEach(fakeAsync(() => { + fix = TestBed.createComponent(IgxTreeGridSelectionWithTransactionComponent); + fix.detectChanges(); + + treeGrid = fix.componentInstance.treeGrid; + treeGrid.rowSelection = GridSelectionMode.multiple; + fix.detectChanges(); + })); + + it('should deselect row when delete its parent', () => { + pending('Related to the bug #5673'); + treeGrid.selectRows([treeGrid.getRowByIndex(3).rowID, treeGrid.getRowByIndex(5).rowID], true); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, true); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + treeGrid.deleteRow(147); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + expect(treeGrid.selectedRows()).toEqual([]); + + // try to select deleted row + TreeGridFunctions.clickRowSelectionCheckbox(fix, 0); + UIInteractions.simulateClickEvent(treeGrid.getRowByIndex(3).nativeElement); + TreeGridFunctions.clickRowSelectionCheckbox(fix, 5); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 0, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + expect(treeGrid.selectedRows()).toEqual([]); + + // undo transaction + treeGrid.transactions.undo(); + fix.detectChanges(); + + // select rows + TreeGridFunctions.clickRowSelectionCheckbox(fix, 0); + UIInteractions.simulateClickEvent(treeGrid.getRowByIndex(3).nativeElement); + TreeGridFunctions.clickRowSelectionCheckbox(fix, 5); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 0, true); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, true); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + expect(treeGrid.selectedRows()).toEqual([147, 317, 998]); + + // redo transaction + treeGrid.transactions.redo(); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 0, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + expect(treeGrid.selectedRows()).toEqual([]); + }); + + it('should have correct header checkbox when delete a row', () => { + treeGrid.selectAllRows(); + fix.detectChanges(); + + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.deleteRow(317); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + expect(treeGrid.selectedRows().includes(317)).toEqual(false); + expect(treeGrid.selectedRows().includes(711)).toEqual(false); + expect(treeGrid.selectedRows().includes(998)).toEqual(false); + + // undo transaction + treeGrid.transactions.undo(); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 4, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + + // redo transaction + treeGrid.transactions.redo(); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 3, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 4, false); + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 5, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + }); + + it('should have correct header checkbox when add a row', () => { + pending('Related to the bug #5673'); + treeGrid.selectAllRows(); + fix.detectChanges(); + + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + + treeGrid.addRow({ ID: 13, Name: 'Michael Cooper', Age: 33, OnPTO: false }, 317); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 6, false); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + expect(treeGrid.selectedRows().includes(13)).toEqual(false); + + // undo transaction + treeGrid.transactions.undo(); + fix.detectChanges(); + + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, true); + }); + + it('should have correct header checkbox when add a row and undo transaction', fakeAsync(() => { + pending('Related to the bug #5673'); + treeGrid.addRow({ ID: 13, Name: 'Michael Cooper', Age: 33, OnPTO: false }, 317); + tick(); + fix.detectChanges(); + + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, false); + TreeGridFunctions.clickRowSelectionCheckbox(fix, 6); + fix.detectChanges(); + + TreeGridFunctions.verifyTreeRowSelectionByIndex(fix, 6, true); + TreeGridFunctions.verifyHeaderCheckboxSelection(fix, null); + + // undo transaction + treeGrid.transactions.undo(); + tick(); + fix.detectChanges(); + + 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', () => { // configureTestSuite(); beforeEach(fakeAsync(/** height/width setter rAF */() => { @@ -608,7 +865,6 @@ describe('IgxTreeGrid - Selection #tGrid', () => { expect(treeGrid.selectedCells[0] instanceof IgxTreeGridCellComponent).toBe(true); expect(treeGrid.selectedCells[0].value).toBe(19); }); - }); describe('Cell/Row Selection With Row Editing', () => { @@ -624,7 +880,7 @@ describe('IgxTreeGrid - Selection #tGrid', () => { it('should display the banner correctly on row selection', fakeAsync(() => { const targetCell = treeGrid.getCellByColumn(1, 'Name'); - treeGrid.rowSelectable = true; + treeGrid.rowSelection = GridSelectionMode.multiple; treeGrid.rowEditable = true; // select the second row 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 859c600efcb..d8bc1db4251 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 @@ -21,11 +21,11 @@
-
- +
diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts index 321edc85ae2..1fdc5de5907 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.spec.ts @@ -1,6 +1,6 @@ import { async, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { IgxTreeGridModule } from './index'; +import { IgxTreeGridModule, GridSelectionMode } from './index'; import { IgxTreeGridComponent } from './tree-grid.component'; import { DisplayDensity } from '../../core/displayDensity'; import { configureTestSuite } from '../../test-utils/configure-suite'; @@ -160,7 +160,7 @@ describe('IgxTreeGrid Component Tests #tGrid', () => { })); it('should not render rows, paging and headers group when all cols are hidden', fakeAsync(() => { - grid.rowSelectable = true; + grid.rowSelection = GridSelectionMode.multiple; grid.rowDraggable = true; grid.showToolbar = true; tick(30); diff --git a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts index d97c46cf6ff..d75c9e7478d 100644 --- a/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts +++ b/projects/igniteui-angular/src/lib/grids/tree-grid/tree-grid.component.ts @@ -22,7 +22,6 @@ import { ViewChild, DoCheck } from '@angular/core'; -import { IgxSelectionAPIService } from '../../core/selection'; import { IgxTreeGridAPIService } from './tree-grid-api.service'; import { IgxGridBaseComponent, IgxGridTransaction, IGridDataBindable } from '../grid-base.component'; import { GridBaseAPIService } from '../api.service'; @@ -139,9 +138,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD set filteredData(value) { this._filteredData = value; - if (this.rowSelectable) { - this.updateHeaderCheckboxStatusOnFilter(this._filteredData); - } + } /** @@ -406,7 +403,6 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD crudService: IgxGridCRUDService, public colResizingService: IgxColumnResizingService, gridAPI: GridBaseAPIService, - selection: IgxSelectionAPIService, @Inject(IgxGridTransaction) protected _transactions: IgxHierarchicalTransactionService, elementRef: ElementRef, zone: NgZone, @@ -420,7 +416,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD @Inject(IgxOverlayService) protected overlayService: IgxOverlayService, summaryService: IgxGridSummaryService, @Optional() @Inject(DisplayDensityToken) protected _displayDensityOptions: IDisplayDensityOptions) { - super(selectionService, crudService, gridAPI, selection, + super(selectionService, crudService, gridAPI, _transactions, elementRef, zone, document, cdr, resolver, differs, viewRef, navigation, filteringService, overlayService, summaryService, _displayDensityOptions); this._gridAPI = gridAPI; @@ -464,9 +460,9 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD this.notifyChanges(); requestAnimationFrame(() => { - const cellID = this.selection.first_item(`${this.id}-cell`); + const cellID = this.selectionService.activeElement; if (cellID) { - const cell = this._gridAPI.get_cell_by_index(cellID.rowIndex, cellID.columnID); + const cell = this._gridAPI.get_cell_by_index(cellID.row, cellID.column); if (cell) { cell.nativeElement.focus(); } @@ -512,7 +508,7 @@ export class IgxTreeGridComponent extends IgxGridBaseComponent implements IGridD parentData[this.childDataKey] = children; } - + this.selectionService.clearHeaderCBState(); this._pipeTrigger++; } diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-base-components.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-base-components.spec.ts index 457783dd64c..548f02f7b08 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-base-components.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-base-components.spec.ts @@ -96,12 +96,10 @@ export class PagingComponent extends GridWithSizeComponent { } @Component({ - template: GridTemplateStrings.declareGrid(` [rowSelectable]="rowSelectable"`, - '', ColumnDefinitions.productBasic) + template: GridTemplateStrings.declareGrid(` rowSelection = "multiple"`, + '', ColumnDefinitions.productBasicNumberID) }) export class SelectionComponent extends BasicGridComponent { - public rowSelectable = true; - data = SampleTestData.generateBigValuesData(100); } diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts index f9cdd1de7fa..8e9e69d9a3e 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-functions.spec.ts @@ -55,6 +55,11 @@ export class GridFunctions { hScrollbar.scrollRight = newRight; } + public static setGridScrollTop(grid: IgxGridComponent, newTop: number) { + const vScrollbar = grid.verticalScrollContainer.getVerticalScroll(); + vScrollbar.scrollTop = newTop; + } + public static getCurrentCellFromGrid(grid, row, cell) { const gridRow = grid.rowList.toArray()[row]; const gridCell = gridRow.cells.toArray()[cell]; diff --git a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts index b5f23ea1d03..fbe2243a55c 100644 --- a/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/grid-samples.spec.ts @@ -135,44 +135,51 @@ export class FilteringComponent extends BasicGridComponent { } @Component({ - template: ` - - - - - ` + template: GridTemplateStrings.declareGrid( + ` #gridSelection3 [primaryKey]="'ID'" [width]="'800px'" [height]="'600px'" [autoGenerate]="true" [rowSelection]="'multiple'"`, + '', '') }) -export class SelectionAndPagingComponent extends BasicGridComponent { - data = SampleTestData.generateBigValuesData(100); +export class SelectionComponent extends BasicGridComponent { + data = SampleTestData.generateBigValuesData(20); +} - public ChangePage(val) { - switch (val) { - case -1: - this.grid.previousPage(); - break; - case 1: - this.grid.nextPage(); - break; - default: - this.grid.paginate(val); - break; - } - } +@Component({ + template: GridTemplateStrings.declareGrid( + ` [width]="width" [height]="height" [rowSelection]="'multiple'" [primaryKey]="'ProductID'"`, + '', ColumnDefinitions.productBasicNumberID) +}) +export class RowSelectionComponent extends BasicGridComponent { + data = SampleTestData.foodProductDataExtended(); + public width = '800px'; + public height = '600px'; } @Component({ template: GridTemplateStrings.declareGrid( - ` #gridSelection3 [primaryKey]="'ID'" [width]="'800px'" [height]="'600px'" [autoGenerate]="true" [rowSelectable]="true"`, - '', '') + ` [width]="width" [height]="height" [rowSelection]="'single'" [primaryKey]="'ProductID'"`, + '', ColumnDefinitions.productBasicNumberID) }) -export class SelectionComponent extends BasicGridComponent { - data = SampleTestData.generateBigValuesData(100); +export class SingleRowSelectionComponent extends BasicGridComponent { + data = SampleTestData.foodProductDataExtended(); + public width = '800px'; + public height = '600px'; } @Component({ template: GridTemplateStrings.declareGrid( - ` [rowSelectable]="true"`, + ` [width]="width" [height]="height" [rowSelection]="'multiple'"`, + '', + ColumnDefinitions.idFirstLastNameSortable) +}) + +export class RowSelectionWithoutPrimaryKeyComponent extends BasicGridComponent { + public data = SampleTestData.personIDNameRegionData(); + public width = '800px'; + public height = '600px'; +} +@Component({ + template: GridTemplateStrings.declareGrid( + ` rowSelection = "multiple"`, EventSubscriptions.onRowSelectionChange, ColumnDefinitions.productBasic) }) @@ -180,7 +187,7 @@ export class SelectionCancellableComponent extends BasicGridComponent { data = SampleTestData.foodProductData(); public rowSelectionChange(evt) { - if (evt.row && (evt.row.index + 1) % 2 === 0) { + if (evt.added.length > 0 && (evt.added[0].ProductID) % 2 === 0) { evt.newSelection = evt.oldSelection || []; } } @@ -193,7 +200,7 @@ export class SelectionCancellableComponent extends BasicGridComponent { [width]="'800px'" [height]="'600px'" [autoGenerate]="true" - [rowSelectable]="true"`, + rowSelection = "multiple"`, EventSubscriptions.onColumnInit, '') }) export class ScrollsComponent extends BasicGridComponent { @@ -205,7 +212,7 @@ export class ScrollsComponent extends BasicGridComponent { @Component({ template: GridTemplateStrings.declareGrid( - ` [rowSelectable]="true"`, + ` rowSelection = "multiple"`, '', ColumnDefinitions.productDefaultSummaries) }) export class SummariesComponent extends BasicGridComponent { @@ -268,7 +275,6 @@ export class VirtualSummaryColumnComponent extends BasicGridComponent { const hScrollbar = this.grid.parentVirtDir.getHorizontalScroll(); hScrollbar.scrollLeft = newLeft; } - } @Component({ @@ -638,14 +644,14 @@ export class GridIDNameJobTitleComponent extends PagingComponent { @Component({ template: `
${GridTemplateStrings.declareGrid( - `[height]="height" [width]="width" [rowSelectable]="enableRowSelection" [autoGenerate]="autoGenerate"`, + `[height]="height" [width]="width" [rowSelection]="rowSelection" [autoGenerate]="autoGenerate"`, EventSubscriptions.onColumnMovingStart + EventSubscriptions.onColumnMoving + EventSubscriptions.onColumnMovingEnd, ColumnDefinitions.movableColumns)}
` }) export class MovableColumnsComponent extends BasicGridComponent { data = SampleTestData.personIDNameRegionData(); autoGenerate = false; - enableRowSelection = false; + rowSelection = 'none'; isFilterable = false; isSortable = false; isResizable = false; @@ -886,6 +892,21 @@ export class SelectionWithScrollsComponent extends BasicGridComponent { public data = SampleTestData.employeeGroupByData(); } +@Component({ + template: `${GridTemplateStrings.declareGrid(`height="300px" width="600px" [primaryKey]="'ID'" cellSelection="none"`, '', + ColumnDefinitions.selectionWithScrollsColumns)}`, +}) +export class CellSelectionNoneComponent extends BasicGridComponent { + public data = SampleTestData.employeeGroupByData(); +} + +@Component({ + template: `${GridTemplateStrings.declareGrid(`height="300px" width="600px" [primaryKey]="'ID'" cellSelection="single"`, '', + ColumnDefinitions.selectionWithScrollsColumns)}`, +}) +export class CellSelectionSingleComponent extends BasicGridComponent { + public data = SampleTestData.employeeGroupByData(); +} @Component({ template: `${GridTemplateStrings.declareGrid(`height="300px" width="600px" [primaryKey]="'ID'"`, '', ColumnDefinitions.selectionWithScrollsColumns)}`, diff --git a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts index 70ed908dbdf..5dd5c3d5b93 100644 --- a/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/helper-utils.spec.ts @@ -9,9 +9,16 @@ import { IgxGridCellComponent } from '../grids/cell.component'; import { ComponentFixture } from '@angular/core/testing'; import { IgxGridBaseComponent } from '../grids/index'; import { IgxHierarchicalGridComponent } from '../grids/hierarchical-grid'; +import { IgxGridExcelStyleFilteringComponent } from '../grids/filtering/excel-style/grid.excel-style-filtering.component'; const CELL_ACTIVE_CSS_CLASS = 'igx-grid-summary--active'; const CELL_SELECTED_CSS_CLASS = 'igx-grid__td--selected'; +const ROW_DIV_SELECTION_CHECKBOX_CSS_CLASS = '.igx-grid__cbx-selection'; +const ROW_SELECTION_CSS_CLASS = 'igx-grid__tr--selected'; +const HEADER_ROW_CSS_CLASS = '.igx-grid__thead'; +const CHECKBOX_INPUT_CSS_CLASS = '.igx-checkbox__input'; +const SCROLL_START_CSS_CLASS = '.igx-grid__scroll-start'; +const CHECKBOX_ELEMENT = 'igx-checkbox'; const DEBOUNCETIME = 50; export function resizeObserverIgnoreError() { @@ -33,7 +40,7 @@ export function setupHierarchicalGridScrollDetection(fixture: ComponentFixture setupGridScrollDetection(fixture, child)); + existingChildren.forEach(child => setupGridScrollDetection(fixture, child)); const layouts = hierarchicalGrid.allLayoutList.toArray(); layouts.forEach((layout) => { @@ -65,7 +72,7 @@ export function verifyDOMMatchesLayoutSettings(row, colSettings) { groupSetting.columns.forEach((col, colIndex) => { const cell = cellsFromBlock[colIndex]; const cellElem = cell.nativeElement; - // check correct attributes are applied + // check correct attributes are applied expect(parseInt(cellElem.style['gridRowStart'], 10)).toBe(parseInt(col.rowStart, 10)); expect(parseInt(cellElem.style['gridColumnStart'], 10)).toBe(parseInt(col.colStart, 10)); expect(cellElem.style['gridColumnEnd']).toBe(col.colEnd ? col.colEnd.toString() : ''); @@ -74,7 +81,7 @@ export function verifyDOMMatchesLayoutSettings(row, colSettings) { // check width let sum = 0; if (cell.gridColumnSpan > 1) { - for (let i = col.colStart; i < col.colStart + cell.column.gridColumnSpan; i++) { + for (let i = col.colStart; i < col.colStart + cell.column.gridColumnSpan; i++) { const colData = groupSetting.columns.find((currCol) => currCol.colStart === i && currCol.field !== col.field); const col2 = row.grid.getColumnByName(colData ? colData.field : ''); sum += col2 ? parseFloat(col2.calcWidth) : 0; @@ -86,9 +93,9 @@ export function verifyDOMMatchesLayoutSettings(row, colSettings) { const expectedHeight = cell.grid.rowHeight * cell.gridRowSpan; expect(cellElem.offsetHeight).toBe(expectedHeight); - // check offset left + // check offset left const acc = (accum, c) => { - if (c.column.colStart < col.colStart && c.column.rowStart === col.rowStart) { + if (c.column.colStart < col.colStart && c.column.rowStart === col.rowStart) { return accum += parseFloat(c.column.calcWidth) * c.column.gridColumnSpan; } else { return accum; @@ -146,38 +153,38 @@ export class HelperUtils { } public static navigateVerticallyToIndex = ( - grid: IgxGridComponent, - rowStartIndex: number, - rowEndIndex: number, - colIndex?: number, - shift = false) => new Promise(async (resolve, reject) => { - const dir = rowStartIndex > rowEndIndex ? 'ArrowUp' : 'ArrowDown'; - const row = grid.getRowByIndex(rowStartIndex); - const cIndx = colIndex || 0; - const colKey = grid.columnList.toArray()[cIndx].field; - const nextIndex = dir === 'ArrowUp' ? rowStartIndex - 1 : rowStartIndex + 1; - let elem; - if (row) { - elem = row instanceof IgxGridGroupByRowComponent ? + grid: IgxGridComponent, + rowStartIndex: number, + rowEndIndex: number, + colIndex?: number, + shift = false) => new Promise(async (resolve, reject) => { + const dir = rowStartIndex > rowEndIndex ? 'ArrowUp' : 'ArrowDown'; + const row = grid.getRowByIndex(rowStartIndex); + const cIndx = colIndex || 0; + const colKey = grid.columnList.toArray()[cIndx].field; + const nextIndex = dir === 'ArrowUp' ? rowStartIndex - 1 : rowStartIndex + 1; + let elem; + if (row) { + elem = row instanceof IgxGridGroupByRowComponent ? row : grid.getCellByColumn(row.index, colKey); - } else { - const summariRow = grid.summariesRowList.find( s => s.index === rowStartIndex) ; - if (summariRow) { - elem = summariRow.summaryCells.find(cell => cell.visibleColumnIndex === cIndx); - } + } else { + const summariRow = grid.summariesRowList.find(s => s.index === rowStartIndex); + if (summariRow) { + elem = summariRow.summaryCells.find(cell => cell.visibleColumnIndex === cIndx); } + } - if (rowStartIndex === rowEndIndex) { - resolve(); - return; - } + if (rowStartIndex === rowEndIndex) { + resolve(); + return; + } - UIInteractions.triggerKeyDownWithBlur(dir, elem.nativeElement, true, false, shift); + UIInteractions.triggerKeyDownWithBlur(dir, elem.nativeElement, true, false, shift); - await wait(40); - HelperUtils.navigateVerticallyToIndex(grid, nextIndex, rowEndIndex, colIndex, shift) - .then(() => { resolve(); }); - }) + await wait(40); + HelperUtils.navigateVerticallyToIndex(grid, nextIndex, rowEndIndex, colIndex, shift) + .then(() => { resolve(); }); + }) public static navigateHorizontallyToIndex = ( grid: IgxGridComponent, @@ -358,14 +365,14 @@ export class HelperUtils { }) public static selectCellsRangeNoWait(fix, startCell, endCell, ctrl = false, shift = false) { - UIInteractions.simulatePointerOverCellEvent('pointerdown', startCell.nativeElement, shift, ctrl); - startCell.nativeElement.dispatchEvent(new Event('focus')); - fix.detectChanges(); + UIInteractions.simulatePointerOverCellEvent('pointerdown', startCell.nativeElement, shift, ctrl); + startCell.nativeElement.dispatchEvent(new Event('focus')); + fix.detectChanges(); - UIInteractions.simulatePointerOverCellEvent('pointerenter', endCell.nativeElement, shift, ctrl); - UIInteractions.simulatePointerOverCellEvent('pointerup', endCell.nativeElement, shift, ctrl); - fix.detectChanges(); - } + UIInteractions.simulatePointerOverCellEvent('pointerenter', endCell.nativeElement, shift, ctrl); + UIInteractions.simulatePointerOverCellEvent('pointerup', endCell.nativeElement, shift, ctrl); + fix.detectChanges(); + } public static selectCellsRangeWithShiftKey = (fix, startCell, endCell) => new Promise(async (resolve, reject) => { @@ -379,15 +386,15 @@ export class HelperUtils { resolve(); }) - public static selectCellsRangeWithShiftKeyNoWait (fix, startCell, endCell) { + public static selectCellsRangeWithShiftKeyNoWait(fix, startCell, endCell) { UIInteractions.simulateClickAndSelectCellEvent(startCell); fix.detectChanges(); UIInteractions.simulateClickAndSelectCellEvent(endCell, true); fix.detectChanges(); - } + } - public static verifyCellsRegionSelected(grid, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, selected = true) { + public static verifyCellsRegionSelected(grid, startRowIndex, endRowIndex, startColumnIndex, endColumnIndex, selected = true) { const startRow = startRowIndex < endRowIndex ? startRowIndex : endRowIndex; const endRow = startRowIndex < endRowIndex ? endRowIndex : startRowIndex; const startCol = startColumnIndex < endColumnIndex ? startColumnIndex : endColumnIndex; @@ -404,16 +411,100 @@ export class HelperUtils { public static verifySelectedRange(grid, rowStart, rowEnd, columnStart, columnEnd, rangeIndex = 0, selectedRanges = 1) { const range = grid.getSelectedRanges(); - expect(range).toBeDefined(); - expect(range.length).toBe(selectedRanges); - expect(range[rangeIndex].columnStart).toBe(columnStart); - expect(range[rangeIndex].columnEnd).toBe(columnEnd); - expect(range[rangeIndex].rowStart).toBe(rowStart); - expect(range[rangeIndex].rowEnd).toBe(rowEnd); + expect(range).toBeDefined(); + expect(range.length).toBe(selectedRanges); + expect(range[rangeIndex].columnStart).toBe(columnStart); + expect(range[rangeIndex].columnEnd).toBe(columnEnd); + expect(range[rangeIndex].rowStart).toBe(rowStart); + expect(range[rangeIndex].rowEnd).toBe(rowEnd); } public static verifyCellSelected(cell, selected = true) { expect(cell.selected).toBe(selected); expect(cell.nativeElement.classList.contains(CELL_SELECTED_CSS_CLASS)).toBe(selected); } + + public static verifyRowSelected(row, selected = true, hasCheckbox = true) { + expect(row.selected).toBe(selected); + expect(row.nativeElement.classList.contains(ROW_SELECTION_CSS_CLASS)).toBe(selected); + if (hasCheckbox) { + HelperUtils.verifyRowHasCheckbox(row.nativeElement); + expect(HelperUtils.getRowCheckboxInput(row.nativeElement).checked).toBe(selected); + } + } + + public static verifyRowsArraySelected(rows, selected = true, hasCheckbox = true) { + rows.forEach(row => { + HelperUtils.verifyRowSelected(row, selected, hasCheckbox); + }); + } + + public static verifyHeaderRowCheckboxState(fix, checked = false, indeterminate = false) { + const header = HelperUtils.getHeaderRow(fix); + const headerCheckboxElement = HelperUtils.getRowCheckboxInput(header); + expect(headerCheckboxElement.checked).toBe(checked); + expect(headerCheckboxElement.indeterminate).toBe(indeterminate); + } + + public static verifyHeaderAndRowCheckBoxesAlignment(fix, grid) { + const headerDiv = HelperUtils.getRowCheckboxDiv(HelperUtils.getHeaderRow(fix)); + const firstRowDiv = HelperUtils.getRowCheckboxDiv(grid.rowList.first.nativeElement); + const scrollStartElement = fix.nativeElement.querySelector(SCROLL_START_CSS_CLASS); + const hScrollbar = grid.parentVirtDir.getHorizontalScroll(); + + expect(headerDiv.offsetWidth).toEqual(firstRowDiv.offsetWidth); + expect(headerDiv.offsetLeft).toEqual(firstRowDiv.offsetLeft); + if (hScrollbar.scrollWidth) { + expect(scrollStartElement.offsetWidth).toEqual(firstRowDiv.offsetWidth); + expect(hScrollbar.offsetLeft).toEqual(firstRowDiv.offsetWidth); + } + } + + public static verifyRowHasCheckbox(rowDOM, hasCheckbox = true, hasCheckboxDiv = true, verifyHeader = false) { + const checkboxDiv = HelperUtils.getRowCheckboxDiv(rowDOM); + if (!hasCheckbox && !hasCheckboxDiv) { + expect(HelperUtils.getRowCheckboxDiv(rowDOM)).toBeNull(); + } else { + expect(checkboxDiv).toBeDefined(); + const rowCheckbox = HelperUtils.getRowCheckbox(rowDOM); + expect(rowCheckbox).toBeDefined(); + if (!hasCheckbox) { + expect(rowCheckbox.style.visibility).toEqual('hidden'); + } else if (verifyHeader) { + expect(rowCheckbox.style.visibility).toEqual('visible'); + } else { + expect(rowCheckbox.style.visibility).toEqual(''); + } + } + } + + public static verifyHeaderRowHasCheckbox(fix, hasCheckbox = true, hasCheckboxDiv = true) { + HelperUtils.verifyRowHasCheckbox(HelperUtils.getHeaderRow(fix), hasCheckbox, hasCheckboxDiv, true); + } + + public static getHeaderRow(fix): HTMLElement { + return fix.nativeElement.querySelector(HEADER_ROW_CSS_CLASS); + } + + public static getRowCheckboxDiv(rowDOM): HTMLElement { + return rowDOM.querySelector(ROW_DIV_SELECTION_CHECKBOX_CSS_CLASS); + } + + public static getRowCheckboxInput(rowDOM): HTMLInputElement { + return HelperUtils.getRowCheckboxDiv(rowDOM).querySelector(CHECKBOX_INPUT_CSS_CLASS); + } + + public static getRowCheckbox(rowDOM): HTMLElement { + return HelperUtils.getRowCheckboxDiv(rowDOM).querySelector(CHECKBOX_ELEMENT); + } + + public static clickRowCheckbox(row) { + const checkboxElement = HelperUtils.getRowCheckboxDiv(row.nativeElement); + checkboxElement.dispatchEvent(new Event('click', {})); + } + + public static clickHeaderRowCheckbox(fix) { + const checkboxElement = HelperUtils.getRowCheckboxDiv(HelperUtils.getHeaderRow(fix)); + checkboxElement.dispatchEvent(new Event('click', {})); + } } 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..b0b244aaec2 --- /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(5, 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 diff --git a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts index 526e42da9e9..54d23cab66f 100644 --- a/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/template-strings.spec.ts @@ -243,6 +243,19 @@ export class ColumnDefinitions { `; + public static productBasicNumberID = ` + + + + + + + + + + + `; + public static productSummariesAndFilter = ` diff --git a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts index f1494d87649..ec1b7d55073 100644 --- a/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/tree-grid-functions.spec.ts @@ -89,10 +89,14 @@ export class TreeGridFunctions { } public static getRowCheckbox(rowDOM) { - const checkboxDiv = rowDOM.query(By.css(TREE_ROW_DIV_SELECTION_CHECKBOX_CSS_CLASS)); + const checkboxDiv = TreeGridFunctions.getRowCheckboxDiv(rowDOM); return checkboxDiv.query(By.css(CHECKBOX_INPUT_CSS_CLASS)); } + public static getRowCheckboxDiv(rowDOM) { + return rowDOM.query(By.css(TREE_ROW_DIV_SELECTION_CHECKBOX_CSS_CLASS)); + } + public static clickHeaderCell(fix, columnKey) { const cell = TreeGridFunctions.getHeaderCell(fix, columnKey); cell.nativeElement.dispatchEvent(new Event('click')); @@ -100,13 +104,13 @@ export class TreeGridFunctions { public static clickRowSelectionCheckbox(fix, rowIndex) { const rowDOM = TreeGridFunctions.sortElementsVertically(TreeGridFunctions.getAllRows(fix))[rowIndex]; - const checkbox = TreeGridFunctions.getRowCheckbox(rowDOM); + const checkbox = TreeGridFunctions.getRowCheckboxDiv(rowDOM); checkbox.nativeElement.dispatchEvent(new Event('click')); } public static clickHeaderRowSelectionCheckbox(fix) { const headerRow = TreeGridFunctions.getHeaderRow(fix); - const checkbox = TreeGridFunctions.getRowCheckbox(headerRow); + const checkbox = TreeGridFunctions.getRowCheckboxDiv(headerRow); checkbox.nativeElement.dispatchEvent(new Event('click')); } @@ -293,7 +297,7 @@ export class TreeGridFunctions { expect(checkboxComponent.nativeCheckbox.nativeElement.checked).toBe(expectedSelection, 'Incorrect native checkbox selection state'); // Verify selection of row - expect(rowComponent.isSelected).toBe(expectedSelection, 'Incorrect row selection state'); + expect(rowComponent.selected).toBe(expectedSelection, 'Incorrect row selection state'); expect((rowDOM.nativeElement).classList.contains(TREE_ROW_SELECTION_CSS_CLASS)).toBe(expectedSelection); // Verify selection of row through treeGrid diff --git a/projects/igniteui-angular/src/lib/test-utils/ui-interactions.spec.ts b/projects/igniteui-angular/src/lib/test-utils/ui-interactions.spec.ts index 91a23e49926..bb89815d870 100644 --- a/projects/igniteui-angular/src/lib/test-utils/ui-interactions.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/ui-interactions.spec.ts @@ -64,7 +64,7 @@ export class UIInteractions { UIInteractions.simulatePointerEvent('pointerdown', nativeElement, elementRect.left, elementRect.top); nativeElement.dispatchEvent(new Event('focus')); UIInteractions.simulatePointerEvent('pointerup', nativeElement, elementRect.left, elementRect.top); - nativeElement.dispatchEvent(new Event('click', { bubbles: true })); + nativeElement.dispatchEvent(new Event('click', { bubbles: true})); } public static simulateMouseEvent(eventName: string, element, x, y) { @@ -124,6 +124,15 @@ export class UIInteractions { element.dispatchEvent(new PointerEvent(eventName, options)); } + public static simulateClickEvent(element, shift = false, ctrl = false) { + const event = new MouseEvent('click', { + bubbles: true, + shiftKey: shift, + ctrlKey: ctrl + }); + element.dispatchEvent(event); + } + public static simulateDragScrollOverCellEvent(eventName: string, element, clientX, clientY, shift = false, ctrl = false) { const options: PointerEventInit = { view: window, diff --git a/src/app/grid-groupby/grid-groupby.sample.html b/src/app/grid-groupby/grid-groupby.sample.html index a4baaf7012a..8a5eddffb97 100644 --- a/src/app/grid-groupby/grid-groupby.sample.html +++ b/src/app/grid-groupby/grid-groupby.sample.html @@ -4,10 +4,10 @@
- + [hidden]='c.hidden' [sortable]='true' [groupable]='c.groupable' [movable]='true' [pinned]='!!c.pinned' [editable]="true" [hasSummary]="true" [dataType]='c.dataType'>