From 9ded20477272781a46a5f2996144896ca3110944 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 19 Jan 2021 17:42:18 -0500 Subject: [PATCH] fix(filters): Grid State filters should always include an operator (#676) * fix(filters): Grid State filters should always include an operator --- .../models/filter.interface.ts | 14 ++++++++++---- .../services/__tests__/filter.service.spec.ts | 19 ++++++++++--------- .../services/filter.service.ts | 15 +++++++-------- test/cypress/integration/example05.spec.js | 2 +- test/cypress/integration/example08.spec.js | 2 +- test/cypress/integration/example10.spec.js | 6 +++--- 6 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/app/modules/angular-slickgrid/models/filter.interface.ts b/src/app/modules/angular-slickgrid/models/filter.interface.ts index 0db732f3a..0fb0459f4 100644 --- a/src/app/modules/angular-slickgrid/models/filter.interface.ts +++ b/src/app/modules/angular-slickgrid/models/filter.interface.ts @@ -20,15 +20,21 @@ export interface Filter { /** SlickGrid grid object */ grid: any; - /** Array of defined search terms to pre-load */ - searchTerms?: SearchTerm[]; + /** The default search operator for the filter when not provided */ + defaultOperator?: OperatorString | OperatorType; /** The search operator for the filter */ operator: OperatorType | OperatorString; - /** You can use "params" to pass any types of arguments to your Filter */ + /** You can use "params" to pass any generic arguments to your Filter */ params?: any | any[]; + /** Array of defined search terms to pre-load */ + searchTerms?: SearchTerm[]; + + // -- + // public functions + /** Filter class initialization, executed by the FilterService right after creating the Filter */ init: (args: FilterArguments, isFilterFirstRender?: boolean) => void; @@ -42,5 +48,5 @@ export interface Filter { getValues?: () => SearchTerm | SearchTerm[] | undefined; /** Set value(s) on the DOM element */ - setValues: (values: SearchTerm | SearchTerm[] | undefined) => void; + setValues: (values: SearchTerm | SearchTerm[] | undefined, operator?: OperatorType | OperatorString) => void; } 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 250436f73..e753c1d7a 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 @@ -8,10 +8,11 @@ import { of, throwError } from 'rxjs'; import { BackendService, Column, + ColumnFilters, CurrentFilter, + FieldType, GridMenuItem, GridOption, - FieldType, MenuCommandItem, SlickEventHandler, } from '../../models'; @@ -19,7 +20,7 @@ import { Filters } from '../../filters'; import { FilterService } from '../filter.service'; import { FilterFactory } from '../../filters/filterFactory'; import { SharedService } from '../shared.service'; -import { SlickgridConfig, CollectionService } from '../..'; +import { CollectionService, SlickgridConfig } from '../../index'; import * as utilities from '../../services/backend-utilities'; const mockRefreshBackendDataset = jest.fn(); @@ -1361,11 +1362,11 @@ describe('FilterService', () => { gridStub.onHeaderRowCellRendered.notify(mockArgs1, new Slick.EventData(), gridStub); gridStub.onHeaderRowCellRendered.notify(mockArgs2, new Slick.EventData(), gridStub); - const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['map'] } }; + const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'] } }; service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['map'] }], true, true, true); const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters }); - expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', searchTerms: ['map',] }]); + expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', operator: 'Contains', searchTerms: ['map',] }]); expect(output).toBe(true); expect(preFilterSpy).toHaveBeenCalledWith(dataset, columnFilters); expect(preFilterSpy).toHaveReturnedWith([21, 4, 5]); @@ -1385,11 +1386,11 @@ describe('FilterService', () => { gridStub.onHeaderRowCellRendered.notify(mockArgs1, new Slick.EventData(), gridStub); gridStub.onHeaderRowCellRendered.notify(mockArgs2, new Slick.EventData(), gridStub); - const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['map'] } }; + const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', operator: 'Contains', searchTerms: ['map'] } }; service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['map'] }], true, true, true); const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters }); - expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', searchTerms: ['map',] }]); + expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', operator: 'Contains', searchTerms: ['map',] }]); expect(output).toBe(false); expect(preFilterSpy).toHaveBeenCalledWith(dataset, columnFilters); expect(preFilterSpy).toHaveReturnedWith([21, 4, 5]); @@ -1409,13 +1410,13 @@ describe('FilterService', () => { gridStub.onHeaderRowCellRendered.notify(mockArgs1, new Slick.EventData(), gridStub); gridStub.onHeaderRowCellRendered.notify(mockArgs2, new Slick.EventData(), gridStub); - const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['unknown'] } }; + const columnFilters = { file: { columnDef: mockColumn1, columnId: 'file', searchTerms: ['unknown'] } } as ColumnFilters; service.updateFilters([{ columnId: 'file', operator: '', searchTerms: ['unknown'] }], true, true, true); const output = service.customLocalFilter(mockItem1, { dataView: dataViewStub, grid: gridStub, columnFilters }); - expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', searchTerms: ['unknown',] }]); + expect(spyRxjs).toHaveBeenCalledWith([{ columnId: 'file', operator: 'Contains', searchTerms: ['unknown',] }]); expect(output).toBe(false); - expect(preFilterSpy).toHaveBeenCalledWith(dataset, columnFilters); + expect(preFilterSpy).toHaveBeenCalledWith(dataset, { ...columnFilters, file: { ...columnFilters.file, operator: 'Contains' } }); // it will use Contains by default expect(preFilterSpy).toHaveReturnedWith([]); }); }); diff --git a/src/app/modules/angular-slickgrid/services/filter.service.ts b/src/app/modules/angular-slickgrid/services/filter.service.ts index 8d3f0ed22..4dedec866 100644 --- a/src/app/modules/angular-slickgrid/services/filter.service.ts +++ b/src/app/modules/angular-slickgrid/services/filter.service.ts @@ -25,7 +25,7 @@ import { SlickEventHandler, } from './../models/index'; import { executeBackendCallback, refreshBackendDataset } from './backend-utilities'; -import { getDescendantProperty } from './utilities'; +import { getDescendantProperty, mapOperatorByFieldType } from './utilities'; import { FilterConditions } from './../filter-conditions'; import { FilterFactory } from '../filters/filterFactory'; import { SharedService } from './shared.service'; @@ -43,7 +43,7 @@ export class FilterService { private _eventHandler: SlickEventHandler; private _isFilterFirstRender = true; private _firstColumnIdRendered = ''; - private _filtersMetadata: any[] = []; + protected _filtersMetadata: Array = []; private _columnFilters: ColumnFilters = {}; private _grid: any; private _onSearchChange: SlickEvent | null; @@ -130,7 +130,7 @@ export class FilterService { if (Array.isArray(this._filtersMetadata)) { this._filtersMetadata.forEach((filter) => { if (filter && filter.destroy) { - filter.destroy(true); + filter.destroy(); } }); } @@ -228,7 +228,7 @@ export class FilterService { } // find the filter object and call its clear method with true (the argument tells the method it was called by a clear filter) - const colFilter: Filter = this._filtersMetadata.find((filter: Filter) => filter.columnDef.id === columnId); + const colFilter: Filter | undefined = this._filtersMetadata.find((filter: Filter) => filter.columnDef.id === columnId); if (colFilter && colFilter.clear) { colFilter.clear(true); } @@ -830,6 +830,7 @@ export class FilterService { const searchTerms = (args.searchTerms && Array.isArray(args.searchTerms)) ? args.searchTerms : (searchTerm ? [searchTerm] : undefined); const columnDef = args.columnDef || null; const columnId = columnDef && columnDef.id || ''; + const fieldType = columnDef && columnDef.filter && columnDef.filter.type || columnDef && columnDef.type || FieldType.string; const operator = args.operator || undefined; const hasSearchTerms = searchTerms && Array.isArray(searchTerms); const termsCount = hasSearchTerms && searchTerms && searchTerms.length; @@ -847,9 +848,7 @@ export class FilterService { columnDef, searchTerms, }; - if (operator) { - colFilter.operator = operator; - } + colFilter.operator = operator || mapOperatorByFieldType(fieldType); this._columnFilters[colId] = colFilter; } } @@ -868,7 +867,7 @@ export class FilterService { columnId, columnDef, columnFilters: this._columnFilters, - operator, + operator: operator || mapOperatorByFieldType(fieldType), searchTerms, grid: this._grid }, eventData); diff --git a/test/cypress/integration/example05.spec.js b/test/cypress/integration/example05.spec.js index ca030eb7f..46f5b1ee3 100644 --- a/test/cypress/integration/example05.spec.js +++ b/test/cypress/integration/example05.spec.js @@ -563,7 +563,7 @@ describe('Example 5 - OData Grid', () => { cy.window().then((win) => { expect(win.console.log).to.have.callCount(2); - expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', searchTerms: ['x'] }], type: 'filter' }); + expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', operator: 'Contains', searchTerms: ['x'] }], type: 'filter' }); expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: { pageNumber: 1, pageSize: 10 }, type: 'pagination' }); }); }); diff --git a/test/cypress/integration/example08.spec.js b/test/cypress/integration/example08.spec.js index b795ed198..686e91fbb 100644 --- a/test/cypress/integration/example08.spec.js +++ b/test/cypress/integration/example08.spec.js @@ -123,7 +123,7 @@ describe('Example 8 - Header Menu Plugin', () => { cy.get('#grid8') .find('.slick-row .slick-cell:nth(2)') .each($row => { - expect(+$row.text()).to.be.greaterThan(80); + expect(+$row.text()).to.be.greaterThan(60); }); }); diff --git a/test/cypress/integration/example10.spec.js b/test/cypress/integration/example10.spec.js index 72362b7f7..c60a9f257 100644 --- a/test/cypress/integration/example10.spec.js +++ b/test/cypress/integration/example10.spec.js @@ -595,7 +595,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { cy.window().then((win) => { expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: { gridRowIndexes: [0, 1], dataContextIds: [3, 12, 13, 522, 1], filteredDataContextIds: [3, 13] }, type: 'rowSelection' }); - expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', searchTerms: ['3'] }], type: 'filter' }); + expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', operator: 'Contains', searchTerms: ['3'] }], type: 'filter' }); }); }); @@ -678,7 +678,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { cy.window().then((win) => { expect(win.console.log).to.have.callCount(4); expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: { gridRowIndexes: [0, 1], dataContextIds: [3, 12, 13, 522, 1], filteredDataContextIds: [3, 13] }, type: 'rowSelection' }); - expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', searchTerms: ['3'] }], type: 'filter' }); + expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', operator: 'Contains', searchTerms: ['3'] }], type: 'filter' }); }); }); @@ -755,7 +755,7 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { cy.window().then((win) => { expect(win.console.log).to.have.callCount(4); expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: { gridRowIndexes: [0, 1], dataContextIds: [3, 12, 13, 522, 1], filteredDataContextIds: [3, 13] }, type: 'rowSelection' }); - expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', searchTerms: ['3'] }], type: 'filter' }); + expect(win.console.log).to.be.calledWith("Grid State changed:: ", { newValues: [{ columnId: 'title', operator: 'Contains', searchTerms: ['3'] }], type: 'filter' }); }); }); });