Skip to content

Commit

Permalink
feat(filters): add missing Date Filters
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Mar 30, 2020
1 parent c897c7c commit 76c66a3
Show file tree
Hide file tree
Showing 19 changed files with 1,411 additions and 97 deletions.
25 changes: 2 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,8 @@ npm run test:watch
## TODO
#### Code
- [x] Aggregators (6)
- [ ] Editors
- [x] Autocomplete
- [x] Checkbox
- [ ] Date
- [x] Float
- [x] Integer
- [x] Long Text
- [x] Multiple Select
- [x] Single Select
- [x] Slider
- [x] Text
- [ ] Filters
- [x] Autocomplete
- [ ] Compound Date
- [x] Compound Input(s)
- [x] Compound Slider
- [ ] Date Range
- [x] Input(s)
- [x] Multiple Select
- [x] Single Select
- [x] Native Select
- [x] Slider
- [x] Slider Range
- [x] Editors (11)
- [x] Filters (17)
- [x] Formatters (31)
- [ ] Extensions
- [x] AutoTooltip
Expand Down
12 changes: 7 additions & 5 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
"postsass-build-task:scss-compile:bootstrap": "postcss --use autoprefixer --output dist/styles/css/slickgrid-theme-bootstrap.css dist/styles/css/slickgrid-theme-bootstrap.css",
"sass-build-task:scss-compile:material": "node-sass --source-map true src/styles/slickgrid-theme-material.scss -o dist/styles/css",
"postsass-build-task:scss-compile:material": "postcss --use autoprefixer --output dist/styles/css/slickgrid-theme-material.css dist/styles/css/slickgrid-theme-material.css",
"sass-build-task:scss-compile:se-material": "node-sass --source-map true src/styles/se-slickgrid-theme-material.scss -o dist/styles/css",
"postsass-build-task:scss-compile:se-material": "postcss --use autoprefixer --output dist/styles/css/se-slickgrid-theme-material.css dist/styles/css/se-slickgrid-theme-material.css",
"sass:build": "run-p sass-build-task:scss-compile:*",
"sass:copy": "cross-env copyfiles -f src/styles/*.scss dist/styles/sass",
"presass:watch:bootstrap": "cross-env npm run sass-build-task:scss-compile:bootstrap",
Expand Down Expand Up @@ -70,20 +72,20 @@
"autoprefixer": "^9.7.5",
"copyfiles": "^2.2.0",
"cross-env": "^7.0.2",
"jest": "^25.2.3",
"jest-cli": "^25.2.3",
"jest-environment-jsdom": "^25.2.3",
"jest": "^25.2.4",
"jest-cli": "^25.2.4",
"jest-environment-jsdom": "^25.2.4",
"jest-extended": "^0.11.5",
"jest-junit": "^10.0.0",
"jsdom": "^16.2.1",
"jsdom": "^16.2.2",
"jsdom-global": "^3.0.2",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "4.13.1",
"nodemon": "^2.0.2",
"npm-run-all": "^4.1.5",
"postcss-cli": "^7.1.0",
"rimraf": "^3.0.2",
"ts-jest": "^25.2.1",
"ts-jest": "^25.3.0",
"typescript": "^3.8.3"
},
"engines": {
Expand Down
320 changes: 320 additions & 0 deletions packages/common/src/filters/__tests__/compoundDateFilter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
import 'jest-extended';

import { Filters } from '..';
import { FieldType, OperatorType } from '../../enums/index';
import { Column, FilterArguments, GridOption } from '../../interfaces/index';
import { CompoundDateFilter } from '../compoundDateFilter';
import { TranslateServiceStub } from '../../../../../test/translateServiceStub';

const containerId = 'demo-container';

// define a <div> container to simulate the grid container
const template = `<div id="${containerId}"></div>`;

const gridOptionMock = {
enableFiltering: true,
enableFilterTrimWhiteSpace: true,
} as GridOption;

const gridStub = {
getOptions: () => gridOptionMock,
getColumns: jest.fn(),
getHeaderRowColumn: jest.fn(),
render: jest.fn(),
};

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

beforeEach(() => {
translateService = new TranslateServiceStub();

divContainer = document.createElement('div');
divContainer.innerHTML = template;
document.body.appendChild(divContainer);
spyGetHeaderRow = jest.spyOn(gridStub, 'getHeaderRowColumn').mockReturnValue(divContainer);

mockColumn = { id: 'finish', field: 'finish', filterable: true, outputType: FieldType.dateIso, filter: { model: Filters.compoundDate, operator: '>' } };

filterArguments = {
grid: gridStub,
columnDef: mockColumn,
callback: jest.fn()
};

filter = new CompoundDateFilter(translateService);
});

afterEach(() => {
filter.destroy();
});

it('should throw an error when trying to call init without any arguments', () => {
expect(() => filter.init(null)).toThrowError('[Slickgrid-Universal] A filter must always have an "init()" with valid arguments.');
});

it('should initialize the filter', () => {
filter.init(filterArguments);
const filterCount = divContainer.querySelectorAll('.form-group.search-filter.filter-finish').length;

expect(spyGetHeaderRow).toHaveBeenCalled();
expect(filterCount).toBe(1);
});

it('should have a placeholder when defined in its column definition', () => {
const testValue = 'test placeholder';
mockColumn.filter.placeholder = testValue;

filter.init(filterArguments);
const filterElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');

expect(filterElm.placeholder).toBe(testValue);
});

it('should hide the DOM element when the "hide" method is called', () => {
filter.init(filterArguments);
const spy = jest.spyOn(filter.flatInstance, 'close');
const calendarElm = document.body.querySelector<HTMLDivElement>('.flatpickr-calendar');
filter.hide();

expect(calendarElm).toBeTruthy();
expect(spy).toHaveBeenCalled();
});

it('should show the DOM element when the "show" method is called', () => {
filter.init(filterArguments);
const spy = jest.spyOn(filter.flatInstance, 'open');
const calendarElm = document.body.querySelector<HTMLDivElement>('.flatpickr-calendar');
filter.show();

expect(calendarElm).toBeTruthy();
expect(spy).toHaveBeenCalled();
});

it('should be able to retrieve default flatpickr options through the Getter', () => {
filter.init(filterArguments);

expect(filter.flatInstance).toBeTruthy();
expect(filter.flatpickrOptions).toEqual({
altFormat: 'Y-m-d',
altInput: true,
closeOnSelect: true,
dateFormat: 'Y-m-d',
defaultDate: '',
locale: 'en',
onChange: expect.anything(),
wrap: true,
});
});

it('should be able to call "setValues" and have that value set in the picker', () => {
const mockDate = '2001-01-02T16:02:02.239Z';
filter.init(filterArguments);
filter.setValues(mockDate);
expect(filter.currentDate).toEqual(mockDate);
});

it('should be able to call "setValues" as an array and have that value set in the picker', () => {
const mockDate = '2001-01-02T16:02:02.239Z';
filter.init(filterArguments);
filter.setValues([mockDate]);
expect(filter.currentDate).toEqual(mockDate);
});

it('should be able to call "setValues" with a value and an extra operator and expect it to be set as new operator', () => {
const mockDate = '2001-01-02T16:02:02.239Z';
filter.init(filterArguments);
filter.setValues([mockDate], OperatorType.greaterThanOrEqual);

const filterOperatorElm = divContainer.querySelector<HTMLInputElement>('.input-group-prepend.operator select');

expect(filter.currentDate).toEqual(mockDate);
expect(filterOperatorElm.value).toBe('>=');
});

it('should trigger input change event and expect the callback to be called with the date provided in the input', () => {
mockColumn.filter.filterOptions = { allowInput: true }; // change to allow input value only for testing purposes
mockColumn.filter.operator = '>';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
filterInputElm.value = '2001-01-02T16:02:02.239Z';
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(spyCallback).toHaveBeenCalledWith(undefined, {
columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true
});
});

it('should pass a different operator then trigger an input change event and expect the callback to be called with the date provided in the input', () => {
mockColumn.filter.filterOptions = { allowInput: true }; // change to allow input value only for testing purposes
mockColumn.filter.operator = '>';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
filterInputElm.value = '2001-01-02T16:02:02.239Z';
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true });
});

it('should create the input filter with a default search term when passed as a filter argument', () => {
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
mockColumn.filter.operator = '<=';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');

filterInputElm.focus();
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', { keyCode: 97, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(filter.currentDate).toBe('2000-01-01T05:00:00.000Z');
expect(filterInputElm.value).toBe('2000-01-01');
expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true });
});

it('should trigger an operator change event and expect the callback to be called with the searchTerms and operator defined', () => {
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
mockColumn.filter.operator = '>';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterSelectElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish select');

filterSelectElm.value = '<=';
filterSelectElm.dispatchEvent(new CustomEvent('change'));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true });
});

it('should work with different locale when locale is changed', () => {
translateService.setLocale('fr-CA'); // will be trimmed to "fr"
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
mockColumn.filter.operator = '<=';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
const calendarElm = document.body.querySelector<HTMLDivElement>('.flatpickr-calendar');
const selectonOptionElms = calendarElm.querySelectorAll<HTMLSelectElement>(' .flatpickr-monthDropdown-months option');

filter.show();

filterInputElm.focus();
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', { keyCode: 97, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(filter.currentDate).toBe('2000-01-01T05:00:00.000Z');
expect(filterInputElm.value).toBe('2000-01-01');
expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true });
expect(calendarElm).toBeTruthy();
expect(selectonOptionElms.length).toBe(12);
expect(selectonOptionElms[0].textContent).toBe('janvier');
});

it('should throw an error and use English locale when user tries to load an unsupported Flatpickr locale', () => {
translateService.setLocale('zx');
const consoleSpy = jest.spyOn(global.console, 'warn').mockReturnValue();

filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
mockColumn.filter.operator = '<=';

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
const calendarElm = document.body.querySelector<HTMLDivElement>('.flatpickr-calendar');
const selectonOptionElms = calendarElm.querySelectorAll<HTMLSelectElement>(' .flatpickr-monthDropdown-months option');

filter.show();

filterInputElm.focus();
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', { keyCode: 97, bubbles: true, cancelable: true }));

expect(consoleSpy).toHaveBeenCalledWith(expect.toInclude('[Slickgrid-Universal - CompoundDate Filter] It seems that "zx" is not a locale supported by Flatpickr'));
expect(selectonOptionElms.length).toBe(12);
expect(selectonOptionElms[0].textContent).toBe('January');
});

it('should trigger a callback with the clear filter set when calling the "clear" method', () => {
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
filter.clear();
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterInputElm.value).toBe('');
expect(filterFilledElms.length).toBe(0);
expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: true });
});

it('should trigger a callback with the clear filter but without querying when when calling the "clear" method with False as argument', () => {
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
filter.clear(false);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterInputElm.value).toBe('');
expect(filterFilledElms.length).toBe(0);
expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, clearFilterTriggered: true, shouldTriggerQuery: false });
});

it('should have a value with date & time in the picker when "enableTime" option is set and we trigger a change', () => {
mockColumn.filter.filterOptions = { enableTime: true, allowInput: true }; // change to allow input value only for testing purposes
mockColumn.outputType = FieldType.dateTimeIsoAmPm;
mockColumn.filter.operator = '>';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');
filterInputElm.value = '2001-01-02T16:02:02.000+05:00';
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', { keyCode: 13, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
// expect(filter.currentDate.toISOString()).toBe('2001-01-02T21:02:02.000Z');
expect(filterInputElm.value).toBe('2001-01-02 4:02:02 PM');
expect(spyCallback).toHaveBeenCalledWith(expect.anything(), {
columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true
});
});

it('should have a value with date & time in the picker when using no "outputType" which will default to UTC date', () => {
mockColumn.outputType = null;
filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z'];
mockColumn.filter.operator = '<=';
const spyCallback = jest.spyOn(filterArguments, 'callback');

filter.init(filterArguments);
const filterInputElm = divContainer.querySelector<HTMLInputElement>('.search-filter.filter-finish .flatpickr input.flatpickr');

filterInputElm.focus();
filterInputElm.dispatchEvent(new (window.window as any).KeyboardEvent('keyup', { keyCode: 97, bubbles: true, cancelable: true }));
const filterFilledElms = divContainer.querySelectorAll<HTMLInputElement>('.form-group.search-filter.filter-finish.filled');

expect(filterFilledElms.length).toBe(1);
expect(filter.currentDate).toBe('2000-01-01T05:00:00.000Z');
expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z');
expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true });
});
});
Loading

0 comments on commit 76c66a3

Please sign in to comment.