diff --git a/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json b/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json index ec1659401..07b3384c3 100644 --- a/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json +++ b/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Clear all Filters", "CLEAR_ALL_GROUPING": "Clear all Grouping", "CLEAR_ALL_SORTING": "Clear all Sorting", - "CLEAR_FROZEN_COLUMNS": "Clear Frozen Columns", + "CLEAR_PINNING": "Unfreeze Columns/Rows", "CLONE": "Clone", "COLLAPSE_ALL_GROUPS": "Collapse all Groups", "COLUMNS": "Columns", diff --git a/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json b/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json index 30cd91d74..ceacd51e8 100644 --- a/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json +++ b/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", "CLEAR_ALL_SORTING": "Supprimer tous les tris", - "CLEAR_FROZEN_COLUMNS": "Libérer les colonnes gelées", + "CLEAR_PINNING": "Dégeler les colonnes/rangées", "CLONE": "Cloner", "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", "COLUMNS": "Colonnes", diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts index 65f8fed97..d17202d5a 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts @@ -5,6 +5,7 @@ import { Column, CurrentColumn, CurrentFilter, + CurrentPinning, CurrentSorter, Editors, FieldType, @@ -58,6 +59,7 @@ export interface ViewDefinition { columns?: CurrentColumn[]; filters: CurrentFilter[]; sorters?: CurrentSorter[]; + pinning?: CurrentPinning; } export class Example11 { @@ -326,7 +328,11 @@ export class Example11 { ], onCommand: (e, args) => this.executeCommand(e, args) }, + headerMenu: { + hideFreezeColumnsCommand: false, + }, gridMenu: { + hideClearFrozenColumnsCommand: false, customItems: [ { command: 'modal', @@ -357,6 +363,9 @@ export class Example11 { if (presetSelection.sorters) { this.gridOptions.presets.sorters = presetSelection.sorters; } + if (presetSelection.pinning) { + this.gridOptions.presets.pinning = presetSelection.pinning; + } } } } @@ -618,7 +627,7 @@ export class Example11 { this.predefinedViews.forEach(viewSelect => viewSelect.isSelected = false); // reset selection const currentGridState = this.sgb.gridStateService.getCurrentGridState(); - const { columns, filters, sorters } = currentGridState; + const { columns, filters, sorters, pinning } = currentGridState; const viewName = await prompt('Please provide a name for the new View.'); if (viewName) { @@ -632,6 +641,7 @@ export class Example11 { columns: deepCopy(columns), filters: deepCopy(filters), sorters: deepCopy(sorters), + pinning: deepCopy(pinning), }; this.dropdownDeleteViewClass = 'dropdown-item'; @@ -671,7 +681,7 @@ export class Example11 { return; } const currentGridState = this.sgb.gridStateService.getCurrentGridState(); - const { columns, filters, sorters } = currentGridState; + const { columns, filters, sorters, pinning } = currentGridState; if (this.currentSelectedViewPreset && filters) { const filterName = await prompt(`Update View name or click on OK to continue.`, this.currentSelectedViewPreset.label); @@ -680,6 +690,7 @@ export class Example11 { this.currentSelectedViewPreset.columns = columns || []; this.currentSelectedViewPreset.filters = filters || []; this.currentSelectedViewPreset.sorters = sorters || []; + this.currentSelectedViewPreset.pinning = pinning || {}; this.recreatePredefinedViews(); localStorage.setItem('gridViewPreset', JSON.stringify(this.predefinedViews)); } @@ -696,10 +707,13 @@ export class Example11 { const columns = selectedView?.columns ?? []; const filters = selectedView?.filters ?? []; const sorters = selectedView?.sorters ?? []; + const pinning = selectedView?.pinning ?? { frozenBottom: false, frozenColumn: -1, frozenRow: -1 }; this.sgb.filterService.updateFilters(filters as CurrentFilter[]); this.sgb.sortService.updateSorting(sorters as CurrentSorter[]); + this.sgb.gridService.setPinning(pinning); this.sgb.gridStateService.changeColumnsArrangement(columns); } else { + this.sgb.gridService.clearPinning(); this.sgb.filterService.clearFilters(); this.sgb.sortService.clearSorting(); this.sgb.gridStateService.changeColumnsArrangement([...this.columnDefinitions].map(col => ({ columnId: `${col.id}` }))); diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts b/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts index cede0b21f..5720b23d6 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts @@ -68,9 +68,11 @@ export class Icons { '.mdi.mdi-check-all', '.mdi.mdi-check-bold', '.mdi.mdi-checkbox-blank-outline', + '.mdi.mdi-checkbox-marked-circle-outline', '.mdi.mdi-check-box-outline', '.mdi.mdi-checkbox-marked', '.mdi.mdi-check-circle', + '.mdi.mdi-check-circle-outline', '.mdi.mdi-check-outline', '.mdi.mdi-check-underline', '.mdi.mdi-chevron-down', @@ -78,6 +80,7 @@ export class Icons { '.mdi.mdi-chevron-down-box-outline', '.mdi.mdi-chevron-down-circle', '.mdi.mdi-chevron-down-circle-outline', + '.mdi.mdi-clipboard-check', '.mdi.mdi-clipboard-check-outline', '.mdi.mdi-clipboard-edit', '.mdi.mdi-clipboard-edit-outline', diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index 5eb7ca7f3..52f743081 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -10,7 +10,7 @@ export class Constants { TEXT_CLEAR_ALL_FILTERS: 'Clear all Filters', TEXT_CLEAR_ALL_GROUPING: 'Clear all Grouping', TEXT_CLEAR_ALL_SORTING: 'Clear all Sorting', - TEXT_CLEAR_FROZEN_COLUMNS: 'Clear Frozen Columns', + TEXT_CLEAR_PINNING: 'Unfreeze Columns/Rows', TEXT_CLONE: 'Clone', TEXT_COLLAPSE_ALL_GROUPS: 'Collapse all Groups', TEXT_CONTAINS: 'Contains', diff --git a/packages/common/src/enums/gridStateType.enum.ts b/packages/common/src/enums/gridStateType.enum.ts index 21607be94..de5dc50ab 100644 --- a/packages/common/src/enums/gridStateType.enum.ts +++ b/packages/common/src/enums/gridStateType.enum.ts @@ -2,6 +2,7 @@ export enum GridStateType { columns = 'columns', filter = 'filter', pagination = 'pagination', + pinning = 'pinning', rowSelection = 'rowSelection', sorter = 'sorter', } diff --git a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts index 65ef9cadc..761b39a0c 100644 --- a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts @@ -356,13 +356,13 @@ describe('gridMenuExtension', () => { expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); }); - it('should expect menu related to "Clear Frozen Columns"', () => { + it('should expect menu related to "Unfreeze Columns/Rows"', () => { const copyGridOptionsMock = { ...gridOptionsMock, gridMenu: { hideClearFrozenColumnsCommand: false, } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-times', title: 'Libérer les colonnes gelées', disabled: false, command: 'clear-frozen-columns', positionOrder: 49 }, + { iconCssClass: 'fa fa-times', title: 'Dégeler les colonnes/rangées', disabled: false, command: 'clear-pinning', positionOrder: 52 }, ]); }); @@ -373,8 +373,8 @@ describe('gridMenuExtension', () => { extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ { iconCssClass: 'fa fa-filter text-danger', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 }, - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 52 }, - { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 56 } + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, + { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } ]); }); @@ -394,7 +394,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 52 }, + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, ]); }); @@ -404,7 +404,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 56 } + { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } ]); }); @@ -414,7 +414,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne de pré-en-tête', disabled: false, command: 'toggle-preheader', positionOrder: 52 } + { iconCssClass: 'fa fa-random', title: 'Basculer la ligne de pré-en-tête', disabled: false, command: 'toggle-preheader', positionOrder: 53 } ]); }); @@ -448,7 +448,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-download', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 53 } + { iconCssClass: 'fa fa-download', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 54 } ]); }); @@ -465,7 +465,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-file-excel-o text-success', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 54 } + { iconCssClass: 'fa fa-file-excel-o text-success', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 55 } ]); }); @@ -475,7 +475,7 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); // calling 2x register to make sure it doesn't duplicate commands expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-download', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 55 } + { iconCssClass: 'fa fa-download', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 56 } ]); }); @@ -529,8 +529,8 @@ describe('gridMenuExtension', () => { it('should have user grid menu custom items', () => { extension.register(); expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 53, title: 'Exporter en format CSV' }, - // { command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 53, title: 'Exporter vers Excel' }, + { command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 54, title: 'Exporter en format CSV' }, + // { command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 54, title: 'Exporter vers Excel' }, { command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' }, ]); }); @@ -539,8 +539,8 @@ describe('gridMenuExtension', () => { extension.register(); extension.register(); expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 53, title: 'Exporter en format CSV' }, - // { command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 53, title: 'Exporter vers Excel' }, + { command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 54, title: 'Exporter en format CSV' }, + // { command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 54, title: 'Exporter vers Excel' }, { command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' }, ]); }); @@ -588,7 +588,7 @@ describe('gridMenuExtension', () => { mockGridMenuAddon.onCommand = new Slick.Event(); }); - it('should call "clearFrozenColumns" when the command triggered is "clear-frozen-columns"', () => { + it('should call "clearFrozenColumns" when the command triggered is "clear-pinning"', () => { const setOptionsSpy = jest.spyOn(gridStub, 'setOptions'); const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu as GridMenu, 'onCommand'); @@ -596,11 +596,11 @@ describe('gridMenuExtension', () => { jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock.slice(0, 1)); const instance = extension.register() as SlickGridMenu; - instance.onCommand!.notify({ item: { command: 'clear-frozen-columns' }, column: {} as Column, grid: gridStub, command: 'clear-frozen-columns' }, new Slick.EventData(), gridStub); + instance.onCommand!.notify({ item: { command: 'clear-pinning' }, column: {} as Column, grid: gridStub, command: 'clear-pinning' }, new Slick.EventData(), gridStub); expect(onCommandSpy).toHaveBeenCalled(); expect(setColumnsSpy).toHaveBeenCalled(); - expect(setOptionsSpy).toHaveBeenCalledWith({ frozenColumn: -1, enableMouseWheelScrollHandler: false }); + expect(setOptionsSpy).toHaveBeenCalledWith({ frozenColumn: -1, frozenRow: -1, frozenBottom: false, enableMouseWheelScrollHandler: false }); }); it('should call "clearFilters" and dataview refresh when the command triggered is "clear-filter"', () => { diff --git a/packages/common/src/extensions/gridMenuExtension.ts b/packages/common/src/extensions/gridMenuExtension.ts index 50761d15a..1b3ceb2cc 100644 --- a/packages/common/src/extensions/gridMenuExtension.ts +++ b/packages/common/src/extensions/gridMenuExtension.ts @@ -225,17 +225,17 @@ export class GridMenuExtension implements Extension { const gridOptions = this.sharedService.gridOptions; const translationPrefix = getTranslationPrefix(gridOptions); - // show grid menu: Clear Frozen Columns + // show grid menu: Unfreeze Columns/Rows if (this.sharedService.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideClearFrozenColumnsCommand) { - const commandName = 'clear-frozen-columns'; + const commandName = 'clear-pinning'; if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { gridMenuCustomItems.push( { iconCssClass: this._gridMenuOptions.iconClearFrozenColumnsCommand || 'fa fa-times', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_FROZEN_COLUMNS`, 'TEXT_CLEAR_FROZEN_COLUMNS'), + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_PINNING`, 'TEXT_CLEAR_PINNING'), disabled: false, command: commandName, - positionOrder: 49 + positionOrder: 52 } ); } @@ -268,7 +268,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}TOGGLE_FILTER_ROW`, 'TEXT_TOGGLE_FILTER_ROW'), disabled: false, command: commandName, - positionOrder: 52 + positionOrder: 53 } ); } @@ -284,7 +284,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REFRESH_DATASET`, 'TEXT_REFRESH_DATASET'), disabled: false, command: commandName, - positionOrder: 56 + positionOrder: 57 } ); } @@ -302,7 +302,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}TOGGLE_PRE_HEADER_ROW`, 'TEXT_TOGGLE_PRE_HEADER_ROW'), disabled: false, command: commandName, - positionOrder: 52 + positionOrder: 53 } ); } @@ -337,7 +337,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_CSV`, 'TEXT_EXPORT_TO_CSV'), disabled: false, command: commandName, - positionOrder: 53 + positionOrder: 54 } ); } @@ -353,7 +353,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_EXCEL`, 'TEXT_EXPORT_TO_EXCEL'), disabled: false, command: commandName, - positionOrder: 54 + positionOrder: 55 } ); } @@ -369,7 +369,7 @@ export class GridMenuExtension implements Extension { title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_TAB_DELIMITED`, 'TEXT_EXPORT_TO_TAB_DELIMITED'), disabled: false, command: commandName, - positionOrder: 55 + positionOrder: 56 } ); } @@ -394,15 +394,25 @@ export class GridMenuExtension implements Extension { if (args && args.command) { switch (args.command) { - case 'clear-frozen-columns': + case 'clear-pinning': const visibleColumns = [...this.sharedService.visibleColumns]; - this.sharedService.slickGrid.setOptions({ frozenColumn: -1, enableMouseWheelScrollHandler: false }); + const newGridOptions = { frozenColumn: -1, frozenRow: -1, frozenBottom: false, enableMouseWheelScrollHandler: false }; + this.sharedService.slickGrid.setOptions(newGridOptions); + this.sharedService.gridOptions.frozenColumn = newGridOptions.frozenColumn; + this.sharedService.gridOptions.frozenRow = newGridOptions.frozenRow; + this.sharedService.gridOptions.frozenBottom = newGridOptions.frozenBottom; + this.sharedService.gridOptions.enableMouseWheelScrollHandler = newGridOptions.enableMouseWheelScrollHandler; // SlickGrid seems to be somehow resetting the columns to their original positions, // so let's re-fix them to the position we kept as reference if (Array.isArray(visibleColumns)) { this.sharedService.slickGrid.setColumns(visibleColumns); } + + // we also need to autosize columns if the option is enabled + if (this.sharedService.gridOptions.enableAutoSizeColumns) { + this.sharedService.slickGrid.autosizeColumns(); + } break; case 'clear-filter': this.filterService.clearFilters(); diff --git a/packages/common/src/extensions/headerMenuExtension.ts b/packages/common/src/extensions/headerMenuExtension.ts index 023e54811..c245e7309 100644 --- a/packages/common/src/extensions/headerMenuExtension.ts +++ b/packages/common/src/extensions/headerMenuExtension.ts @@ -349,7 +349,10 @@ export class HeaderMenuExtension implements Extension { case 'freeze-columns': const visibleColumns = [...this.sharedService.visibleColumns]; const columnPosition = visibleColumns.findIndex((col) => col.id === args.column.id); - this.sharedService.slickGrid.setOptions({ frozenColumn: columnPosition, enableMouseWheelScrollHandler: true }); + const newGridOptions = { frozenColumn: columnPosition, enableMouseWheelScrollHandler: true }; + this.sharedService.slickGrid.setOptions(newGridOptions); + this.sharedService.gridOptions.frozenColumn = newGridOptions.frozenColumn; + this.sharedService.gridOptions.enableMouseWheelScrollHandler = newGridOptions.enableMouseWheelScrollHandler; this.sharedService.frozenVisibleColumnId = args.column.id; // to freeze columns, we need to take only the visible columns and we also need to use setColumns() when some of them are hidden @@ -357,6 +360,11 @@ export class HeaderMenuExtension implements Extension { if (this.sharedService.hasColumnsReordered || (Array.isArray(visibleColumns) && Array.isArray(this.sharedService.allColumns) && visibleColumns.length !== this.sharedService.allColumns.length)) { this.sharedService.slickGrid.setColumns(visibleColumns); } + + // we also need to autosize columns if the option is enabled + if (this.sharedService.gridOptions.enableAutoSizeColumns) { + this.sharedService.slickGrid.autosizeColumns(); + } break; case 'sort-asc': case 'sort-desc': diff --git a/packages/common/src/interfaces/currentPinning.interface.ts b/packages/common/src/interfaces/currentPinning.interface.ts new file mode 100644 index 000000000..8287414c1 --- /dev/null +++ b/packages/common/src/interfaces/currentPinning.interface.ts @@ -0,0 +1,10 @@ +export interface CurrentPinning { + /** Defaults to false, do we want to freeze (pin) the bottom portion instead of the top */ + frozenBottom?: boolean; + + /** Number of column index(es) to freeze (pin) in the grid */ + frozenColumn?: number; + + /** Number of row index(es) to freeze (pin) in the grid */ + frozenRow?: number; +} diff --git a/packages/common/src/interfaces/gridMenuOption.interface.ts b/packages/common/src/interfaces/gridMenuOption.interface.ts index 501afede6..66d064aa4 100644 --- a/packages/common/src/interfaces/gridMenuOption.interface.ts +++ b/packages/common/src/interfaces/gridMenuOption.interface.ts @@ -37,7 +37,7 @@ export interface GridMenuOption { /** Defaults to false, which will hide the "Clear all Sorting" command in the Grid Menu (Grid Option "enableSorting: true" has to be enabled) */ hideClearAllSortingCommand?: boolean; - /** Defaults to true, which will hide the "Clear Frozen Columns" command in the Grid Menu */ + /** Defaults to true, which will hide the "Unfreeze Columns/Rows" command in the Grid Menu */ hideClearFrozenColumnsCommand?: boolean; /** Defaults to false, which will hide the "Export to CSV" command in the Grid Menu (Grid Option "enableTextExport: true" has to be enabled) */ @@ -73,7 +73,7 @@ export interface GridMenuOption { /** icon for the "Clear all Sorting" command */ iconClearAllSortingCommand?: string; - /** icon for the "Clear Frozen Columns" command */ + /** icon for the "Unfreeze Columns/Rows" command */ iconClearFrozenColumnsCommand?: string; /** icon for the "Export to CSV" command */ diff --git a/packages/common/src/interfaces/gridOption.interface.ts b/packages/common/src/interfaces/gridOption.interface.ts index c385be2c4..df24311a1 100644 --- a/packages/common/src/interfaces/gridOption.interface.ts +++ b/packages/common/src/interfaces/gridOption.interface.ts @@ -371,10 +371,10 @@ export interface GridOption { /** Defaults to false, do we want to freeze (pin) the bottom portion instead of the top */ frozenBottom?: boolean; - /** Number of column(s) to freeze (pin) in the grid */ + /** Number of column index(es) to freeze (pin) in the grid */ frozenColumn?: number; - /** Number of row(s) to freeze (pin) in the grid */ + /** Number of row index(es) to freeze (pin) in the grid */ frozenRow?: number; /** Defaults to false, which leads to have row with full width */ diff --git a/packages/common/src/interfaces/gridState.interface.ts b/packages/common/src/interfaces/gridState.interface.ts index 6e2ba3b07..f52d505c8 100644 --- a/packages/common/src/interfaces/gridState.interface.ts +++ b/packages/common/src/interfaces/gridState.interface.ts @@ -1,4 +1,4 @@ -import { CurrentColumn, CurrentFilter, CurrentPagination, CurrentRowSelection, CurrentSorter } from './index'; +import { CurrentColumn, CurrentFilter, CurrentPagination, CurrentPinning, CurrentRowSelection, CurrentSorter } from './index'; export interface GridState { /** Columns (and their state: visibility/position) that are currently applied in the grid */ @@ -13,6 +13,9 @@ export interface GridState { /** Pagination (and it's state, pageNumber, pageSize) that are currently applied in the grid */ pagination?: CurrentPagination | null; + /** Pinning (frozen) column & row position */ + pinning?: CurrentPinning; + /** Row Selections (by their dataContext IDs and/or grid row indexes) */ rowSelection?: CurrentRowSelection | null; } diff --git a/packages/common/src/interfaces/index.ts b/packages/common/src/interfaces/index.ts index 997251e0c..d8d8d6213 100644 --- a/packages/common/src/interfaces/index.ts +++ b/packages/common/src/interfaces/index.ts @@ -31,6 +31,7 @@ export * from './contextMenuOption.interface'; export * from './currentColumn.interface'; export * from './currentFilter.interface'; export * from './currentPagination.interface'; +export * from './currentPinning.interface'; export * from './currentRowSelection.interface'; export * from './currentSorter.interface'; export * from './customFooterOption.interface'; diff --git a/packages/common/src/interfaces/locale.interface.ts b/packages/common/src/interfaces/locale.interface.ts index 34a183bb1..f27341e9a 100644 --- a/packages/common/src/interfaces/locale.interface.ts +++ b/packages/common/src/interfaces/locale.interface.ts @@ -26,8 +26,8 @@ export interface Locale { /** Text "Clear all Sorting" shown in Header Menu */ TEXT_CLEAR_ALL_SORTING: string; - /** Text "Clear Frozen Columns" shown in Grid Menu */ - TEXT_CLEAR_FROZEN_COLUMNS: string; + /** Text "Unfreeze Columns/Rows" shown in Grid Menu */ + TEXT_CLEAR_PINNING: string; /** Text "Clone" displayed in the Composite Editor with a "clone" modal type. */ TEXT_CLONE?: string; diff --git a/packages/common/src/services/__tests__/grid.service.spec.ts b/packages/common/src/services/__tests__/grid.service.spec.ts index d18ebfbc1..1e2ead7d8 100644 --- a/packages/common/src/services/__tests__/grid.service.spec.ts +++ b/packages/common/src/services/__tests__/grid.service.spec.ts @@ -1,6 +1,6 @@ import 'jest-extended'; -import { ExtensionService, FilterService, GridService, GridStateService, PaginationService, PubSubService, SharedService, SortService } from '../index'; +import { FilterService, GridService, GridStateService, PaginationService, PubSubService, SharedService, SortService } from '../index'; import { GridOption, CellArgs, Column, OnEventArgs, SlickGrid, SlickDataView, SlickNamespace } from '../../interfaces/index'; jest.useFakeTimers(); @@ -17,10 +17,6 @@ jest.mock('flatpickr', () => { }); jest.mock('slickgrid/plugins/slick.rowselectionmodel', () => mockSelectionModelImplementation); Slick.RowSelectionModel = mockSelectionModelImplementation; -const extensionServiceStub = { - getAllColumns: jest.fn(), -} as unknown as ExtensionService; - const filterServiceStub = { clearFilters: jest.fn(), } as unknown as FilterService; @@ -80,6 +76,7 @@ const gridStub = { navigateTop: jest.fn(), render: jest.fn(), setColumns: jest.fn(), + setOptions: jest.fn(), setSelectedRows: jest.fn(), scrollRowIntoView: jest.fn(), updateRow: jest.fn(), @@ -98,7 +95,7 @@ describe('Grid Service', () => { jest.spyOn(gridStub, 'getOptions').mockReturnValue(mockGridOptions); beforeEach(() => { - service = new GridService(extensionServiceStub, gridStateServiceStub, filterServiceStub, pubSubServiceStub, paginationServiceStub, sharedService, sortServiceStub); + service = new GridService(gridStateServiceStub, filterServiceStub, pubSubServiceStub, paginationServiceStub, sharedService, sortServiceStub); service.init(gridStub); }); @@ -1012,6 +1009,51 @@ describe('Grid Service', () => { }); }); + describe('Pinning methods', () => { + const columnsMock: Column[] = [{ id: 'field1', field: 'field1', width: 100, nameKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }]; + + it('should call "clearPinning" and expect SlickGrid "setOptions" and "setColumns" to be called with frozen options being reset', () => { + const setOptionsSpy = jest.spyOn(gridStub, 'setOptions'); + const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); + jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); + jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock.slice(0, 1)); + + service.clearPinning(); + + expect(setColumnsSpy).toHaveBeenCalled(); + expect(setOptionsSpy).toHaveBeenCalledWith({ frozenBottom: false, frozenColumn: -1, frozenRow: -1, enableMouseWheelScrollHandler: false }); + }); + + it('should call "setPinning" and expect SlickGrid "setOptions" be called with new frozen options and "autosizeColumns" also be called', () => { + const mockPinning = { frozenBottom: true, frozenColumn: 1, frozenRow: 2 }; + jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); + const setOptionsSpy = jest.spyOn(gridStub, 'setOptions'); + const autosizeColumnsSpy = jest.spyOn(gridStub, 'autosizeColumns'); + const gridOptionSetterSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'set'); + + service.setPinning(mockPinning); + + expect(setOptionsSpy).toHaveBeenCalledWith(mockPinning); + expect(gridOptionSetterSpy).toHaveBeenCalledWith(mockPinning); + expect(autosizeColumnsSpy).toHaveBeenCalled(); + }); + + it('should call "setPinning" and expect SlickGrid "setOptions" be called with new frozen options and "autosizeColumns" not being called when passing False as 2nd argument', () => { + const mockPinning = { frozenBottom: true, frozenColumn: 1, frozenRow: 2 }; + jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); + const setOptionsSpy = jest.spyOn(gridStub, 'setOptions'); + const autosizeColumnsSpy = jest.spyOn(gridStub, 'autosizeColumns'); + const gridOptionSetterSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'set'); + + service.setPinning(mockPinning, false); + + expect(setOptionsSpy).toHaveBeenCalledWith(mockPinning); + expect(gridOptionSetterSpy).toHaveBeenCalledWith(mockPinning); + expect(autosizeColumnsSpy).not.toHaveBeenCalled(); + }); + }); + describe('getColumnFromEventArguments method', () => { it('should throw an error when slickgrid getColumns method is not available', () => { gridStub.getColumns = undefined as any; @@ -1480,32 +1522,34 @@ describe('Grid Service', () => { it('should call a reset and expect a few grid methods to be called', () => { const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[]; jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableAutoSizeColumns: true } as GridOption); - const extensionSpy = jest.spyOn(extensionServiceStub, 'getAllColumns').mockReturnValue(mockColumns); + const allColumnSpy = jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(mockColumns); const setColSpy = jest.spyOn(gridStub, 'setColumns'); const autosizeSpy = jest.spyOn(gridStub, 'autosizeColumns'); const gridStateSpy = jest.spyOn(gridStateServiceStub, 'resetColumns'); const filterSpy = jest.spyOn(filterServiceStub, 'clearFilters'); const sortSpy = jest.spyOn(sortServiceStub, 'clearSorting'); + const clearPinningSpy = jest.spyOn(service, 'clearPinning'); service.resetGrid(); - expect(extensionSpy).toHaveBeenCalled(); + expect(allColumnSpy).toHaveBeenCalled(); expect(setColSpy).toHaveBeenCalled(); expect(autosizeSpy).toHaveBeenCalled(); expect(gridStateSpy).toHaveBeenCalled(); expect(filterSpy).toHaveBeenCalled(); expect(sortSpy).toHaveBeenCalled(); + expect(clearPinningSpy).toHaveBeenCalled(); }); it('should call a reset and expect the grid "resetColumns" method to be called with the column definitions provided to the method', () => { const mockColumns = [{ id: 'field1', width: 100 }, { id: 'field2', width: 150 }, { id: 'field3', field: 'field3' }] as Column[]; jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableAutoResize: true, enableAutoSizeColumns: true } as GridOption); - const extensionSpy = jest.spyOn(extensionServiceStub, 'getAllColumns').mockReturnValue(mockColumns); + const allColumnSpy = jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(mockColumns); const gridStateSpy = jest.spyOn(gridStateServiceStub, 'resetColumns'); service.resetGrid(mockColumns); - expect(extensionSpy).toHaveBeenCalled(); + expect(allColumnSpy).toHaveBeenCalled(); expect(gridStateSpy).toHaveBeenCalledWith(mockColumns); }); }); diff --git a/packages/common/src/services/__tests__/gridState.service.spec.ts b/packages/common/src/services/__tests__/gridState.service.spec.ts index 29795ca2f..afc07f6fa 100644 --- a/packages/common/src/services/__tests__/gridState.service.spec.ts +++ b/packages/common/src/services/__tests__/gridState.service.spec.ts @@ -11,6 +11,7 @@ import { BackendService, GridOption, CurrentPagination, + CurrentPinning, CurrentRowSelection, CurrentSorter, CurrentFilter, @@ -40,7 +41,10 @@ jest.mock('../pubSub.service', () => ({ })); const gridOptionMock = { - enableAutoResize: true + enableAutoResize: true, + frozenBottom: false, + frozenColumn: -1, + frozenRow: -1, } as GridOption; const backendServiceStub = { @@ -69,6 +73,7 @@ const gridStub = { setSelectedRows: jest.fn(), onColumnsReordered: new Slick.Event(), onColumnsResized: new Slick.Event(), + onSetOptions: new Slick.Event(), onSelectedRowsChanged: new Slick.Event(), } as unknown as SlickGrid; @@ -243,7 +248,7 @@ describe('GridStateService', () => { }); }); - describe('bindSlickGridEventToGridStateChange tests', () => { + describe('bindSlickGridColumnChangeEventToGridStateChange tests', () => { it('should subscribe to some SlickGrid events and expect the event to be triggered when a notify is triggered after service was initialized', () => { const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; const associatedColumnsMock = [{ columnId: 'field1', cssClass: 'red', headerCssClass: '', width: 100 }] as CurrentColumn[]; @@ -268,6 +273,23 @@ describe('GridStateService', () => { expect(pubSubSpy).toHaveBeenNthCalledWith(1, `onGridStateChanged`, stateChangeMock); }); }); + + describe('bindSlickGridOnSetOptionsEventToGridStateChange tests', () => { + it('should subscribe to some SlickGrid events and expect the event to be triggered when a notify is triggered after service was initialized', () => { + const mockGridOptionsBefore = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as GridOption; + const mockGridOptionsAfter = { frozenBottom: true, frozenColumn: 1, frozenRow: 1 } as GridOption; + const gridStateMock = { pinning: mockGridOptionsBefore, columns: [], filters: [], sorters: [] } as GridState; + const stateChangeMock = { change: { newValues: mockGridOptionsAfter, type: GridStateType.pinning }, gridState: gridStateMock } as GridStateChange; + const pubSubSpy = jest.spyOn(mockPubSub, 'publish'); + const gridStateSpy = jest.spyOn(service, 'getCurrentGridState').mockReturnValue(gridStateMock); + + service.init(gridStub); + gridStub.onSetOptions.notify({ optionsBefore: mockGridOptionsBefore, optionsAfter: mockGridOptionsAfter, grid: gridStub }, new Slick.EventData()); + + expect(gridStateSpy).toHaveBeenCalled(); + expect(pubSubSpy).toHaveBeenCalledWith(`onGridStateChanged`, stateChangeMock); + }); + }); }); describe('getAssociatedCurrentColumns method', () => { @@ -360,11 +382,14 @@ describe('GridStateService', () => { }); it('should call "getCurrentGridState" method and return Pagination', () => { + const gridOptionsMock = { enablePagination: true, frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as GridOption; const paginationMock = { pageNumber: 2, pageSize: 50 } as CurrentPagination; const columnMock = [{ columnId: 'field1', cssClass: 'red', headerCssClass: '', width: 100 }] as CurrentColumn[]; const filterMock = [{ columnId: 'field1', operator: 'EQ', searchTerms: [] }] as CurrentFilter[]; const sorterMock = [{ columnId: 'field1', direction: 'ASC' }, { columnId: 'field2', direction: 'DESC' }] as CurrentSorter[]; + const pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); const columnSpy = jest.spyOn(service, 'getCurrentColumns').mockReturnValue(columnMock); const filterSpy = jest.spyOn(service, 'getCurrentFilters').mockReturnValue(filterMock); const sorterSpy = jest.spyOn(service, 'getCurrentSorters').mockReturnValue(sorterMock); @@ -376,17 +401,20 @@ describe('GridStateService', () => { expect(filterSpy).toHaveBeenCalled(); expect(sorterSpy).toHaveBeenCalled(); expect(paginationSpy).toHaveBeenCalled(); - expect(output).toEqual({ columns: columnMock, filters: filterMock, sorters: sorterMock, pagination: paginationMock } as GridState); + expect(output).toEqual({ columns: columnMock, filters: filterMock, sorters: sorterMock, pagination: paginationMock, pinning: pinningMock, } as GridState); }); }); describe('getCurrentRowSelections method', () => { + let pinningMock: CurrentPinning; + beforeEach(() => { jest.clearAllMocks(); + pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; }); it('should return null when "enableCheckboxSelector" flag is disabled', () => { - const gridOptionsMock = { enableCheckboxSelector: false, enableRowSelection: false } as GridOption; + const gridOptionsMock = { enableCheckboxSelector: false, enableRowSelection: false, ...pinningMock } as GridOption; jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); const output = service.getCurrentRowSelections(); @@ -397,7 +425,7 @@ describe('GridStateService', () => { it('should call "getCurrentGridState" method and return the Row Selection when either "enableCheckboxSelector" or "enableRowSelection" flag is enabled', () => { const selectedGridRows = [2]; const selectedRowIds = [99]; - const gridOptionsMock = { enableCheckboxSelector: true } as GridOption; + const gridOptionsMock = { enableCheckboxSelector: true, ...pinningMock } as GridOption; jest.spyOn(gridStub, 'getSelectedRows').mockReturnValue(selectedGridRows); jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); const columnMock = [{ columnId: 'field1', cssClass: 'red', headerCssClass: '', width: 100 }] as CurrentColumn[]; @@ -416,7 +444,7 @@ describe('GridStateService', () => { expect(filterSpy).toHaveBeenCalled(); expect(sorterSpy).toHaveBeenCalled(); expect(selectionSpy).toHaveBeenCalled(); - expect(output).toEqual({ columns: columnMock, filters: filterMock, sorters: sorterMock, rowSelection: selectionMock } as GridState); + expect(output).toEqual({ columns: columnMock, filters: filterMock, sorters: sorterMock, rowSelection: selectionMock, pinning: pinningMock, } as GridState); }); it('should call the "mapIdsToRows" from the DataView and get the data IDs from the "selectedRowDataContextIds" array', () => { @@ -437,13 +465,17 @@ describe('GridStateService', () => { }); describe('Row Selection - bindSlickGridRowSelectionToGridStateChange method', () => { + let pinningMock: CurrentPinning; + let gridOptionsMock: GridOption; + beforeEach(() => { jest.clearAllMocks(); }); describe('without Pagination', () => { beforeEach(() => { - const gridOptionsMock = { enablePagination: false, enableRowSelection: true } as GridOption; + pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; + gridOptionsMock = { enablePagination: false, enableRowSelection: true, ...pinningMock } as GridOption; jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); }); @@ -475,6 +507,7 @@ describe('GridStateService', () => { columns: columnMock, filters: filterMock, sorters: sorterMock, + pinning: pinningMock, rowSelection: { gridRowIndexes: mockRowIndexes, dataContextIds: mockRowIds, filteredDataContextIds: mockRowIds }, }, }); @@ -484,11 +517,14 @@ describe('GridStateService', () => { }); describe('with Pagination (bindSlickGridRowSelectionWithPaginationToGridStateChange)', () => { + let pinningMock: CurrentPinning; beforeEach(() => { jest.clearAllMocks(); service.dispose(); - const gridOptionsMock = { enablePagination: true, enableRowSelection: true } as GridOption; + pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; + const gridOptionsMock = { enablePagination: true, enableRowSelection: true, ...pinningMock } as GridOption; jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); }); it('should call the "onGridStateChanged" event with the row selection when Pagination is disabled and "onSelectedRowsChanged" is triggered', (done) => { @@ -526,6 +562,7 @@ describe('GridStateService', () => { columns: columnMock, filters: filterMock, sorters: sorterMock, + pinning: pinningMock, pagination: paginationMock, rowSelection: { gridRowIndexes: mockRowIndexes, dataContextIds: mockRowIds, filteredDataContextIds: mockRowIds }, }, @@ -602,6 +639,7 @@ describe('GridStateService', () => { filters: null, sorters: null, pagination: paginationMock, + pinning: pinningMock, rowSelection: { gridRowIndexes: shouldBeSelectedRowIndexes, dataContextIds: mockRowIds, filteredDataContextIds: mockRowIds }, }, }); @@ -700,6 +738,7 @@ describe('GridStateService', () => { filters: filterMock, sorters: sorterMock, pagination: paginationMock, + pinning: pinningMock, rowSelection: { gridRowIndexes: mockRowIndexes, dataContextIds: mockRowIds, filteredDataContextIds: mockRowIds }, } }); @@ -711,6 +750,7 @@ describe('GridStateService', () => { filters: filterMock, sorters: sorterMock, pagination: paginationMock, + pinning: pinningMock, rowSelection: { gridRowIndexes: mockRowIndexes, dataContextIds: mockRowIds, filteredDataContextIds: mockFilterSearchTerms }, }, }); @@ -973,10 +1013,13 @@ describe('GridStateService', () => { let currentColumnsMock: CurrentColumn[]; let filterMock: CurrentFilter[]; let sorterMock: CurrentSorter[]; + let pinningMock: CurrentPinning; beforeEach(() => { - const gridOptionsMock = { enablePagination: false, enableCheckboxSelector: false } as GridOption; + pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; + const gridOptionsMock = { enablePagination: false, enableCheckboxSelector: false, ...pinningMock } as GridOption; jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; filterMock = [{ columnId: 'field1', operator: 'EQ', searchTerms: [] }, { columnId: 'field2', operator: '>=', searchTerms: [2] }] as CurrentFilter[]; @@ -989,7 +1032,7 @@ describe('GridStateService', () => { }); it('should trigger a "onGridStateChanged" event when "onFilterChanged" is triggered', () => { - const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock } as GridState; + const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock, pinning: pinningMock } as GridState; const stateChangeMock = { change: { newValues: filterMock, type: GridStateType.filter }, gridState: gridStateMock } as GridStateChange; const pubSubSpy = jest.spyOn(mockPubSub, 'publish'); @@ -999,8 +1042,7 @@ describe('GridStateService', () => { }); it('should trigger a "onGridStateChanged" event when "onFilterCleared" is triggered', () => { - const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock } as GridState; - + const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock, pinning: pinningMock } as GridState; const stateChangeMock = { change: { newValues: [], type: GridStateType.filter }, gridState: gridStateMock } as GridStateChange; const pubSubSpy = jest.spyOn(mockPubSub, 'publish'); @@ -1010,7 +1052,7 @@ describe('GridStateService', () => { }); it('should trigger a "onGridStateChanged" event when "onSortChanged" is triggered', () => { - const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock } as GridState; + const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock, pinning: pinningMock } as GridState; const stateChangeMock = { change: { newValues: sorterMock, type: GridStateType.sorter }, gridState: gridStateMock } as GridStateChange; const pubSubSpy = jest.spyOn(mockPubSub, 'publish'); @@ -1020,8 +1062,7 @@ describe('GridStateService', () => { }); it('should trigger a "onGridStateChanged" event when "onSortCleared" is triggered', () => { - const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock } as GridState; - + const gridStateMock = { columns: currentColumnsMock, filters: filterMock, sorters: sorterMock, pinning: pinningMock } as GridState; const stateChangeMock = { change: { newValues: [], type: GridStateType.sorter }, gridState: gridStateMock } as GridStateChange; const pubSubSpy = jest.spyOn(mockPubSub, 'publish'); diff --git a/packages/common/src/services/grid.service.ts b/packages/common/src/services/grid.service.ts index 34b0c788f..be7223931 100644 --- a/packages/common/src/services/grid.service.ts +++ b/packages/common/src/services/grid.service.ts @@ -1,6 +1,7 @@ import { CellArgs, Column, + CurrentPinning, SlickDataView, GridOption, GridServiceDeleteOption, @@ -12,7 +13,6 @@ import { SlickNamespace, SlickRowSelectionModel, } from '../interfaces/index'; -import { ExtensionService } from './extension.service'; import { FilterService } from './filter.service'; import { GridStateService } from './gridState.service'; import { PaginationService } from '../services/pagination.service'; @@ -35,7 +35,6 @@ export class GridService { private _rowSelectionPlugin?: SlickRowSelectionModel; constructor( - private extensionService: ExtensionService, private gridStateService: GridStateService, private filterService: FilterService, private pubSubService: PubSubService, @@ -75,6 +74,32 @@ export class GridService { } } + /** Clear all the pinning (frozen) options */ + clearPinning() { + const visibleColumns = [...this.sharedService.visibleColumns]; + this.sharedService.slickGrid.setOptions({ frozenColumn: -1, frozenRow: -1, frozenBottom: false, enableMouseWheelScrollHandler: false }); + + // SlickGrid seems to be somehow resetting the columns to their original positions, + // so let's re-fix them to the position we kept as reference + if (Array.isArray(visibleColumns)) { + this.sharedService.slickGrid.setColumns(visibleColumns); + } + } + + /** + * Set pinning (frozen) grid options + * @param pinningOptions - which pinning/frozen options to modify + * @param shouldAutosizeColumns - defaults to True, should we call an autosizeColumns after the pinning is done? + */ + setPinning(pinningOptions: CurrentPinning, shouldAutosizeColumns = true) { + this.sharedService.slickGrid.setOptions(pinningOptions); + this.sharedService.gridOptions = { ...this.sharedService.gridOptions, ...pinningOptions }; + + if (shouldAutosizeColumns) { + this.sharedService.slickGrid.autosizeColumns(); + } + } + /** * Get all column set in the grid, that is all visible/hidden columns * and also include any extra columns used by some plugins (like Row Selection, Row Detail, ...) @@ -368,7 +393,7 @@ export class GridService { resetGrid(columnDefinitions?: Column[]) { // reset columns to original states & refresh the grid if (this._grid && this._dataView) { - const originalColumns = this.extensionService.getAllColumns(); + const originalColumns = this.sharedService.allColumns || []; if (Array.isArray(originalColumns) && originalColumns.length > 0) { // set the grid columns to it's original column definitions @@ -380,6 +405,9 @@ export class GridService { } } + // clear any Pinning/Frozen columns/rows + this.clearPinning(); + if (this.filterService && this.filterService.clearFilters) { this.filterService.clearFilters(); } diff --git a/packages/common/src/services/gridState.service.ts b/packages/common/src/services/gridState.service.ts index 00c95b7d8..1b634b596 100644 --- a/packages/common/src/services/gridState.service.ts +++ b/packages/common/src/services/gridState.service.ts @@ -131,10 +131,12 @@ export class GridStateService { * @return grid state */ getCurrentGridState(args?: { requestRefreshRowFilteredRow?: boolean }): GridState { + const { frozenColumn, frozenRow, frozenBottom } = this.sharedService.gridOptions; const gridState: GridState = { columns: this.getCurrentColumns(), filters: this.getCurrentFilters(), sorters: this.getCurrentSorters(), + pinning: { frozenColumn, frozenRow, frozenBottom }, }; const currentPagination = this.getCurrentPagination(); @@ -394,6 +396,7 @@ export class GridStateService { // subscribe to Column Resize & Reordering this.bindSlickGridColumnChangeEventToGridStateChange('onColumnsReordered', grid); this.bindSlickGridColumnChangeEventToGridStateChange('onColumnsResized', grid); + this.bindSlickGridOnSetOptionsEventToGridStateChange(grid); // subscribe to Row Selection changes (when enabled) if (this._gridOptions.enableRowSelection || this._gridOptions.enableCheckboxSelector) { @@ -418,7 +421,7 @@ export class GridStateService { * @param extension name * @param event name */ - bindExtensionAddonEventToGridStateChange(extensionName: ExtensionName, eventName: string) { + private bindExtensionAddonEventToGridStateChange(extensionName: ExtensionName, eventName: string) { const extension = this.extensionService && this.extensionService.getExtensionByName && this.extensionService.getExtensionByName(extensionName); const slickEvent = extension && extension.instance && extension.instance[eventName]; @@ -433,10 +436,10 @@ export class GridStateService { /** * Bind a Grid Event (of Column changes) to a Grid State change event - * @param event name - * @param grid + * @param event - event name + * @param grid - SlickGrid object */ - bindSlickGridColumnChangeEventToGridStateChange(eventName: string, grid: SlickGrid) { + private bindSlickGridColumnChangeEventToGridStateChange(eventName: string, grid: SlickGrid) { const slickGridEvent = (grid as any)?.[eventName]; if (slickGridEvent && typeof slickGridEvent.subscribe === 'function') { @@ -448,6 +451,24 @@ export class GridStateService { } } + /** + * Bind a Grid Event (of grid option changes) to a Grid State change event, if we detect that any of the pinning (frozen) options changes then we'll trigger a Grid State change + * @param grid - SlickGrid object + */ + private bindSlickGridOnSetOptionsEventToGridStateChange(grid: SlickGrid) { + const onSetOptionsHandler = grid.onSetOptions; + (this._eventHandler as SlickEventHandler>).subscribe(onSetOptionsHandler, (_e, args) => { + const { frozenBottom: frozenBottomBefore, frozenColumn: frozenColumnBefore, frozenRow: frozenRowBefore } = args.optionsBefore; + const { frozenBottom: frozenBottomAfter, frozenColumn: frozenColumnAfter, frozenRow: frozenRowAfter } = args.optionsAfter; + + if ((frozenBottomBefore !== frozenBottomAfter) || (frozenColumnBefore !== frozenColumnAfter) || (frozenRowBefore !== frozenRowAfter)) { + const newValues = { frozenBottom: frozenBottomAfter, frozenColumn: frozenColumnAfter, frozenRow: frozenRowAfter }; + const currentGridState = this.getCurrentGridState(); + this.pubSubService.publish('onGridStateChanged', { change: { newValues, type: GridStateType.pinning }, gridState: currentGridState }); + } + }); + } + /** * Bind a Grid Event of Row Selection change to a Grid State change event * For the row selection, we can't just use the getSelectedRows() since this will only return the visible rows shown in the UI which is not enough. diff --git a/packages/common/src/styles/material-svg-icons.scss b/packages/common/src/styles/material-svg-icons.scss index 826b6da8b..f8de8f041 100644 --- a/packages/common/src/styles/material-svg-icons.scss +++ b/packages/common/src/styles/material-svg-icons.scss @@ -221,6 +221,11 @@ $icon-height: $icon-width; "M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,5V19H5V5H19Z", encodecolor($icon-color), $icon-height, $icon-width, inline-block); +@include loadsvg( + ".mdi.mdi-checkbox-marked-circle-outline", + "M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z", + encodecolor($icon-color), $icon-height, $icon-width, inline-block); + @include loadsvg( ".mdi.mdi-check-box-outline", "M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M19,5V19H5V5H19M10,17L6,13L7.41,11.58L10,14.17L16.59,7.58L18,9", @@ -236,6 +241,11 @@ $icon-height: $icon-width; "M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z", encodecolor($icon-color), $icon-height, $icon-width, inline-block); +@include loadsvg( + ".mdi.mdi-check-circle-outline", + "M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z", + encodecolor($icon-color), $icon-height, $icon-width, inline-block); + @include loadsvg( ".mdi.mdi-check-outline", "M19.78,2.2L24,6.42L8.44,22L0,13.55L4.22,9.33L8.44,13.55L19.78,2.2M19.78,5L8.44,16.36L4.22,12.19L2.81,13.55L8.44,19.17L21.19,6.42L19.78,5Z", @@ -271,6 +281,11 @@ $icon-height: $icon-width; "M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12M6,10L12,16L18,10L16.6,8.6L12,13.2L7.4,8.6L6,10Z", encodecolor($icon-color), $icon-height, $icon-width, inline-block); +@include loadsvg( + ".mdi.mdi-clipboard-check", + "M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z", + encodecolor($icon-color), $icon-height, $icon-width, inline-block); + @include loadsvg( ".mdi.mdi-clipboard-check-outline", "M19,3H14.82C14.4,1.84 13.3,1 12,1C10.7,1 9.6,1.84 9.18,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M12,3A1,1 0 0,1 13,4A1,1 0 0,1 12,5A1,1 0 0,1 11,4A1,1 0 0,1 12,3M7,7H17V5H19V19H5V5H7V7M7.5,13.5L9,12L11,14L15.5,9.5L17,11L11,17L7.5,13.5Z", diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts index 3fc2fb961..062458a63 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts @@ -37,6 +37,7 @@ import { TreeDataService, TranslaterService, SlickEditorLock, + CurrentPinning, } from '@slickgrid-universal/common'; import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, GraphqlServiceOption } from '@slickgrid-universal/graphql'; import { SlickCompositeEditorComponent } from '@slickgrid-universal/composite-editor-component'; @@ -1088,6 +1089,17 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () expect(backendSpy).toHaveBeenCalledWith(mockColumnFilter as unknown as CurrentFilter[], false); }); + it('should override frozen grid options when "pinning" is defined in the "presets" property', () => { + const pinningMock = { frozenBottom: false, frozenColumn: -1, frozenRow: -1 } as CurrentPinning; + const gridOptionSetterSpy = jest.spyOn(component, 'gridOptions', 'set'); + + component.gridOptions.presets = { pinning: pinningMock }; + component.initialization(divContainer, slickEventHandler); + + expect(gridOptionSetterSpy).toHaveBeenCalledWith({ ...component.gridOptions, ...pinningMock }); + expect(component.gridOptions).toEqual({ ...component.gridOptions, ...pinningMock }); + }); + it('should call the "updateFilters" method when filters are defined in the "presets" property', () => { const spy = jest.spyOn(mockGraphqlService, 'updateFilters'); const mockFilters = [{ columnId: 'company', searchTerms: ['xyz'], operator: 'IN' }] as CurrentFilter[]; diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts index 4bf25cd3f..59c9aa849 100644 --- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts @@ -361,8 +361,7 @@ export class SlickVanillaGridBundle { ); this.gridStateService = services?.gridStateService ?? new GridStateService(this.extensionService, this.filterService, this._eventPubSubService, this.sharedService, this.sortService); - this.gridService = services?.gridService ?? new GridService(this.extensionService, this.gridStateService, this.filterService, this._eventPubSubService, this.paginationService, this.sharedService, this.sortService); - + this.gridService = services?.gridService ?? new GridService(this.gridStateService, this.filterService, this._eventPubSubService, this.paginationService, this.sharedService, this.sortService); this.groupingService = services?.groupingAndColspanService ?? new GroupingAndColspanService(this.extensionUtility, this.extensionService, this._eventPubSubService); if (hierarchicalDataset) { @@ -511,6 +510,11 @@ export class SlickVanillaGridBundle { this.sharedService.visibleColumns = this._columnDefinitions; this.extensionService.createExtensionsBeforeGridCreation(this._columnDefinitions, this._gridOptions); + // if user entered some Pinning/Frozen "presets", we need to apply them in the grid options + if (this.gridOptions.presets?.pinning) { + this.gridOptions = { ...this.gridOptions, ...this.gridOptions.presets.pinning }; + } + this.slickGrid = new Slick.Grid(gridContainerElm, this.dataView, this._columnDefinitions, this._gridOptions); this.sharedService.dataView = this.dataView; this.sharedService.slickGrid = this.slickGrid; diff --git a/test/cypress/integration/example04.spec.js b/test/cypress/integration/example04.spec.js index a298591c3..21b530d54 100644 --- a/test/cypress/integration/example04.spec.js +++ b/test/cypress/integration/example04.spec.js @@ -1,7 +1,7 @@ /// describe('Example 04 - Frozen Grid', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after clear frozen columns + // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const fullTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; @@ -201,16 +201,16 @@ describe('Example 04 - Frozen Grid', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the Grid Menu command "Clear Frozen Columns" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { + it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns/rows', () => { cy.get('.grid4') .find('button.slick-gridmenu-button') .click({ force: true }); - cy.contains('Clear Frozen Columns') + cy.contains('Unfreeze Columns/Rows') .click({ force: true }); - cy.get('[style="top:0px"]').should('have.length', 1 * 2); - cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 9 * 2); + cy.get('[style="top:0px"]').should('have.length', 1); + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 9); cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(1)').should('contain', 'Task 0'); diff --git a/test/cypress/integration/example08.spec.js b/test/cypress/integration/example08.spec.js index 7520482d0..c4a5eaf9e 100644 --- a/test/cypress/integration/example08.spec.js +++ b/test/cypress/integration/example08.spec.js @@ -5,7 +5,7 @@ function removeExtraSpaces(textS) { } describe('Example 08 - Column Span & Header Grouping', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after clear frozen columns + // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const fullPreTitles = ['', 'Common Factor', 'Period', 'Analysis']; const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', '% Complete', 'Effort Driven']; const GRID_ROW_HEIGHT = 33; @@ -106,12 +106,12 @@ describe('Example 08 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the Grid Menu command "Clear Frozen Columns" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { + it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { cy.get('.grid2') .find('button.slick-gridmenu-button') .click({ force: true }); - cy.contains('Clear Frozen Columns') + cy.contains('Unfreeze Columns/Rows') .click({ force: true }); cy.get('.grid2').find(`[style="top:${GRID_ROW_HEIGHT * 0}px"]`).should('have.length', 1); diff --git a/test/cypress/integration/example11.spec.js b/test/cypress/integration/example11.spec.js index 78a03e679..5cbab8f26 100644 --- a/test/cypress/integration/example11.spec.js +++ b/test/cypress/integration/example11.spec.js @@ -561,4 +561,174 @@ describe('Example 11 - Batch Editing', () => { cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); cy.get(`[style="top:${GRID_ROW_HEIGHT * 4}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); }); + + it('should be able to filter "Duration" with greater than symbol ">8"', () => { + cy.get('input.search-filter.filter-duration').type('>8'); + + cy.get('.grid11') + .find('.slick-cell:nth(2)') + .each(($cell, index) => { + if (index < 10) { + const [number, dayString] = $cell.text().split(' '); + expect(+number).to.be.greaterThan(8); + } + }); + }); + + it('should be able to filter "Title" with Task finishing by 5', () => { + cy.get('input.search-filter.filter-title').type('*5'); + + cy.get('.grid11') + .find('.slick-cell:nth(1)') + .each(($cell, index) => { + if (index < 10) { + const [taskString, taskNumber] = $cell.text().split(' '); + expect(taskNumber).to.be.eq('5') + } + }); + }); + + it('should be able to freeze "Duration" column', () => { + cy.get('.grid11') + .find('.slick-header-columns') + .find('.slick-header-column:nth(2)') + .trigger('mouseover') + .children('.slick-header-menubutton') + .click(); + + cy.get('.slick-header-menu') + .should('be.visible') + .children('.slick-header-menuitem:nth-child(1)') + .children('.slick-header-menucontent') + .should('contain', 'Freeze Column') + .click(); + }); + + it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 8 columns on the right', () => { + cy.get('[style="top:0px"]').should('have.length', 2); + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 3); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').contains(/\$[0-9\.]*/); + }); + + it('should create a new View with current pinning & filters', () => { + const filterName = "Custom View Test" + const winPromptStub = () => filterName; + + cy.window().then(win => { + cy.stub(win, 'prompt').callsFake(winPromptStub).as('winPromptStubReturnNonNull') + }); + + cy.get('.action.dropdown') + .click(); + + cy.get('.action.dropdown .dropdown-item') + .contains('Create New View') + .click(); + + cy.get('@winPromptStubReturnNonNull').should('be.calledOnce') + .and('be.calledWith', 'Please provide a name for the new View.'); + + cy.should(() => { + const savedDefinedFilters = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)); + expect(Object.keys(savedDefinedFilters)).to.have.lengthOf(3); + }); + + cy.get('.selected-view').should('have.value', 'CustomViewTest'); + }); + + it('should change pre-defined view to "Tasks Finished in Previous Years" and expect different filters and no pinning', () => { + const expectedTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Start', 'Finish', 'Completed', 'Action']; + cy.get('.selected-view').select('previousYears'); + cy.get('.selected-view').should('have.value', 'previousYears'); + + cy.get('.grid11') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); + + cy.get('.rangeInput_percentComplete') + .invoke('val') + .then(text => expect(text).to.eq('50')); + + cy.get('.search-filter.filter-finish .operator .form-control') + .should('have.value', '<='); + + cy.get('.search-filter.filter-finish .flatpickr-input') + .invoke('val') + .then(text => expect(text).to.eq(`${currentYear}-01-01`)); + + cy.get('[style="top:0px"]').should('have.length', 1); + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 9); + }); + + it('should change pre-defined view back to the Custom View Test', () => { + const expectedTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Start', 'Finish', 'Completed', 'Product', 'Country of Origin', 'Action']; + cy.get('.selected-view').select('CustomViewTest'); + cy.get('.selected-view').should('have.value', 'CustomViewTest'); + + cy.get('.grid11') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); + }); + + it('should have back the frozen columns from CustomViewTest on the right side of the "Duration" column', () => { + cy.get('[style="top:0px"]').should('have.length', 2); + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 3); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').contains(/\$[0-9\.]*/); + }); + + it('should have the same 3 filters defined in the CustomViewTest', () => { + cy.get('input.search-filter.filter-title').invoke('val').then(text => expect(text).to.eq('*5')); + cy.get('input.search-filter.filter-duration').invoke('val').then(text => expect(text).to.eq('>8')); + + cy.get('.grid11') + .find('.slick-cell:nth(1)') + .each(($cell, index) => { + if (index < 10) { + const [taskString, taskNumber] = $cell.text().split(' '); + expect(taskNumber).to.be.eq('5') + } + }); + + cy.get('.grid11') + .find('.slick-cell:nth(2)') + .each(($cell, index) => { + if (index < 10) { + const [number, dayString] = $cell.text().split(' '); + expect(+number).to.be.greaterThan(8); + } + }); + + cy.get('input.search-filter.filter-countryOfOrigin').invoke('val').then(text => expect(text).to.eq('b..e')); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 0}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 2}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 3}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); + cy.get(`[style="top:${GRID_ROW_HEIGHT * 4}px"] > .slick-cell:nth(9)`).should('contain', 'Canada'); + }); + + it('should clear pinning from Grid Menu & expect to no longer have any columns freezed', () => { + cy.get('.grid11') + .find('button.slick-gridmenu-button') + .click({ force: true }); + + cy.contains('Unfreeze Columns/Rows') + .click({ force: true }); + + cy.get('[style="top:0px"]').should('have.length', 1); + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 11); + }); }); diff --git a/test/translateServiceStub.ts b/test/translateServiceStub.ts index 7aaa202a6..bea98afab 100644 --- a/test/translateServiceStub.ts +++ b/test/translateServiceStub.ts @@ -21,7 +21,7 @@ export class TranslateServiceStub implements TranslaterService { case 'CLEAR_ALL_GROUPING': output = this._locale === 'en' ? 'Clear all Grouping' : 'Supprimer tous les groupes'; break; case 'CLEAR_ALL_FILTERS': output = this._locale === 'en' ? 'Clear all Filters' : 'Supprimer tous les filtres'; break; case 'CLEAR_ALL_SORTING': output = this._locale === 'en' ? 'Clear all Sorting' : 'Supprimer tous les tris'; break; - case 'CLEAR_FROZEN_COLUMNS': output = this._locale === 'en' ? 'Clear Frozen Columns' : 'Libérer les colonnes gelées'; break; + case 'CLEAR_PINNING': output = this._locale === 'en' ? 'Unfreeze Columns/Rows' : 'Dégeler les colonnes/rangées'; break; case 'CLONE': output = this._locale === 'en' ? 'Clone' : 'Cloner'; break; case 'COLUMNS': output = this._locale === 'en' ? 'Columns' : 'Colonnes'; break; case 'COMMANDS': output = this._locale === 'en' ? 'Commands' : 'Commandes'; break;