From 4d69bc01a349ed1665c483d84c64544124a36c9b Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 10 Jun 2024 11:41:53 -0400 Subject: [PATCH] fix(filters): skipCompoundOperatorFilterWithNullInput skip empty string (#1566) * fix(filters): skipCompoundOperatorFilterWithNullInput skip empty string --- .../src/examples/example12.ts | 3 +- .../__tests__/compoundDateFilter.spec.ts | 34 +++++++++++++++++++ .../__tests__/compoundInputFilter.spec.ts | 34 ++++++++++++++++++- packages/common/src/filters/dateFilter.ts | 6 ++-- packages/common/src/filters/inputFilter.ts | 10 ++++-- 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example12.ts b/examples/vite-demo-vanilla-bundle/src/examples/example12.ts index 3a007dcd9..942920fed 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example12.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example12.ts @@ -489,7 +489,8 @@ export default class Example12 { this.toggleBodyBackground(); } } - } + }, + skipCompoundOperatorFilterWithNullInput: true }; } diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index c78936a4a..92120fddc 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -228,6 +228,40 @@ describe('CompoundDateFilter', () => { expect(spyCallback).not.toHaveBeenCalled(); }); + it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as True and value is undefined', () => { + mockColumn.filter!.operator = '>'; + mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = true; + const callbackSpy = jest.spyOn(filterArguments, 'callback'); + + filter.init(filterArguments); + filter.setValues(['']); + const filterInputElm = divContainer.querySelector('.search-filter.filter-finish input.date-picker') as HTMLInputElement; + const filterSelectElm = divContainer.querySelector('.search-filter.filter-finish select') as HTMLInputElement; + + filterInputElm.value = undefined as any; + filterSelectElm.value = '<='; + filterSelectElm.dispatchEvent(new Event('change')); + + expect(callbackSpy).not.toHaveBeenCalled(); + }); + + it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as True and value is empty string', () => { + mockColumn.filter!.operator = '>'; + mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = true; + const callbackSpy = jest.spyOn(filterArguments, 'callback'); + + filter.init(filterArguments); + filter.setValues(['']); + const filterInputElm = divContainer.querySelector('.search-filter.filter-finish input.date-picker') as HTMLInputElement; + const filterSelectElm = divContainer.querySelector('.search-filter.filter-finish select') as HTMLInputElement; + + filterInputElm.value = ''; + filterSelectElm.value = '<='; + filterSelectElm.dispatchEvent(new Event('change')); + + expect(callbackSpy).not.toHaveBeenCalled(); + }); + it('should change operator dropdown without a date entered and expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as False', () => { mockColumn.filter!.operator = '>'; mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = false; diff --git a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts index 0ad2e9b60..6616b4f4f 100644 --- a/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundInputFilter.spec.ts @@ -176,7 +176,7 @@ describe('CompoundInputFilter', () => { expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['9'], shouldTriggerQuery: true }); }); - it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as True', () => { + it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as True and value is undefined', () => { mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = true; mockColumn.type = FieldType.number; const callbackSpy = jest.spyOn(filterArguments, 'callback'); @@ -190,6 +190,38 @@ describe('CompoundInputFilter', () => { expect(callbackSpy).not.toHaveBeenCalled(); }); + it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as True and value is empty string', () => { + mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = true; + mockColumn.type = FieldType.number; + const callbackSpy = jest.spyOn(filterArguments, 'callback'); + + filter.init(filterArguments); + filter.setValues(['']); + const filterSelectElm = divContainer.querySelector('.search-filter.filter-duration select') as HTMLInputElement; + + filterSelectElm.value = '<='; + filterSelectElm.dispatchEvent(new Event('change')); + + expect(callbackSpy).not.toHaveBeenCalled(); + }); + + it('should change operator dropdown without a value entered and expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" but value was changed from set to unset', () => { + mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = true; + mockColumn.type = FieldType.number; + const callbackSpy = jest.spyOn(filterArguments, 'callback'); + + filter.init(filterArguments); + const filterSelectElm = divContainer.querySelector('.search-filter.filter-duration select') as HTMLInputElement; + filter.setValues(['abc']); + filterSelectElm.dispatchEvent(new Event('change')); + + filter.setValues(['']); + filterSelectElm.value = '<='; + filterSelectElm.dispatchEvent(new Event('change')); + + expect(callbackSpy).toHaveBeenCalled(); + }); + it('should change operator dropdown without a value entered and not expect the callback to be called when "skipCompoundOperatorFilterWithNullInput" is defined as False', () => { mockColumn.filter!.skipCompoundOperatorFilterWithNullInput = false; mockColumn.type = FieldType.number; diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index 87a5a21c1..cfaf41751 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -1,5 +1,5 @@ import { BindingEventService } from '@slickgrid-universal/binding'; -import { createDomElement, emptyElement, extend, } from '@slickgrid-universal/utils'; +import { createDomElement, emptyElement, extend, isDefined, } from '@slickgrid-universal/utils'; import { format, parse } from '@formkit/tempo'; import { VanillaCalendar, type IOptions } from 'vanilla-calendar-picker'; @@ -495,8 +495,8 @@ export class DateFilter implements Filter { this._currentValue ? this._filterElm.classList.add('filled') : this._filterElm.classList.remove('filled'); // when changing compound operator, we don't want to trigger the filter callback unless the date input is also provided - const skipCompoundOperatorFilterWithNullInput = this.columnFilter.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput === undefined; - if (!skipCompoundOperatorFilterWithNullInput || this._currentDateOrDates !== undefined) { + const skipNullInput = this.columnFilter.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput === undefined; + if (!skipNullInput || (skipNullInput && isDefined(this._currentDateOrDates))) { this.callback(e, { columnDef: this.columnDef, searchTerms: (this._currentValue ? [this._currentValue] : null), operator: selectedOperator || '', shouldTriggerQuery: this._shouldTriggerQuery }); } } diff --git a/packages/common/src/filters/inputFilter.ts b/packages/common/src/filters/inputFilter.ts index b994d8154..de1fd40e7 100644 --- a/packages/common/src/filters/inputFilter.ts +++ b/packages/common/src/filters/inputFilter.ts @@ -1,5 +1,5 @@ import { BindingEventService } from '@slickgrid-universal/binding'; -import { createDomElement, emptyElement, toSentenceCase } from '@slickgrid-universal/utils'; +import { createDomElement, emptyElement, isDefined, toSentenceCase } from '@slickgrid-universal/utils'; import type { Column, @@ -25,6 +25,7 @@ export class InputFilter implements Filter { protected _cellContainerElm!: HTMLDivElement; protected _filterContainerElm!: HTMLDivElement; protected _filterInputElm!: HTMLInputElement; + protected _lastSearchValue?: number | string; protected _selectOperatorElm?: HTMLSelectElement; inputFilterType: 'single' | 'compound' = 'single'; grid!: SlickGrid; @@ -335,8 +336,10 @@ export class InputFilter implements Filter { const callbackArgs = { columnDef: this.columnDef, operator: selectedOperator, searchTerms: (value ? [value] : null), shouldTriggerQuery: this._shouldTriggerQuery }; const typingDelay = (eventType === 'keyup' && (event as KeyboardEvent)?.key !== 'Enter') ? this._debounceTypingDelay : 0; - const skipCompoundOperatorFilterWithNullInput = this.columnFilter.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput; - if (this.inputFilterType === 'single' || !skipCompoundOperatorFilterWithNullInput || this._currentValue !== undefined) { + const skipNullInput = this.columnFilter.skipCompoundOperatorFilterWithNullInput ?? this.gridOptions.skipCompoundOperatorFilterWithNullInput; + const hasSkipNullValChanged = (skipNullInput && isDefined(this._currentValue)) || (this._currentValue === '' && isDefined(this._lastSearchValue)); + + if (this.inputFilterType === 'single' || !skipNullInput || hasSkipNullValChanged) { if (typingDelay > 0) { clearTimeout(this._timer as NodeJS.Timeout); this._timer = setTimeout(() => this.callback(event, callbackArgs), typingDelay); @@ -344,6 +347,7 @@ export class InputFilter implements Filter { this.callback(event, callbackArgs); } } + this._lastSearchValue = value; } // reset both flags for next use