From ae6ece89c967a49b275f2656d75f8bcdf6081766 Mon Sep 17 00:00:00 2001 From: Ghislain Beaulac Date: Tue, 13 Oct 2020 15:14:23 -0400 Subject: [PATCH 1/2] fix(core): hide Grid Menu Filter/Sort cmd when disabling functionality --- README.md | 2 +- src/app/examples/grid-graphql.component.ts | 2 +- .../services/__tests__/filter.service.spec.ts | 42 ++++++- .../services/__tests__/sort.service.spec.ts | 32 ++++- .../services/filter.service.ts | 42 +++++++ .../services/sort.service.ts | 22 +++- test/cypress/integration/example17.spec.js | 114 +++++++++++++++++- 7 files changed, 240 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index acb1816da..3889be877 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ module.exports = { ``` ### Fully Tested with [Jest](https://jestjs.io/) (Unit Tests) - [Cypress](https://www.cypress.io/) (E2E Tests) -Angular-Slickgrid reached **100%** Unit Test Coverage, we are talking about ~10,000 lines of code (+2,600 unit tests) that are now fully tested with [Jest](https://jestjs.io/). There are also over 300 Cypress E2E tests to cover most UI functionalities. +Angular-Slickgrid reached **100%** Unit Test Coverage, we are talking about +10,000 lines of code (+2,700 unit tests) that are now fully tested with [Jest](https://jestjs.io/). There are also over +350 Cypress E2E tests to cover most UI functionalities on nearly all Examples of Angular-Slickgrid. ## Installation Refer to the **[Wiki - HOWTO Step by Step](https://github.com/ghiscoding/angular-slickgrid/wiki/HOWTO---Step-by-Step)** and/or clone the [Angular-Slickgrid Demos](https://github.com/ghiscoding/angular-slickgrid-demos) repository. Please don't open any issue unless you have followed these steps (from the Wiki), and if any of the steps are incorrect or confusing, then please let me know. diff --git a/src/app/examples/grid-graphql.component.ts b/src/app/examples/grid-graphql.component.ts index 404b1d128..2b4658c9a 100644 --- a/src/app/examples/grid-graphql.component.ts +++ b/src/app/examples/grid-graphql.component.ts @@ -19,7 +19,7 @@ import { import * as moment from 'moment-mini'; import { Subscription } from 'rxjs'; -const defaultPageSize = 10; +const defaultPageSize = 20; const GRAPHQL_QUERY_DATASET_NAME = 'users'; const LOCAL_STORAGE_KEY = 'gridStateGraphql'; diff --git a/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts index 3f68d871b..b129a2587 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts @@ -9,8 +9,10 @@ import { BackendService, Column, CurrentFilter, + GridMenuItem, GridOption, FieldType, + MenuCommandItem, SlickEventHandler, } from '../../models'; import { Filters } from '../../filters'; @@ -35,6 +37,22 @@ const gridOptionMock = { preProcess: jest.fn(), process: jest.fn(), postProcess: jest.fn(), + }, + gridMenu: { + customItems: [{ + command: 'clear-filter', + disabled: false, + iconCssClass: 'fa fa-filter mdi mdi-filter-remove-outline', + positionOrder: 50, + title: 'Clear all Filters' + }, { + command: 'toggle-filter', + disabled: false, + hidden: true, + iconCssClass: 'fa fa-random mdi mdi-flip-vertical', + positionOrder: 52, + title: 'Toggle Filter Row' + }] } } as GridOption; @@ -1125,15 +1143,25 @@ describe('FilterService', () => { }); it('should disable the Filter Functionality from the Grid Options & toggle the Header Filter row', () => { - const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }]; + const mockColumns = [ + { id: 'field1', field: 'field1', width: 100, header: { menu: { items: [{ command: 'clear-filter' }] } } }, + { id: 'field2', field: 'field2', width: 100, header: { menu: { items: [{ command: 'clear-filter' }] } } } + ]; const setOptionSpy = jest.spyOn(gridStub, 'setOptions'); const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility'); const setColsSpy = jest.spyOn(gridStub, 'setColumns'); + jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns); jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns) service.init(gridStub); service.disableFilterFunctionality(); + mockColumns.forEach(col => col.header.menu.items.forEach(item => { + expect((item as MenuCommandItem).hidden).toBeTruthy(); + })); + gridOptionMock.gridMenu.customItems.forEach(item => { + expect((item as GridMenuItem).hidden).toBeTruthy(); + }); expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: false }, false, true); expect(setHeaderSpy).toHaveBeenCalledWith(false); expect(setColsSpy).toHaveBeenCalledWith(mockColumns); @@ -1143,15 +1171,25 @@ describe('FilterService', () => { gridOptionMock.enableFiltering = false; gridOptionMock.showHeaderRow = false; - const mockColumns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }]; + const mockColumns = [ + { id: 'field1', field: 'field1', width: 100, header: { menu: { items: [{ command: 'clear-filter' }] } } }, + { id: 'field2', field: 'field2', width: 100, header: { menu: { items: [{ command: 'clear-filter' }] } } } + ]; const setOptionSpy = jest.spyOn(gridStub, 'setOptions'); const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility'); const setColsSpy = jest.spyOn(gridStub, 'setColumns'); + jest.spyOn(gridStub, 'getColumns').mockReturnValue(mockColumns); jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(mockColumns) service.init(gridStub); service.disableFilterFunctionality(false); + mockColumns.forEach(col => col.header.menu.items.forEach(item => { + expect((item as MenuCommandItem).hidden).toBeFalsy(); + })); + gridOptionMock.gridMenu.customItems.forEach(item => { + expect((item as GridMenuItem).hidden).toBeFalsy(); + }); expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: true }, false, true); expect(setHeaderSpy).toHaveBeenCalledWith(true); expect(setColsSpy).toHaveBeenCalledWith(mockColumns); diff --git a/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts index b998d6dd7..05c1549d6 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/sort.service.spec.ts @@ -7,6 +7,7 @@ import { CurrentSorter, EmitterType, FieldType, + GridMenuItem, GridOption, MenuCommandItem, SlickEventHandler, @@ -29,6 +30,16 @@ const gridOptionMock = { preProcess: jest.fn(), process: jest.fn(), postProcess: jest.fn(), + }, + gridMenu: { + customItems: [{ + command: 'clear-sorting', + disabled: false, + hidden: true, + iconCssClass: 'fa fa-unsorted mdi mdi-swap-vertical', + positionOrder: 51, + title: 'Clear all Sorting' + }] } } as GridOption; @@ -563,11 +574,14 @@ describe('SortService', () => { expect(clearSpy).toHaveBeenCalled(); expect(unsubscribeSpy).toHaveBeenCalled(); mockColumns.forEach(col => { - expect(col.sortable).toBeFalse(); + expect(col.sortable).toBeFalsy(); }); mockColumns.forEach(col => col.header.menu.items.forEach(item => { - expect((item as MenuCommandItem).hidden).toBeTrue(); + expect((item as MenuCommandItem).hidden).toBeTruthy(); })); + gridOptionMock.gridMenu.customItems.forEach(item => { + expect((item as GridMenuItem).hidden).toBeTruthy(); + }); }); it('should disable Sort functionality when passing True as 1st argument and False as 2nd argument SHOULD NOT trigger an event', () => { @@ -581,11 +595,14 @@ describe('SortService', () => { expect(clearSpy).not.toHaveBeenCalled(); expect(unsubscribeSpy).toHaveBeenCalled(); mockColumns.forEach(col => { - expect(col.sortable).toBeFalse(); + expect(col.sortable).toBeFalsy(); }); mockColumns.forEach(col => col.header.menu.items.forEach(item => { - expect((item as MenuCommandItem).hidden).toBeTrue(); + expect((item as MenuCommandItem).hidden).toBeTruthy(); })); + gridOptionMock.gridMenu.customItems.forEach(item => { + expect((item as GridMenuItem).hidden).toBeTruthy(); + }); }); it('should enable Sort functionality when passing False as 1st argument', (done) => { @@ -597,11 +614,14 @@ describe('SortService', () => { gridStub.onSort.notify({ multiColumnSort: true, sortCols: [], grid: gridStub }, new Slick.EventData(), gridStub); mockColumns.forEach(col => { - expect(col.sortable).toBeTrue(); + expect(col.sortable).toBeTruthy(); }); mockColumns.forEach(col => col.header.menu.items.forEach(item => { - expect((item as MenuCommandItem).hidden).toBeFalse(); + expect((item as MenuCommandItem).hidden).toBeFalsy(); })); + gridOptionMock.gridMenu.customItems.forEach(item => { + expect((item as GridMenuItem).hidden).toBeFalsy(); + }); setTimeout(() => { expect(handleSpy).toHaveBeenCalled(); diff --git a/src/app/modules/angular-slickgrid/services/filter.service.ts b/src/app/modules/angular-slickgrid/services/filter.service.ts index 3a2b5aa8c..b3d7bf287 100644 --- a/src/app/modules/angular-slickgrid/services/filter.service.ts +++ b/src/app/modules/angular-slickgrid/services/filter.service.ts @@ -651,8 +651,11 @@ export class FilterService { if (clearFiltersWhenDisabled && isFilterDisabled) { this.clearFilters(); } + this.disableAllFilteringCommands(isFilterDisabled); this._grid.setOptions({ enableFiltering: newShowFilterFlag }, false, true); this._grid.setHeaderRowVisibility(newShowFilterFlag); + this._gridOptions.enableFiltering = !isFilterDisabled; + this.sharedService.gridOptions = this._gridOptions; // when displaying header row, we'll call "setColumns" which in terms will recreate the header row filters this._grid.setColumns(this.sharedService.columnDefinitions); @@ -859,6 +862,45 @@ export class FilterService { } } + /** + * Loop through all column definitions and do the following thing + * 1. loop through each Header Menu commands and change the "hidden" commands to show/hide depending if it's enabled/disabled + * Also note that we aren't deleting any properties, we just toggle their flags so that we can reloop through at later point in time. + * (if we previously deleted these properties we wouldn't be able to change them back since these properties wouldn't exist anymore, hence why we just hide the commands) + * @param {boolean} isDisabling - are we disabling the filter functionality? Defaults to true + */ + private disableAllFilteringCommands(isDisabling = true): Column[] { + const columnDefinitions = this._grid.getColumns(); + + // loop through column definition to hide/show header menu commands + columnDefinitions.forEach((col) => { + if (col && col.header && col.header.menu) { + col.header.menu.items.forEach(menuItem => { + if (menuItem && typeof menuItem !== 'string') { + const menuCommand = menuItem.command; + if (menuCommand === 'clear-filter') { + menuItem.hidden = isDisabling; + } + } + }); + } + }); + + // loop through column definition to hide/show grid menu commands + if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.customItems) { + this._gridOptions.gridMenu.customItems.forEach((menuItem) => { + if (menuItem && typeof menuItem !== 'string') { + const menuCommand = menuItem.command; + if (menuCommand === 'clear-filter' || menuCommand === 'toggle-filter') { + menuItem.hidden = isDisabling; + } + } + }); + } + + return columnDefinitions; + } + private updateColumnFilters(searchTerms: SearchTerm[] | undefined, columnDef: any, operator?: OperatorType | OperatorString) { if (searchTerms && columnDef) { this._columnFilters[columnDef.id] = { diff --git a/src/app/modules/angular-slickgrid/services/sort.service.ts b/src/app/modules/angular-slickgrid/services/sort.service.ts index 433c1a912..e1a78c041 100644 --- a/src/app/modules/angular-slickgrid/services/sort.service.ts +++ b/src/app/modules/angular-slickgrid/services/sort.service.ts @@ -206,13 +206,14 @@ export class SortService { this.clearSorting(); } this._eventHandler.unsubscribeAll(); - updatedColumnDefinitions = this.disableSortingOnAllColumns(true); + updatedColumnDefinitions = this.disableAllSortingCommands(true); } else { - updatedColumnDefinitions = this.disableSortingOnAllColumns(false); + updatedColumnDefinitions = this.disableAllSortingCommands(false); const onSortHandler = this._grid.onSort; this._eventHandler.subscribe(onSortHandler, (e: Event, args: any) => this.handleLocalOnSort(e, args)); } this._grid.setOptions({ enableSorting: this._gridOptions.enableSorting }, false, true); + this.sharedService.gridOptions = this._gridOptions; // reset columns so that it recreate the column headers and remove/add the sort icon hints // basically without this, the sort icon hints were still showing up even after disabling the Sorting @@ -527,14 +528,15 @@ export class SortService { /** * Loop through all column definitions and do the following 2 things * 1. disable/enable the "sortable" property of each column - * 2. loop through each Header Menu commands and change the command "hidden" property to enable/disable + * 2. loop through each Header Menu commands and change the "hidden" commands to show/hide depending if it's enabled/disabled * Also note that we aren't deleting any properties, we just toggle their flags so that we can reloop through at later point in time. * (if we previously deleted these properties we wouldn't be able to change them back since these properties wouldn't exist anymore, hence why we just hide the commands) * @param {boolean} isDisabling - are we disabling the sort functionality? Defaults to true */ - private disableSortingOnAllColumns(isDisabling = true): Column[] { + private disableAllSortingCommands(isDisabling = true): Column[] { const columnDefinitions = this._grid.getColumns(); + // loop through column definition to hide/show header menu commands columnDefinitions.forEach((col) => { if (typeof col.sortable !== undefined) { col.sortable = !isDisabling; @@ -551,6 +553,18 @@ export class SortService { } }); + // loop through column definition to hide/show grid menu commands + if (this._gridOptions && this._gridOptions.gridMenu && this._gridOptions.gridMenu.customItems) { + this._gridOptions.gridMenu.customItems.forEach((menuItem) => { + if (menuItem && typeof menuItem !== 'string') { + const menuCommand = menuItem.command; + if (menuCommand === 'clear-sorting') { + menuItem.hidden = isDisabling; + } + } + }); + } + return columnDefinitions; } } diff --git a/test/cypress/integration/example17.spec.js b/test/cypress/integration/example17.spec.js index 6134e3a31..1ddc0b059 100644 --- a/test/cypress/integration/example17.spec.js +++ b/test/cypress/integration/example17.spec.js @@ -128,6 +128,27 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { cy.get('[data-test="toggle-filtering-btn"]').click(); // show it back }); + it('should expect "Clear all Filters" command to be hidden in the Grid Menu', () => { + const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; + + cy.get('#grid17') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get('.slick-gridmenu-custom') + .find('.slick-gridmenu-item') + .each(($child, index) => { + const commandTitle = $child.text(); + expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]); + + // expect all Sorting commands to be hidden + if (commandTitle === 'Clear all Filters' || commandTitle === 'Toggle Filter Row') { + expect($child).to.be.visible; + } + }); + }); + it('should be able to toggle Filters functionality', () => { const expectedTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Title']; @@ -150,7 +171,7 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); }); - it('should be able to toggle Sorting functionality (disable) and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => { + it('should be able to toggle Sorting functionality (disable) and expect all header menu Sorting commands to be hidden and also not show Sort hint while hovering a column', () => { const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column']; cy.get('.slick-sort-indicator').should('have.length.greaterThan', 0); // sort icon hints @@ -178,7 +199,28 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { }); }); - it('should be able to toggle Sorting functionality (re-enable) and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => { + it('should expect "Clear Sorting" command to be hidden in the Grid Menu', () => { + const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; + + cy.get('#grid17') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get('.slick-gridmenu-custom') + .find('.slick-gridmenu-item') + .each(($child, index) => { + const commandTitle = $child.text(); + expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]); + + // expect all Sorting commands to be hidden + if (commandTitle === 'Clear all Sorting') { + expect($child).not.to.be.visible; + } + }); + }); + + it('should be able to toggle Sorting functionality (re-enable) and expect all Sorting header menu commands to be hidden and also not show Sort hint while hovering a column', () => { const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column']; cy.get('.slick-sort-indicator').should('have.length', 0); // sort icon hints @@ -202,6 +244,27 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { }); }); + it('should expect "Clear Sorting" command to be hidden in the Grid Menu', () => { + const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; + + cy.get('#grid17') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get('.slick-gridmenu-custom') + .find('.slick-gridmenu-item') + .each(($child, index) => { + const commandTitle = $child.text(); + expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]); + + // expect all Sorting commands to be hidden + if (commandTitle === 'Clear all Sorting') { + expect($child).to.be.visible; + } + }); + }); + it('should be able to click disable Sorting functionality button and expect all Sorting commands to be hidden and also not show Sort hint while hovering a column', () => { const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column']; @@ -230,6 +293,53 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { }); }); + it('should be able to click disable Filter functionality button and expect all Filter commands to be hidden and also not show Sort hint while hovering a column', () => { + const expectedFullHeaderMenuCommands = ['Sort Ascending', 'Sort Descending', '', 'Remove Filter', 'Remove Sort', 'Hide Column']; + + cy.get('[data-test="disable-filters-btn"]').click().click(); // even clicking twice should have same result + + cy.get('#grid17') + .find('.slick-header-column:nth(5)') + .trigger('mouseover') + .children('.slick-header-menubutton') + .should('be.hidden') + .invoke('show') + .click(); + + cy.get('.slick-header-menu') + .children() + .each(($child, index) => { + const commandTitle = $child.text(); + expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]); + + // expect all Sorting commands to be hidden + if (commandTitle === 'Remove Filter') { + expect($child).not.to.be.visible; + } + }); + }); + + it('should expect "Clear all Filters" command to be hidden in the Grid Menu', () => { + const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; + + cy.get('#grid17') + .find('button.slick-gridmenu-button') + .trigger('click') + .click(); + + cy.get('.slick-gridmenu-custom') + .find('.slick-gridmenu-item') + .each(($child, index) => { + const commandTitle = $child.text(); + expect(commandTitle).to.eq(expectedFullHeaderMenuCommands[index]); + + // expect all Sorting commands to be hidden + if (commandTitle === 'Clear all Filters' || commandTitle === 'Toggle Filter Row') { + expect($child).not.to.be.visible; + } + }); + }); + it('should open Column Picker and show the "Duration" column back to visible and expect it to have kept its position after toggling filter/sorting', () => { // first 2 cols are hidden but they do count as li item const expectedFullPickerTitles = ['', '', 'Title', '% Complete', 'Start', 'Finish', 'Duration', 'Completed']; From dd06615ab7bd3458a1d40be88a3b8efb1fc139f1 Mon Sep 17 00:00:00 2001 From: Ghislain Beaulac Date: Tue, 13 Oct 2020 15:32:47 -0400 Subject: [PATCH 2/2] refactor(tests): fix Cypress failing test --- test/cypress/integration/example17.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cypress/integration/example17.spec.js b/test/cypress/integration/example17.spec.js index 1ddc0b059..acebaf077 100644 --- a/test/cypress/integration/example17.spec.js +++ b/test/cypress/integration/example17.spec.js @@ -134,7 +134,7 @@ describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { cy.get('#grid17') .find('button.slick-gridmenu-button') .trigger('click') - .click(); + .click({ force: true }); cy.get('.slick-gridmenu-custom') .find('.slick-gridmenu-item')