diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example09.html b/examples/webpack-demo-vanilla-bundle/src/examples/example09.html
index 29df3e9c7..607c10303 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example09.html
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example09.html
@@ -9,6 +9,11 @@
+
+ NOTE: The last column (filter & sort) will always throw an error and its only purpose is to demo what would happen
+ when you encounter a backend server error (the UI should rollback to previous state before you did the action).
+
+
diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example09.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example09.ts
index 71d606534..2bbf7996e 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example09.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example09.ts
@@ -36,11 +36,16 @@ export class Example09 {
// this._bindingEventService.bind(gridContainerElm, 'onafterexporttoexcel', () => console.log('onAfterExportToExcel'));
this.sgb = new Slicker.GridBundle(gridContainerElm, this.columnDefinitions, { ...ExampleGridOptions, ...this.gridOptions }, []);
- // you can optionally cancel the sort for whatever reason
+ // you can optionally cancel the Sort, Filter
// this._bindingEventService.bind(gridContainerElm, 'onbeforesort', (e) => {
// e.preventDefault();
// return false;
// });
+ // this._bindingEventService.bind(gridContainerElm, 'onbeforesearchchange', (e) => {
+ // e.preventDefault();
+ // this.sgb.filterService.resetToPreviousSearchFilters(); // optionally reset filter input value
+ // return false;
+ // });
}
dispose() {
@@ -75,7 +80,7 @@ export class Example09 {
collection: [{ value: '', label: '' }, { value: 'male', label: 'male' }, { value: 'female', label: 'female' }]
}
},
- { id: 'company', name: 'Company', field: 'company', sortable: true },
+ { id: 'company', name: 'Company', field: 'company', filterable: true, sortable: true },
];
this.gridOptions = {
@@ -116,7 +121,8 @@ export class Example09 {
enableCount: this.isCountEnabled, // add the count in the OData query, which will return a property named "odata.count" (v2) or "@odata.count" (v4)
version: this.odataVersion // defaults to 2, the query string is slightly different between OData 2 and 4
},
- onError: () => {
+ onError: (error: Error) => {
+ this.errorStatus = error.message;
this.errorStatusClass = 'visible notification is-light is-danger is-small is-narrow';
this.displaySpinner(false, true);
},
@@ -175,7 +181,6 @@ export class Example09 {
* in your case the getCustomer() should be a WebAPI function returning a Promise
*/
getCustomerDataApiMock(query): Promise {
- this.errorStatus = '';
this.errorStatusClass = 'hidden';
// the mock is returning a Promise, just like a WebAPI typically does
@@ -226,14 +231,17 @@ export class Example09 {
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = { type: 'ends', term: filterMatch[2].trim() };
}
+
+ // simular a backend error when trying to sort on the "Company" field
+ if (filterBy.includes('company')) {
+ throw new Error('Cannot filter by the field "Company"');
+ }
}
}
// simular a backend error when trying to sort on the "Company" field
if (orderBy.includes('company')) {
- const errorMsg = 'Cannot sort by the field "Company"';
- this.errorStatus = errorMsg;
- throw new Error(errorMsg);
+ throw new Error('Cannot sort by the field "Company"');
}
const sort = orderBy.includes('asc')
diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example15.html b/examples/webpack-demo-vanilla-bundle/src/examples/example15.html
index 686d33df0..53ba2fb42 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example15.html
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example15.html
@@ -9,6 +9,11 @@
+
+ NOTE: The last column (filter & sort) will always throw an error and its only purpose is to demo what would happen
+ when you encounter a backend server error (the UI should rollback to previous state before you did the action).
+
+
diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example15.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example15.ts
index 529528cc4..0445638d2 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example15.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example15.ts
@@ -83,7 +83,7 @@ export class Example15 {
}
}
},
- { id: 'company', name: 'Company', field: 'company', sortable: true },
+ { id: 'company', name: 'Company', field: 'company', filterable: true, sortable: true },
];
this.gridOptions = {
@@ -129,7 +129,8 @@ export class Example15 {
enableCount: this.isCountEnabled, // add the count in the OData query, which will return a property named "odata.count" (v2) or "@odata.count" (v4)
version: this.odataVersion // defaults to 2, the query string is slightly different between OData 2 and 4
},
- onError: () => {
+ onError: (error: Error) => {
+ this.errorStatus = error.message;
this.errorStatusClass = 'visible notification is-light is-danger is-small is-narrow';
this.displaySpinner(false, true);
},
@@ -265,14 +266,17 @@ export class Example15 {
const fieldName = filterMatch[1].trim();
columnFilters[fieldName] = { type: 'ends', term: filterMatch[2].trim() };
}
+
+ // simular a backend error when trying to sort on the "Company" field
+ if (filterBy.includes('company')) {
+ throw new Error('Cannot filter by the field "Company"');
+ }
}
}
// simular a backend error when trying to sort on the "Company" field
if (orderBy.includes('company')) {
- const errorMsg = 'Cannot sort by the field "Company"';
- this.errorStatus = errorMsg;
- throw new Error(errorMsg);
+ throw new Error('Cannot sort by the field "Company"');
}
const sort = orderBy.includes('asc')
diff --git a/packages/common/src/services/__tests__/filter.service.spec.ts b/packages/common/src/services/__tests__/filter.service.spec.ts
index 3384ed9fe..5f4047083 100644
--- a/packages/common/src/services/__tests__/filter.service.spec.ts
+++ b/packages/common/src/services/__tests__/filter.service.spec.ts
@@ -565,14 +565,19 @@ describe('FilterService', () => {
gridOptionMock.backendServiceApi!.onError = () => jest.fn();
const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish');
const spyOnError = jest.spyOn(gridOptionMock.backendServiceApi as BackendServiceApi, 'onError');
+ const updateFilterSpy = jest.spyOn(service, 'updateFilters');
jest.spyOn(gridOptionMock.backendServiceApi as BackendServiceApi, 'process');
+ // get previous filters before calling the query that will fail
+ const previousFilters = service.getPreviousFilters();
+
service.clearFilters();
expect(pubSubSpy).toHaveBeenCalledWith(`onBeforeFilterClear`, true, 0);
setTimeout(() => {
expect(pubSubSpy).toHaveBeenCalledWith(`onFilterCleared`, true);
expect(spyOnError).toHaveBeenCalledWith(errorExpected);
+ expect(updateFilterSpy).toHaveBeenCalledWith(previousFilters, false, false, false);
done();
});
});
@@ -970,7 +975,7 @@ describe('FilterService', () => {
});
it('should throw an error when grid argument is an empty object', (done) => {
- service.onBackendFilterChange(undefined as any, {}).catch((error) => {
+ service.onBackendFilterChange(undefined as any, {} as any).catch((error) => {
expect(error.message).toContain(`Something went wrong when trying to bind the "onBackendFilterChange(event, args)" function`);
done();
});
@@ -979,7 +984,7 @@ describe('FilterService', () => {
it('should throw an error when backendServiceApi is undefined', (done) => {
gridOptionMock.backendServiceApi = undefined;
- service.onBackendFilterChange(undefined as any, { grid: gridStub }).catch((error) => {
+ service.onBackendFilterChange(undefined as any, { grid: gridStub } as any).catch((error) => {
expect(error.message).toContain(`BackendServiceApi requires at least a "process" function and a "service" defined`);
done();
});
@@ -989,7 +994,7 @@ describe('FilterService', () => {
const spy = jest.spyOn(gridOptionMock.backendServiceApi as BackendServiceApi, 'preProcess');
service.init(gridStub);
- service.onBackendFilterChange(undefined as any, { grid: gridStub });
+ service.onBackendFilterChange(undefined as any, { grid: gridStub } as any);
expect(spy).toHaveBeenCalled();
});
diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts
index 3b05afeab..323ecfe1b 100644
--- a/packages/common/src/services/filter.service.ts
+++ b/packages/common/src/services/filter.service.ts
@@ -39,7 +39,7 @@ import { Constants } from '../constants';
// using external non-typed js libraries
declare const Slick: SlickNamespace;
-interface OnSearchChangeEvent {
+interface OnSearchChangeEventArgs {
clearFilterTriggered?: boolean;
shouldTriggerQuery?: boolean;
columnId: string | number;
@@ -59,7 +59,8 @@ export class FilterService {
protected _columnFilters: ColumnFilters = {};
protected _grid!: SlickGrid;
protected _isTreePresetExecuted = false;
- protected _onSearchChange: SlickEvent | null;
+ protected _previousFilters: CurrentFilter[] = [];
+ protected _onSearchChange: SlickEvent | null;
protected _tmpPreFilteredData?: number[];
protected httpCancelRequests$?: Subject; // this will be used to cancel any pending http request
@@ -133,7 +134,7 @@ export class FilterService {
* Dispose of the filters, since it's a singleton, we don't want to affect other grids with same columns
*/
disposeColumnFilters() {
- this.resetColumnFilters();
+ this.removeAllColumnFiltersProperties();
// also destroy each Filter instances
if (Array.isArray(this._filtersMetadata)) {
@@ -145,20 +146,6 @@ export class FilterService {
}
}
- /**
- * When clearing or disposing of all filters, we need to loop through all columnFilters and delete them 1 by 1
- * only trying to make columnFilter an empty (without looping) would not trigger a dataset change
- */
- resetColumnFilters() {
- if (typeof this._columnFilters === 'object') {
- for (const columnId in this._columnFilters) {
- if (columnId && this._columnFilters[columnId]) {
- delete this._columnFilters[columnId];
- }
- }
- }
- }
-
/**
* Bind a backend filter hook to the grid
* @param grid SlickGrid Grid object
@@ -227,6 +214,9 @@ export class FilterService {
if (!isClearFilterEvent) {
await this.emitFilterChanged(EmitterType.local);
}
+
+ // keep a copy of the filters in case we need to rollback
+ this._previousFilters = this.extractBasicFilterDetails(this._columnFilters);
});
}
@@ -262,7 +252,7 @@ export class FilterService {
// when using a backend service, we need to manually trigger a filter change but only if the filter was previously filled
if (isBackendApi) {
if (currentColFilter !== undefined) {
- this.onBackendFilterChange(event as KeyboardEvent, { grid: this._grid, columnFilters: this._columnFilters });
+ this.onBackendFilterChange(event as KeyboardEvent, { grid: this._grid, columnFilters: this._columnFilters } as unknown as OnSearchChangeEventArgs);
}
}
@@ -286,8 +276,8 @@ export class FilterService {
}
});
- // also reset the columnFilters object and remove any filters from the object
- this.resetColumnFilters();
+ // also delete the columnFilters object and remove any filters from the object
+ this.removeAllColumnFiltersProperties();
// also remove any search terms directly on each column definitions
if (Array.isArray(this._columnDefinitions)) {
@@ -312,8 +302,13 @@ export class FilterService {
const query = queryResponse as string;
const totalItems = this._gridOptions?.pagination?.totalItems ?? 0;
this.backendUtilities?.executeBackendCallback(backendApi, query, callbackArgs, new Date(), totalItems, {
+ errorCallback: this.resetToPreviousSearchFilters.bind(this),
+ successCallback: (responseArgs) => this._previousFilters = this.extractBasicFilterDetails(responseArgs.columnFilters),
emitActionChangedCallback: this.emitFilterChanged.bind(this)
});
+ } else {
+ // keep a copy of the filters in case we need to rollback
+ this._previousFilters = this.extractBasicFilterDetails(this._columnFilters);
}
// emit an event when filters are all cleared
@@ -617,11 +612,15 @@ export class FilterService {
return filteredChildrenAndParents;
}
- getColumnFilters() {
+ getColumnFilters(): ColumnFilters {
return this._columnFilters;
}
- getFiltersMetadata() {
+ getPreviousFilters(): CurrentFilter[] {
+ return this._previousFilters;
+ }
+
+ getFiltersMetadata(): Filter[] {
return this._filtersMetadata;
}
@@ -668,7 +667,7 @@ export class FilterService {
}
}
- async onBackendFilterChange(event: KeyboardEvent, args: any) {
+ async onBackendFilterChange(event: KeyboardEvent, args: OnSearchChangeEventArgs) {
const isTriggeringQueryEvent = args?.shouldTriggerQuery;
if (isTriggeringQueryEvent) {
@@ -695,9 +694,11 @@ export class FilterService {
// query backend, except when it's called by a ClearFilters then we won't
if (isTriggeringQueryEvent) {
- const query = await backendApi.service.processOnFilterChanged(event, args);
+ const query = await backendApi.service.processOnFilterChanged(event, args as FilterChangedArgs);
const totalItems = this._gridOptions?.pagination?.totalItems ?? 0;
this.backendUtilities?.executeBackendCallback(backendApi, query, args, startTime, totalItems, {
+ errorCallback: this.resetToPreviousSearchFilters.bind(this),
+ successCallback: (responseArgs) => this._previousFilters = this.extractBasicFilterDetails(responseArgs.columnFilters),
emitActionChangedCallback: this.emitFilterChanged.bind(this),
httpCancelRequestSubject: this.httpCancelRequests$
});
@@ -732,6 +733,9 @@ export class FilterService {
if (this._gridOptions?.enableTreeData) {
this.refreshTreeDataFilters();
}
+
+ // keep reference of the filters
+ this._previousFilters = this.extractBasicFilterDetails(this._columnFilters);
}
return this._columnDefinitions;
}
@@ -780,6 +784,14 @@ export class FilterService {
}
}
+ /**
+ * Reset (revert) to previous filters, it could be because you prevented `onBeforeSearchChange` OR a Backend Error was thrown.
+ * It will reapply the previous filter state in the UI.
+ */
+ resetToPreviousSearchFilters() {
+ this.updateFilters(this._previousFilters, false, false, false);
+ }
+
/**
* Toggle the Filter Functionality (show/hide the header row filter bar as well)
* @param {boolean} clearFiltersWhenDisabled - when disabling the filters, do we want to clear the filters before hiding the filters? Defaults to True
@@ -1052,7 +1064,7 @@ export class FilterService {
const eventKey = (event as KeyboardEvent)?.key;
const eventKeyCode = (event as KeyboardEvent)?.keyCode;
if (this._onSearchChange && (args.forceOnSearchChangeEvent || eventKey === 'Enter' || eventKeyCode === KeyCode.ENTER || !dequal(oldColumnFilters, this._columnFilters))) {
- this._onSearchChange.notify({
+ const eventArgs = {
clearFilterTriggered: args.clearFilterTriggered,
shouldTriggerQuery: args.shouldTriggerQuery,
columnId,
@@ -1062,7 +1074,10 @@ export class FilterService {
searchTerms,
parsedSearchTerms,
grid: this._grid
- }, eventData);
+ } as OnSearchChangeEventArgs;
+ if (this.pubSubService.publish('onBeforeSearchChange', eventArgs) !== false) {
+ this._onSearchChange.notify(eventArgs, eventData);
+ }
}
}
}
@@ -1106,6 +1121,37 @@ export class FilterService {
return columnDefinitions;
}
+ /**
+ * From a ColumnFilters object, extra only the basic filter details (columnId, operator & searchTerms)
+ * @param {Object} columnFiltersObject - columnFilters object
+ * @returns - basic details of a column filter
+ */
+ protected extractBasicFilterDetails(columnFiltersObject: ColumnFilters): CurrentFilter[] {
+ const filters: CurrentFilter[] = [];
+
+ if (columnFiltersObject && typeof columnFiltersObject === 'object') {
+ for (const columnId of Object.keys(columnFiltersObject)) {
+ const { operator, searchTerms } = columnFiltersObject[`${columnId}`];
+ filters.push({ columnId, operator, searchTerms });
+ }
+ }
+ return filters;
+ }
+
+ /**
+ * When clearing or disposing of all filters, we need to loop through all columnFilters and delete them 1 by 1
+ * only trying to make columnFilter an empty (without looping) would not trigger a dataset change
+ */
+ protected removeAllColumnFiltersProperties() {
+ if (typeof this._columnFilters === 'object') {
+ for (const columnId in this._columnFilters) {
+ if (columnId && this._columnFilters[columnId]) {
+ delete this._columnFilters[columnId];
+ }
+ }
+ }
+ }
+
/**
* Subscribe to `onBeforeHeaderRowCellDestroy` to destroy Filter(s) to avoid leak and not keep orphan filters
* @param {Object} grid - Slick Grid object
diff --git a/packages/event-pub-sub/src/eventPubSub.service.spec.ts b/packages/event-pub-sub/src/eventPubSub.service.spec.ts
index 005e104dc..964483b57 100644
--- a/packages/event-pub-sub/src/eventPubSub.service.spec.ts
+++ b/packages/event-pub-sub/src/eventPubSub.service.spec.ts
@@ -28,10 +28,11 @@ describe('EventPubSub Service', () => {
const dispatchSpy = jest.spyOn(service, 'dispatchCustomEvent');
const getEventNameSpy = jest.spyOn(service, 'getEventNameByNamingConvention');
- service.publish('onClick', { name: 'John' });
+ const publishResult = service.publish('onClick', { name: 'John' });
+ expect(publishResult).toBeTruthy();
expect(getEventNameSpy).toHaveBeenCalledWith('onClick', '');
- expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, false);
+ expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, true);
});
it('should call publish method and expect it to return it a simple boolean (without delay argument provided)', () => {
@@ -42,7 +43,7 @@ describe('EventPubSub Service', () => {
expect(publishResult).toBeTruthy();
expect(getEventNameSpy).toHaveBeenCalledWith('onClick', '');
- expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, false);
+ expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, true);
});
it('should call publish method and expect it to return it a boolean in a Promise when a delay is provided', async () => {
@@ -53,7 +54,7 @@ describe('EventPubSub Service', () => {
expect(publishResult).toBeTruthy();
expect(getEventNameSpy).toHaveBeenCalledWith('onClick', '');
- expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, false);
+ expect(dispatchSpy).toHaveBeenCalledWith('onClick', { name: 'John' }, true, true);
});
it('should define a different event name styling and expect "dispatchCustomEvent" and "getEventNameByNamingConvention" to be called', () => {
@@ -61,10 +62,11 @@ describe('EventPubSub Service', () => {
const getEventNameSpy = jest.spyOn(service, 'getEventNameByNamingConvention');
service.eventNamingStyle = EventNamingStyle.lowerCase;
- service.publish('onClick', { name: 'John' });
+ const publishResult = service.publish('onClick', { name: 'John' });
+ expect(publishResult).toBeTruthy();
expect(getEventNameSpy).toHaveBeenCalledWith('onClick', '');
- expect(dispatchSpy).toHaveBeenCalledWith('onclick', { name: 'John' }, true, false);
+ expect(dispatchSpy).toHaveBeenCalledWith('onclick', { name: 'John' }, true, true);
});
});
diff --git a/packages/event-pub-sub/src/eventPubSub.service.ts b/packages/event-pub-sub/src/eventPubSub.service.ts
index 1fae21585..db05031ea 100644
--- a/packages/event-pub-sub/src/eventPubSub.service.ts
+++ b/packages/event-pub-sub/src/eventPubSub.service.ts
@@ -27,9 +27,10 @@ export class EventPubSubService implements PubSubService {
/**
* Method to publish a message via a dispatchEvent.
- * We return the dispatched event in a Promise with a delayed cycle and we do this because
- * most framework require a cycle before the binding is processed and binding a spinner end up showing too late
- * for example this is used for these events: onBeforeFilterClear, onBeforeFilterChange, onBeforeToggleTreeCollapse, onBeforeSortChange
+ * Return is a Boolean (from the event dispatch) unless a delay is provided if so we'll return the dispatched event in a Promise with a delayed cycle
+ * The delay is rarely use and is only used when we want to make sure that certain events have the time to execute
+ * and we do this because most framework require a cycle before the binding is processed and binding a spinner end up showing too late
+ * for example this is used for the following events: onBeforeFilterClear, onBeforeFilterChange, onBeforeToggleTreeCollapse, onBeforeSortChange
* @param {String} event - The event or channel to publish to.
* @param {*} data - The data to publish on the channel.
* @param {Number} delay - optional argument to delay the publish event
@@ -40,11 +41,11 @@ export class EventPubSubService implements PubSubService {
if (delay) {
return new Promise(resolve => {
- const isDispatched = this.dispatchCustomEvent(eventNameByConvention, data, true, false);
+ const isDispatched = this.dispatchCustomEvent(eventNameByConvention, data, true, true);
setTimeout(() => resolve(isDispatched), delay);
});
} else {
- return this.dispatchCustomEvent(eventNameByConvention, data, true, false);
+ return this.dispatchCustomEvent(eventNameByConvention, data, true, true);
}
}
diff --git a/test/cypress/integration/example09.spec.js b/test/cypress/integration/example09.spec.js
index e56e7c39c..c9facbb7f 100644
--- a/test/cypress/integration/example09.spec.js
+++ b/test/cypress/integration/example09.spec.js
@@ -670,5 +670,46 @@ describe('Example 09 - OData Grid', { retries: 1 }, () => {
expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(startswith(Name, 'A') and Gender eq 'female')`);
});
});
+
+ it('should try the "Company" filter and expect an error to throw and also expect the filter to reset to empty after the error is displayed', () => {
+ cy.get('input.search-filter.filter-company')
+ .type('Core');
+
+ // wait for the query to finish
+ cy.get('[data-test=error-status]').should('contain', 'Cannot filter by the field "Company"');
+ cy.get('[data-test=status]').should('contain', 'ERROR!!');
+
+ cy.get('[data-test=odata-query-result]')
+ .should(($span) => {
+ expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(startswith(Name, 'A') and Gender eq 'female')`);
+ });
+
+ cy.get('.grid9')
+ .find('.slick-row')
+ .should('have.length', 1);
+ });
+
+ it('should clear the "Name" filter and expect query to be successfull with just 1 filter "Gender" to be filled but without the previous failed filter', () => {
+ cy.get('.grid9')
+ .find('.slick-header-left .slick-header-column:nth(1)')
+ .trigger('mouseover')
+ .children('.slick-header-menubutton')
+ .click();
+
+ cy.get('.slick-header-menu')
+ .should('be.visible')
+ .children('.slick-header-menuitem:nth-child(6)')
+ .children('.slick-header-menucontent')
+ .should('contain', 'Remove Filter')
+ .click();
+
+ // wait for the query to finish
+ cy.get('[data-test=status]').should('contain', 'finished');
+
+ cy.get('[data-test=odata-query-result]')
+ .should(($span) => {
+ expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(Gender eq 'female')`);
+ });
+ });
});
});
diff --git a/test/cypress/integration/example15.spec.js b/test/cypress/integration/example15.spec.js
index b18a5149a..83445f4e4 100644
--- a/test/cypress/integration/example15.spec.js
+++ b/test/cypress/integration/example15.spec.js
@@ -773,5 +773,47 @@ describe('Example 15 - OData Grid using RxJS', { retries: 1 }, () => {
expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(startswith(Name, 'A') and Gender eq 'female')`);
});
});
+
+ it('should try the "Company" filter and expect an error to throw and also expect the filter to reset to empty after the error is displayed', () => {
+ cy.get('input.search-filter.filter-company')
+ .type('Core');
+
+ // wait for the query to finish
+ cy.get('[data-test=error-status]').should('contain', 'Cannot filter by the field "Company"');
+ cy.get('[data-test=status]').should('contain', 'ERROR!!');
+
+ cy.get('[data-test=odata-query-result]')
+ .should(($span) => {
+ expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(startswith(Name, 'A') and Gender eq 'female')`);
+ });
+
+ cy.get('.grid15')
+ .find('.slick-row')
+ .should('have.length', 1);
+ });
+
+ it('should clear the "Name" filter and expect query to be successfull with just 1 filter "Gender" to be filled but without the previous failed filter', () => {
+ cy.get('.grid15')
+ .find('.slick-header-left .slick-header-column:nth(1)')
+ .trigger('mouseover')
+ .children('.slick-header-menubutton')
+ .invoke('show')
+ .click();
+
+ cy.get('.slick-header-menu')
+ .should('be.visible')
+ .children('.slick-header-menuitem:nth-child(6)')
+ .children('.slick-header-menucontent')
+ .should('contain', 'Remove Filter')
+ .click();
+
+ // wait for the query to finish
+ cy.get('[data-test=status]').should('contain', 'finished');
+
+ cy.get('[data-test=odata-query-result]')
+ .should(($span) => {
+ expect($span.text()).to.eq(`$top=10&$orderby=Name desc&$filter=(Gender eq 'female')`);
+ });
+ });
});
});