Skip to content

Commit

Permalink
perf(filters): merge all Slider filters into one class (#791)
Browse files Browse the repository at this point in the history
* perf(filters): merge all Slider filters into one class
  • Loading branch information
ghiscoding committed Nov 4, 2022
1 parent 71c976e commit fc4304b
Show file tree
Hide file tree
Showing 17 changed files with 568 additions and 925 deletions.
Expand Up @@ -9,6 +9,7 @@ import {
GridOption,
Grouping,
GroupTotalFormatters,
SliderOption,
SortComparers,
SortDirectionNumber,
} from '@slickgrid-universal/common';
Expand Down Expand Up @@ -76,7 +77,11 @@ export class Example2 {
id: 'duration', name: 'Duration', field: 'duration',
minWidth: 50, width: 60,
filterable: true,
filter: { model: Filters.slider, operator: '>=' },
filter: {
model: Filters.slider,
operator: '>=',
filterOptions: { hideSliderNumber: true, enableSliderTrackColoring: true, sliderTrackFilledColor: '#9ac49c' } as SliderOption
},
sortable: true,
type: FieldType.number,
groupTotalsFormatter: GroupTotalFormatters.sumTotals,
Expand Down
Expand Up @@ -107,7 +107,9 @@ export class Example7 {
},
{
id: 'percentComplete', nameKey: 'PERCENT_COMPLETE', field: 'percentComplete', type: 'number',
filterable: true, sortable: true, editor: { model: Editors.slider, minValue: 0, maxValue: 100, },
filterable: true, sortable: true,
filter: { model: Filters.compoundSlider, minValue: 0, maxValue: 100, operator: '>=' },
editor: { model: Editors.slider, minValue: 0, maxValue: 100, },
},
{
id: 'start', nameKey: 'START', field: 'start', formatter: Formatters.dateIso,
Expand Down
Expand Up @@ -179,6 +179,7 @@ export class Example14 {
filter: {
model: Filters.sliderRange,
operator: '>=',
// searchTerms: [15, 78],
filterOptions: {
enableSliderTrackColoring: true,
hideSliderNumbers: false,
Expand Down
Expand Up @@ -143,7 +143,7 @@ export class Example16 {
type: FieldType.number,
},
{
id: 'percentComplete', name: '% Complete', field: 'percentComplete', type: FieldType.number,
id: 'percentComplete', name: '% Complete', field: 'percentComplete', type: FieldType.number, minWidth: 130,
editor: {
model: Editors.slider,
minValue: 0,
Expand All @@ -153,7 +153,7 @@ export class Example16 {
exportWithFormatter: false,
formatter: Formatters.percentCompleteBar,
sortable: true, filterable: true,
filter: { model: Filters.slider, operator: '>=' },
filter: { model: Filters.sliderRange, operator: '>=' },
customTooltip: { useRegularTooltip: true, position: 'center' },
},
{
Expand Down
52 changes: 39 additions & 13 deletions packages/common/src/filters/__tests__/compoundSliderFilter.spec.ts
Expand Up @@ -69,16 +69,17 @@ describe('CompoundSliderFilter', () => {

expect(spyGetHeaderRow).toHaveBeenCalled();
expect(filterCount).toBe(1);
expect(filter.currentValue).toBe(0);
});

it('should have an aria-label when creating the filter', () => {
filter.init(filterArguments);
const filterInputElm = divContainer.querySelector('.input-group.search-filter.filter-duration input') as HTMLInputElement;

expect(filterInputElm.getAttribute('aria-label')).toBe('Duration Search Filter');
expect(filterInputElm.ariaLabel).toBe('Duration Search Filter');
});

it('should call "setValues" with "operator" set in the filter arguments and expect that value to be in the callback when triggered', () => {
it('should call "setValues" with "operator" set in the filter arguments and expect that value to be converted to number and in the callback when triggered', () => {
const callbackSpy = jest.spyOn(filterArguments, 'callback');
const rowMouseEnterSpy = jest.spyOn(gridStub.onHeaderRowMouseEnter, 'notify');
const filterArgs = { ...filterArguments, operator: '>', grid: gridStub } as FilterArguments;
Expand All @@ -90,7 +91,7 @@ describe('CompoundSliderFilter', () => {

jest.runAllTimers(); // fast-forward timer

expect(callbackSpy).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>', searchTerms: ['2'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>', searchTerms: [2], shouldTriggerQuery: true });
expect(rowMouseEnterSpy).toHaveBeenCalledWith({ column: mockColumn, grid: gridStub }, expect.anything());
});

Expand All @@ -105,7 +106,7 @@ describe('CompoundSliderFilter', () => {
const filterFilledElms = divContainer.querySelectorAll('.slider-container.search-filter.filter-duration.filled');

expect(filterFilledElms.length).toBe(1);
expect(callbackSpy).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['3'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: [3], shouldTriggerQuery: true });
});

it('should trigger an operator change event and expect the callback to be called with the searchTerms and operator defined', () => {
Expand All @@ -118,10 +119,10 @@ describe('CompoundSliderFilter', () => {
filterSelectElm.value = '<=';
filterSelectElm.dispatchEvent(new CustomEvent('change'));

expect(callbackSpy).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['9'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: [9], shouldTriggerQuery: true });
});

it('should be able to call "setValues" with a value and an extra operator and expect it to be set as new operator', () => {
it('should be able to call "setValues" with a value, converted as a number, and an extra operator and expect it to be set as new operator', () => {
const callbackSpy = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
Expand All @@ -130,7 +131,7 @@ describe('CompoundSliderFilter', () => {
const filterSelectElm = divContainer.querySelector('.search-filter.filter-duration select') as HTMLInputElement;
filterSelectElm.dispatchEvent(new CustomEvent('change'));

expect(callbackSpy).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>=', searchTerms: ['9'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>=', searchTerms: [9], shouldTriggerQuery: true });
});

it('should be able to call "setValues" and set empty values and the input to not have the "filled" css class', () => {
Expand Down Expand Up @@ -186,7 +187,7 @@ describe('CompoundSliderFilter', () => {

it('should create the input filter with min/max slider values being set by filter "sliderStartValue" and "sliderEndValue" through the filter params', () => {
mockColumn.filter = {
params: {
filterOptions: {
sliderStartValue: 4,
sliderEndValue: 69,
}
Expand All @@ -202,7 +203,7 @@ describe('CompoundSliderFilter', () => {

it('should create the input filter with default search terms range but without showing side numbers when "hideSliderNumber" is set in params', () => {
filterArguments.searchTerms = [3];
mockColumn.filter!.params = { hideSliderNumber: true };
mockColumn.filter!.filterOptions = { hideSliderNumber: true };

filter.init(filterArguments);

Expand All @@ -220,7 +221,7 @@ describe('CompoundSliderFilter', () => {
filter.clear();

expect(filter.getValues()).toBe(0);
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: true });
});

it('should trigger a callback with the clear filter but without querying when when calling the "clear" method with False as argument', () => {
Expand All @@ -231,14 +232,14 @@ describe('CompoundSliderFilter', () => {
filter.clear(false);

expect(filter.getValues()).toBe(0);
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
});

it('should trigger a callback with the clear filter set when calling the "clear" method and expect min slider values being with values of "sliderStartValue" when defined through the filter params', () => {
const filterArgs = { ...filterArguments, operator: '<=', searchTerms: [3], grid: gridStub, } as FilterArguments;
const callbackSpy = jest.spyOn(filterArguments, 'callback');
mockColumn.filter = {
params: {
filterOptions: {
sliderStartValue: 4,
sliderEndValue: 69,
}
Expand All @@ -248,7 +249,32 @@ describe('CompoundSliderFilter', () => {
filter.clear(false);

expect(filter.getValues()).toEqual(4);
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
});

it('should enableSliderTrackColoring and trigger a change event and expect slider track to have background color', () => {
mockColumn.filter = { filterOptions: { enableSliderTrackColoring: true } };
filter.init(filterArguments);
filter.setValues(['80']);
const filterElms = divContainer.querySelectorAll<HTMLInputElement>('.search-filter.slider-container.filter-duration input');
filterElms[0].dispatchEvent(new CustomEvent('change'));

expect(filter.sliderOptions?.sliderTrackBackground).toBe('linear-gradient(to right, #eee 0%, var(--slick-slider-filter-thumb-color, #86bff8) 0%, var(--slick-slider-filter-thumb-color, #86bff8) 80%, #eee 80%)');
});

it('should click on the slider track and expect handle to move to the new position', () => {
filter.init(filterArguments);
const sliderInputs = divContainer.querySelectorAll<HTMLInputElement>('.slider-filter-input');
const sliderTrackElm = divContainer.querySelector('.slider-track') as HTMLDivElement;

const sliderRightChangeSpy = jest.spyOn(sliderInputs[0], 'dispatchEvent');

const clickEvent = new Event('click');
Object.defineProperty(clickEvent, 'offsetX', { writable: true, configurable: true, value: 56 });
Object.defineProperty(sliderTrackElm, 'offsetWidth', { writable: true, configurable: true, value: 75 });
sliderTrackElm.dispatchEvent(clickEvent);

expect(sliderRightChangeSpy).toHaveBeenCalled();
});

it('should create the input filter with all available operators in a select dropdown options as a prepend element', () => {
Expand Down
@@ -1,6 +1,7 @@
import { Filters } from '../filters.index';
import { Column, FilterArguments, GridOption, SlickGrid, SlickNamespace } from '../../interfaces/index';
import { SliderFilter } from '../sliderFilter';
import { SingleSliderFilter } from '../singleSliderFilter';
import { TranslateServiceStub } from '../../../../../test/translateServiceStub';

const containerId = 'demo-container';
declare const Slick: SlickNamespace;
Expand All @@ -23,14 +24,16 @@ const gridStub = {
onHeaderRowMouseEnter: new Slick.Event(),
} as unknown as SlickGrid;

describe('SliderFilter', () => {
describe('SingleSliderFilter', () => {
let translateService: TranslateServiceStub;
let divContainer: HTMLDivElement;
let filter: SliderFilter;
let filter: SingleSliderFilter;
let filterArgs: FilterArguments;
let spyGetHeaderRow;
let mockColumn: Column;

beforeEach(() => {
translateService = new TranslateServiceStub();
divContainer = document.createElement('div');
divContainer.innerHTML = template;
document.body.appendChild(divContainer);
Expand All @@ -44,7 +47,7 @@ describe('SliderFilter', () => {
filterContainerElm: gridStub.getHeaderRowColumn(mockColumn.id)
};

filter = new SliderFilter();
filter = new SingleSliderFilter(translateService);
});

afterEach(() => {
Expand All @@ -61,16 +64,17 @@ describe('SliderFilter', () => {

expect(spyGetHeaderRow).toHaveBeenCalled();
expect(filterCount).toBe(1);
expect(filter.currentValue).toBe(0);
});

it('should have an aria-label when creating the filter', () => {
filter.init(filterArgs);
const filterInputElm = divContainer.querySelector('.search-filter.slider-container.filter-duration input') as HTMLInputElement;

expect(filterInputElm.getAttribute('aria-label')).toBe('Duration Search Filter');
expect(filterInputElm.ariaLabel).toBe('Duration Search Filter');
});

it('should call "setValues" and expect that value to be in the callback when triggered', () => {
it('should call "setValues" and expect that value, converted as a number, to be in the callback when triggered', () => {
const callbackSpy = jest.spyOn(filterArgs, 'callback');
const rowMouseEnterSpy = jest.spyOn(gridStub.onHeaderRowMouseEnter, 'notify');

Expand All @@ -81,11 +85,11 @@ describe('SliderFilter', () => {

jest.runAllTimers(); // fast-forward timer

expect(callbackSpy).toHaveBeenLastCalledWith(new CustomEvent('change'), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['2'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(new CustomEvent('change'), { columnDef: mockColumn, operator: 'EQ', searchTerms: [2], shouldTriggerQuery: true });
expect(rowMouseEnterSpy).toHaveBeenCalledWith({ column: mockColumn, grid: gridStub }, expect.anything());
});

it('should call "setValues" and expect that value, converted as a string, to be in the callback when triggered', () => {
it('should call "setValues" and expect that value, converted as a number, to be in the callback when triggered', () => {
const callbackSpy = jest.spyOn(filterArgs, 'callback');

filter.init(filterArgs);
Expand All @@ -98,7 +102,7 @@ describe('SliderFilter', () => {
const filterFilledElms = divContainer.querySelectorAll('.search-filter.slider-container.filter-duration.filled');

expect(filterFilledElms.length).toBe(1);
expect(callbackSpy).toHaveBeenLastCalledWith(new CustomEvent('change'), { columnDef: mockColumn, operator: 'EQ', searchTerms: ['13'], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(new CustomEvent('change'), { columnDef: mockColumn, operator: 'EQ', searchTerms: [3], shouldTriggerQuery: true });
});

it('should be able to call "setValues" and set empty values and the input to not have the "filled" css class', () => {
Expand Down Expand Up @@ -154,7 +158,7 @@ describe('SliderFilter', () => {

it('should create the input filter with min/max slider values being set by filter "sliderStartValue" and "sliderEndValue" through the filter params', () => {
mockColumn.filter = {
params: {
filterOptions: {
sliderStartValue: 4,
sliderEndValue: 69,
}
Expand All @@ -170,7 +174,7 @@ describe('SliderFilter', () => {

it('should create the input filter with default search terms range but without showing side numbers when "hideSliderNumber" is set in params', () => {
filterArgs.searchTerms = [3];
mockColumn.filter!.params = { hideSliderNumber: true };
mockColumn.filter!.filterOptions = { hideSliderNumber: true };

filter.init(filterArgs);

Expand All @@ -188,7 +192,7 @@ describe('SliderFilter', () => {
filter.clear();

expect(filter.getValues()).toBe(0);
expect(callbackSpy).toHaveBeenLastCalledWith(new Event('change'), { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: true });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: true });
});

it('should trigger a callback with the clear filter but without querying when when calling the "clear" method with False as argument', () => {
Expand All @@ -199,13 +203,13 @@ describe('SliderFilter', () => {
filter.clear(false);

expect(filter.getValues()).toBe(0);
expect(callbackSpy).toHaveBeenLastCalledWith(new Event('change'), { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
});

it('should trigger a callback with the clear filter set when calling the "clear" method and expect min slider values being with values of "sliderStartValue" when defined through the filter params', () => {
const callbackSpy = jest.spyOn(filterArgs, 'callback');
mockColumn.filter = {
params: {
filterOptions: {
sliderStartValue: 4,
sliderEndValue: 69,
}
Expand All @@ -215,6 +219,31 @@ describe('SliderFilter', () => {
filter.clear(false);

expect(filter.getValues()).toEqual(4);
expect(callbackSpy).toHaveBeenLastCalledWith(new Event('change'), { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
expect(callbackSpy).toHaveBeenLastCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, searchTerms: [], shouldTriggerQuery: false });
});

it('should enableSliderTrackColoring and trigger a change event and expect slider track to have background color', () => {
mockColumn.filter = { filterOptions: { enableSliderTrackColoring: true } };
filter.init(filterArgs);
filter.setValues(['80']);
const filterElms = divContainer.querySelectorAll<HTMLInputElement>('.search-filter.slider-container.filter-duration input');
filterElms[0].dispatchEvent(new CustomEvent('change'));

expect(filter.sliderOptions?.sliderTrackBackground).toBe('linear-gradient(to right, #eee 0%, var(--slick-slider-filter-thumb-color, #86bff8) 0%, var(--slick-slider-filter-thumb-color, #86bff8) 80%, #eee 80%)');
});

it('should click on the slider track and expect handle to move to the new position', () => {
filter.init(filterArgs);
const sliderInputs = divContainer.querySelectorAll<HTMLInputElement>('.slider-filter-input');
const sliderTrackElm = divContainer.querySelector('.slider-track') as HTMLDivElement;

const sliderRightChangeSpy = jest.spyOn(sliderInputs[0], 'dispatchEvent');

const clickEvent = new Event('click');
Object.defineProperty(clickEvent, 'offsetX', { writable: true, configurable: true, value: 56 });
Object.defineProperty(sliderTrackElm, 'offsetWidth', { writable: true, configurable: true, value: 75 });
sliderTrackElm.dispatchEvent(clickEvent);

expect(sliderRightChangeSpy).toHaveBeenCalled();
});
});

0 comments on commit fc4304b

Please sign in to comment.