Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit e10f49f

Browse files
authored
Merge pull request #241 from ghiscoding/bugfix/backend-clear-filter
fix(backend): clear empty filter by header menu not stopping spinner
2 parents 1924368 + 1485f2b commit e10f49f

File tree

7 files changed

+89
-9
lines changed

7 files changed

+89
-9
lines changed

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
{
3535
"label": "Cypress Open Tool",
3636
"type": "shell",
37-
"command": "yarn run cypress open",
37+
"command": "yarn run cypress:open",
3838
"problemMatcher": []
3939
},
4040
{

src/app/modules/angular-slickgrid/services/__tests__/filter.service.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ describe('FilterService', () => {
363363
describe('clearFilter methods on backend grid', () => {
364364
let mockColumn1: Column;
365365
let mockColumn2: Column;
366+
let mockColumn3: Column;
366367

367368
beforeEach(() => {
368369
gridOptionMock.backendServiceApi = {
@@ -372,13 +373,16 @@ describe('FilterService', () => {
372373
};
373374
mockColumn1 = { id: 'firstName', field: 'firstName', filterable: true } as Column;
374375
mockColumn2 = { id: 'lastName', field: 'lastName', filterable: true } as Column;
376+
mockColumn3 = { id: 'age', field: 'age', filterable: true } as Column;
375377
const mockArgs1 = { grid: gridStub, column: mockColumn1, node: document.getElementById(DOM_ELEMENT_ID) };
376378
const mockArgs2 = { grid: gridStub, column: mockColumn2, node: document.getElementById(DOM_ELEMENT_ID) };
379+
const mockArgs3 = { grid: gridStub, column: mockColumn3, node: document.getElementById(DOM_ELEMENT_ID) };
377380

378381
service.init(gridStub);
379382
service.bindBackendOnFilter(gridStub, dataViewStub);
380383
gridStub.onHeaderRowCellRendered.notify(mockArgs1, new Slick.EventData(), gridStub);
381384
gridStub.onHeaderRowCellRendered.notify(mockArgs2, new Slick.EventData(), gridStub);
385+
gridStub.onHeaderRowCellRendered.notify(mockArgs3, new Slick.EventData(), gridStub);
382386
service.getFiltersMetadata()[0].callback(new Event('keyup'), { columnDef: mockColumn1, operator: 'EQ', searchTerms: ['John'], shouldTriggerQuery: true });
383387
service.getFiltersMetadata()[1].callback(new Event('keyup'), { columnDef: mockColumn2, operator: 'NE', searchTerms: ['Doe'], shouldTriggerQuery: true });
384388
});
@@ -402,6 +406,26 @@ describe('FilterService', () => {
402406
expect(service.getColumnFilters()).toEqual({ lastName: filterExpectation });
403407
expect(spyEmitter).toHaveBeenCalledWith('remote');
404408
});
409+
410+
it('should not call "onBackendFilterChange" method when the filter is previously empty', () => {
411+
const filterFirstExpectation = { columnDef: mockColumn1, columnId: 'firstName', operator: 'EQ', searchTerms: ['John'] };
412+
const filterLastExpectation = { columnDef: mockColumn2, columnId: 'lastName', operator: 'NE', searchTerms: ['Doe'] };
413+
const newEvent = new Event('mouseup');
414+
const spyClear = jest.spyOn(service.getFiltersMetadata()[2], 'clear');
415+
const spyFilterChange = jest.spyOn(service, 'onBackendFilterChange');
416+
const spyEmitter = jest.spyOn(service, 'emitFilterChanged');
417+
418+
const filterCountBefore = Object.keys(service.getColumnFilters()).length;
419+
service.clearFilterByColumnId(newEvent, 'age');
420+
const filterCountAfter = Object.keys(service.getColumnFilters()).length;
421+
422+
expect(spyClear).toHaveBeenCalled();
423+
expect(spyFilterChange).not.toHaveBeenCalled();
424+
expect(filterCountBefore).toBe(2);
425+
expect(filterCountAfter).toBe(2);
426+
expect(service.getColumnFilters()).toEqual({ firstName: filterFirstExpectation, lastName: filterLastExpectation });
427+
expect(spyEmitter).toHaveBeenCalledWith('remote');
428+
});
405429
});
406430

407431
describe('clearFilters method', () => {

src/app/modules/angular-slickgrid/services/filter.service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ export class FilterService {
169169
}
170170

171171
clearFilterByColumnId(event: Event, columnId: number | string) {
172+
// get current column filter before clearing, this allow us to know if the filter was empty prior to calling the clear filter
173+
const currentColumnFilters = Object.keys(this._columnFilters) as ColumnFilter[];
174+
let currentColFilter: ColumnFilter;
175+
if (Array.isArray(currentColumnFilters)) {
176+
currentColFilter = currentColumnFilters.find((name) => name === columnId);
177+
}
178+
179+
// find the filter object and call its clear method with true (the argument tells the method it was called by a clear filter)
172180
const colFilter: Filter = this._filtersMetadata.find((filter: Filter) => filter.columnDef.id === columnId);
173181
if (colFilter && colFilter.clear) {
174182
colFilter.clear(true);
@@ -177,10 +185,12 @@ export class FilterService {
177185
let emitter: EmitterType = EmitterType.local;
178186
const isBackendApi = this._gridOptions && this._gridOptions.backendServiceApi || false;
179187

180-
// when using a backend service, we need to manually trigger a filter change
188+
// when using a backend service, we need to manually trigger a filter change but only if the filter was previously filled
181189
if (isBackendApi) {
182190
emitter = EmitterType.remote;
183-
this.onBackendFilterChange(event as KeyboardEvent, { grid: this._grid, columnFilters: this._columnFilters });
191+
if (currentColFilter) {
192+
this.onBackendFilterChange(event as KeyboardEvent, { grid: this._grid, columnFilters: this._columnFilters });
193+
}
184194
}
185195

186196
// emit an event when filter is cleared

src/tsconfig.spec.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"module": "commonjs",
77
"target": "es5",
88
"types": [
9+
"cypress",
910
"jasmine",
1011
"node"
1112
]

test/cypress/integration/example1.spec.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference types="Cypress" />
2-
31
describe('Example 1 - Basic Grids', () => {
42
const titles = ['Title', 'Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];
53

test/cypress/integration/example6.spec.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference types="Cypress" />
2-
31
describe('Example 6 - GraphQL Grid', () => {
42
it('should display Example 6 title', () => {
53
cy.visit(`${Cypress.config('baseExampleUrl')}/gridgraphql`);
@@ -69,6 +67,57 @@ describe('Example 6 - GraphQL Grid', () => {
6967
});
7068
});
7169

70+
it('should clear a single filter, that is not empty, by the header menu and expect query change', () => {
71+
cy.get('#grid6')
72+
.find('.slick-header-column:nth(1)')
73+
.trigger('mouseover')
74+
.children('.slick-header-menubutton')
75+
.should('be.hidden')
76+
.invoke('show')
77+
.click();
78+
79+
cy.get('.slick-header-menu')
80+
.should('be.visible')
81+
.children('.slick-header-menuitem:nth-child(4)')
82+
.children('.slick-header-menucontent')
83+
.should('contain', 'Remove Filter')
84+
.click();
85+
86+
// wait for the query to finish
87+
cy.get('[data-test=status]').should('contain', 'done');
88+
89+
cy.get('[data-test=graphql-query-result]')
90+
.should(($span) => {
91+
const text = $span.text().replace(/\s/g, ''); // remove all white spaces
92+
expect(text).to.eq('query{users(first:30,offset:0,orderBy:[{field:"name",direction:ASC},{field:"company",direction:DESC}],filterBy:[{field:"gender",operator:EQ,value:"male"},{field:"company",operator:IN,value:"xyz"}],locale:"en",userId:123){totalCount,nodes{id,name,gender,company,billing{address{street,zip}}}}}');
93+
});
94+
});
95+
96+
it('should try clearing same filter, which is now empty, by the header menu and expect same query without loading spinner', () => {
97+
cy.get('#grid6')
98+
.find('.slick-header-column:nth(1)')
99+
.trigger('mouseover')
100+
.children('.slick-header-menubutton')
101+
.invoke('show')
102+
.click();
103+
104+
cy.get('.slick-header-menu')
105+
.should('be.visible')
106+
.children('.slick-header-menuitem:nth-child(4)')
107+
.children('.slick-header-menucontent')
108+
.should('contain', 'Remove Filter')
109+
.click();
110+
111+
// wait for the query to finish
112+
cy.get('[data-test=status]').should('contain', 'done');
113+
114+
cy.get('[data-test=graphql-query-result]')
115+
.should(($span) => {
116+
const text = $span.text().replace(/\s/g, ''); // remove all white spaces
117+
expect(text).to.eq('query{users(first:30,offset:0,orderBy:[{field:"name",direction:ASC},{field:"company",direction:DESC}],filterBy:[{field:"gender",operator:EQ,value:"male"},{field:"company",operator:IN,value:"xyz"}],locale:"en",userId:123){totalCount,nodes{id,name,gender,company,billing{address{street,zip}}}}}');
118+
});
119+
});
120+
72121
it('should clear all Filters & Sorts', () => {
73122
cy.contains('Clear all Filter & Sorts').click();
74123

test/cypress/integration/home.spec.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference types="Cypress" />
2-
31
describe('Home Page', () => {
42
it('should display Home Page', () => {
53
cy.visit('http://localhost:4300/home');

0 commit comments

Comments
 (0)