diff --git a/src/app/modules/angular-slickgrid/filters/__tests__/inputFilter.spec.ts b/src/app/modules/angular-slickgrid/filters/__tests__/inputFilter.spec.ts index d174d1835..b0a637b24 100644 --- a/src/app/modules/angular-slickgrid/filters/__tests__/inputFilter.spec.ts +++ b/src/app/modules/angular-slickgrid/filters/__tests__/inputFilter.spec.ts @@ -23,7 +23,7 @@ describe('InputFilter', () => { let divContainer: HTMLDivElement; let filter: InputFilter; let filterArguments: FilterArguments; - let spyGetHeaderRow; + let spyGetHeaderRow: any; let mockColumn: Column; beforeEach(() => { @@ -47,7 +47,7 @@ describe('InputFilter', () => { }); it('should throw an error when trying to call init without any arguments', () => { - expect(() => filter.init(null)).toThrowError('[Angular-SlickGrid] A filter must always have an "init()" with valid arguments.'); + expect(() => filter.init(null as any)).toThrowError('[Angular-SlickGrid] A filter must always have an "init()" with valid arguments.'); }); it('should initialize the filter', () => { @@ -61,101 +61,143 @@ describe('InputFilter', () => { it('should have a placeholder when defined in its column definition', () => { const testValue = 'test placeholder'; - mockColumn.filter.placeholder = testValue; + mockColumn.filter!.placeholder = testValue; filter.init(filterArguments); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; expect(filterElm.placeholder).toBe(testValue); }); - it('should call "setValues" and expect that value to be in the callback when triggered', () => { - const spyCallback = jest.spyOn(filterArguments, 'callback'); + describe('setValues method', () => { + it('should call "setValues" and expect that value to be in the callback when triggered', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filter.init(filterArguments); - filter.setValues('abc'); - const filterElm = divContainer.querySelector('input.filter-duration'); + filter.init(filterArguments); + filter.setValues('abc'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - it('should call "setValues" and expect that value to be in the callback when triggered by ENTER key', () => { - const spyCallback = jest.spyOn(filterArguments, 'callback'); + it('should call "setValues" and expect that value to be in the callback when triggered by ENTER key', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filter.init(filterArguments); - filter.setValues('abc'); - const filterElm = divContainer.querySelector('input.filter-duration'); + filter.init(filterArguments); + filter.setValues('abc'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - filterElm.focus(); - const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); - event.key = 'Enter'; - filterElm.dispatchEvent(event); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filterElm.focus(); + const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); + event.key = 'Enter'; + filterElm.dispatchEvent(event); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - it('should call "setValues" and expect that value NOT to be in the callback when triggered by a keyup event that is NOT the ENTER key', () => { - const spyCallback = jest.spyOn(filterArguments, 'callback'); + it('should call "setValues" and expect that value NOT to be in the callback when triggered by a keyup event that is NOT the ENTER key', () => { + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filter.init(filterArguments); - filter.setValues('abc'); - const filterElm = divContainer.querySelector('input.filter-duration'); + filter.init(filterArguments); + filter.setValues('abc'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - filterElm.focus(); - const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); - event.key = 'a'; - filterElm.dispatchEvent(event); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filterElm.focus(); + const event = new (window.window as any).Event('keyup', { bubbles: true, cancelable: true }); + event.key = 'a'; + filterElm.dispatchEvent(event); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - expect(filterFilledElms.length).toBe(0); - expect(spyCallback).not.toHaveBeenCalled(); - }); + expect(filterFilledElms.length).toBe(0); + expect(spyCallback).not.toHaveBeenCalled(); + }); - it('should call "setValues" an operator and with extra spaces at the beginning of the searchTerms and trim value when "enableFilterTrimWhiteSpace" is enabled in grid options', () => { - gridOptionMock.enableFilterTrimWhiteSpace = true; - const spyCallback = jest.spyOn(filterArguments, 'callback'); + it('should call "setValues" an operator and with extra spaces at the beginning of the searchTerms and trim value when "enableFilterTrimWhiteSpace" is enabled in grid options', () => { + gridOptionMock.enableFilterTrimWhiteSpace = true; + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filter.init(filterArguments); - filter.setValues(' abc ', 'EQ'); - const filterElm = divContainer.querySelector('input.filter-duration'); + filter.init(filterArguments); + filter.setValues(' abc ', 'EQ'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['abc'], shouldTriggerQuery: true }); - }); + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); - it('should call "setValues" with extra spaces at the beginning of the searchTerms and trim value when "enableTrimWhiteSpace" is enabled in the column filter', () => { - gridOptionMock.enableFilterTrimWhiteSpace = false; - mockColumn.filter.enableTrimWhiteSpace = true; - const spyCallback = jest.spyOn(filterArguments, 'callback'); + it('should call "setValues" with extra spaces at the beginning of the searchTerms and trim value when "enableTrimWhiteSpace" is enabled in the column filter', () => { + gridOptionMock.enableFilterTrimWhiteSpace = false; + mockColumn.filter!.enableTrimWhiteSpace = true; + const spyCallback = jest.spyOn(filterArguments, 'callback'); - filter.init(filterArguments); - filter.setValues(' abc '); - const filterElm = divContainer.querySelector('input.filter-duration'); + filter.init(filterArguments); + filter.setValues(' abc '); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; - filterElm.focus(); - filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); - const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + filterElm.focus(); + filterElm.dispatchEvent(new (window.window as any).Event('input', { keyCode: 97, bubbles: true, cancelable: true })); + const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); + + expect(filterFilledElms.length).toBe(1); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + }); + + it('should call "setValues" and include an operator and expect the operator to show up in the output search string shown in the filter input text value', () => { + filter.init(filterArguments); + + filter.setValues('abc', '<>'); + expect(filter.getValue()).toBe('<>abc'); + + filter.setValues('abc', '!='); + expect(filter.getValue()).toBe('!=abc'); + + filter.setValues('abc', '='); + expect(filter.getValue()).toBe('=abc'); + + filter.setValues('abc', '=='); + expect(filter.getValue()).toBe('==abc'); + + filter.setValues(123, '<'); + expect(filter.getValue()).toBe('<123'); + + filter.setValues(123, '<='); + expect(filter.getValue()).toBe('<=123'); + + filter.setValues(123, '>'); + expect(filter.getValue()).toBe('>123'); + + filter.setValues(123, '>='); + expect(filter.getValue()).toBe('>=123'); + + filter.setValues('abc', 'EndsWith'); + expect(filter.getValue()).toBe('*abc'); + + filter.setValues('abc', '*z'); + expect(filter.getValue()).toBe('*abc'); + + filter.setValues('abc', 'StartsWith'); + expect(filter.getValue()).toBe('abc*'); - expect(filterFilledElms.length).toBe(1); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '', searchTerms: ['abc'], shouldTriggerQuery: true }); + filter.setValues('abc', 'a*'); + expect(filter.getValue()).toBe('abc*'); + }); }); it('should trigger the callback method when user types something in the input', () => { const spyCallback = jest.spyOn(filterArguments, 'callback'); filter.init(filterArguments); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; filterElm.focus(); filterElm.value = 'a'; @@ -168,7 +210,7 @@ describe('InputFilter', () => { filterArguments.searchTerms = ['xyz']; filter.init(filterArguments); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; expect(filterElm.value).toBe('xyz'); }); @@ -177,7 +219,7 @@ describe('InputFilter', () => { filterArguments.searchTerms = ['']; filter.init(filterArguments); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); expect(filterElm.value).toBe(''); @@ -190,7 +232,7 @@ describe('InputFilter', () => { filter.init(filterArguments); filter.clear(); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); expect(filterElm.value).toBe(''); @@ -204,7 +246,7 @@ describe('InputFilter', () => { filter.init(filterArguments); filter.clear(false); - const filterElm = divContainer.querySelector('input.filter-duration'); + const filterElm = divContainer.querySelector('input.filter-duration') as HTMLInputElement; const filterFilledElms = divContainer.querySelectorAll('input.filter-duration.filled'); diff --git a/src/app/modules/angular-slickgrid/filters/inputFilter.ts b/src/app/modules/angular-slickgrid/filters/inputFilter.ts index 9dd99d096..9c83affba 100644 --- a/src/app/modules/angular-slickgrid/filters/inputFilter.ts +++ b/src/app/modules/angular-slickgrid/filters/inputFilter.ts @@ -111,10 +111,17 @@ export class InputFilter implements Filter { this.$filterElm = null; } + getValue() { + return this.$filterElm.val(); + } + /** Set value(s) on the DOM element */ setValues(values: SearchTerm | SearchTerm[], operator?: OperatorType | OperatorString) { - if (values) { - this.$filterElm.val(values); + const searchValues = Array.isArray(values) ? values : [values]; + let searchValue: SearchTerm = ''; + for (const value of searchValues) { + searchValue = operator ? this.addOptionalOperatorIntoSearchString(value, operator) : value; + this.$filterElm.val(searchValue); } // set the operator when defined @@ -125,6 +132,47 @@ export class InputFilter implements Filter { // protected functions // ------------------ + /** + * When loading the search string from the outside into the input text field, we should also add the prefix/suffix of the operator. + * We do this so that if it was loaded by a Grid Presets then we should also add the operator into the search string + * Let's take these 3 examples: + * 1. (operator: '>=', searchTerms:[55]) should display as ">=55" + * 2. (operator: 'StartsWith', searchTerms:['John']) should display as "John*" + * 3. (operator: 'EndsWith', searchTerms:['John']) should display as "*John" + * @param operator - operator string + */ + protected addOptionalOperatorIntoSearchString(inputValue: SearchTerm, operator: OperatorType | OperatorString): string { + let searchTermPrefix = ''; + let searchTermSuffix = ''; + let outputValue = inputValue === undefined || inputValue === null ? '' : `${inputValue}`; + + if (operator && outputValue) { + switch (operator) { + case '<>': + case '!=': + case '=': + case '==': + case '>': + case '>=': + case '<': + case '<=': + searchTermPrefix = operator; + break; + case 'EndsWith': + case '*z': + searchTermPrefix = '*'; + break; + case 'StartsWith': + case 'a*': + searchTermSuffix = '*'; + break; + } + outputValue = `${searchTermPrefix}${outputValue}${searchTermSuffix}`; + } + + return outputValue; + } + /** * Create the HTML template as a string */ diff --git a/src/app/modules/angular-slickgrid/services/filter.service.ts b/src/app/modules/angular-slickgrid/services/filter.service.ts index 995ab124f..53d1bcccf 100644 --- a/src/app/modules/angular-slickgrid/services/filter.service.ts +++ b/src/app/modules/angular-slickgrid/services/filter.service.ts @@ -925,7 +925,7 @@ export class FilterService { // when hiding/showing (with Column Picker or Grid Menu), it will try to re-create yet again the filters (since SlickGrid does a re-render) // we need to also set again the values in the DOM elements if the values were set by a searchTerm(s) if (searchTerms && newFilter.setValues) { - newFilter.setValues(searchTerms); + newFilter.setValues(searchTerms, operator); } } }