UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

6,947 lines 321 kB
import { DebugElement } from '@angular/core';
import { fakeAsync, TestBed, tick, flush, ComponentFixture } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { IgxInputDirective } from '../../directives/input/input.directive';
import { IgxGridComponent } from './grid.component';
import { UIInteractions, wait } from '../../test-utils/ui-interactions.spec';
import { configureTestSuite } from '../../test-utils/configure-suite';
import {
    IgxNumberFilteringOperand,
    IgxDateFilteringOperand,
    IgxBooleanFilteringOperand,
    IgxStringFilteringOperand,
    IgxDateTimeFilteringOperand,
    IgxTimeFilteringOperand
} from '../../data-operations/filtering-condition';
import { IgxDatePickerComponent } from '../../date-picker/date-picker.component';
import { IgxGridFilteringCellComponent } from '../filtering/base/grid-filtering-cell.component';
import { IgxGridHeaderComponent } from '../headers/grid-header.component';
import { IgxGridFilteringRowComponent } from '../filtering/base/grid-filtering-row.component';
import { GridFunctions, GridSelectionFunctions } from '../../test-utils/grid-functions.spec';
import { IgxBadgeComponent } from '../../badge/badge.component';
import { DefaultSortingStrategy, SortingDirection } from '../../data-operations/sorting-strategy';
import { IgxGridHeaderGroupComponent } from '../headers/grid-header-group.component';
import { changei18n, getCurrentResourceStrings } from '../../core/i18n/resources';
import { DatePipe } from '@angular/common';
import { FilteringExpressionsTree, IFilteringExpressionsTree } from '../../data-operations/filtering-expressions-tree';
import { FilteringLogic, IFilteringExpression } from '../../data-operations/filtering-expression.interface';
import { IgxChipComponent } from '../../chips/chip.component';
import { DisplayDensity } from '../../core/density';
import { SampleTestData } from '../../test-utils/sample-test-data.spec';
import {
    IgxGridFilteringComponent,
    IgxGridFilteringScrollComponent,
    IgxGridFilteringMCHComponent,
    IgxGridFilteringTemplateComponent,
    IgxGridFilteringESFEmptyTemplatesComponent,
    IgxGridFilteringESFTemplatesComponent,
    IgxGridFilteringESFLoadOnDemandComponent,
    CustomFilteringStrategyComponent,
    IgxGridExternalESFComponent,
    IgxGridExternalESFTemplateComponent,
    IgxGridDatesFilteringComponent,
    LoadOnDemandFilterStrategy
} from '../../test-utils/grid-samples.spec';
import { GridSelectionMode, FilterMode } from '../common/enums';
import { ControlsFunction } from '../../test-utils/controls-functions.spec';
import { FilteringStrategy, FormattedValuesFilteringStrategy } from '../../data-operations/filtering-strategy';
import { IgxInputGroupComponent } from '../../input-group/public_api';
import { formatDate } from '../../core/utils';
import { IgxCalendarComponent } from '../../calendar/calendar.component';

const DEBOUNCETIME = 30;
const FILTER_UI_ROW = 'igx-grid-filtering-row';
const FILTER_UI_CELL = 'igx-grid-filtering-cell';
const GRID_RESIZE_CLASS = '.igx-grid-th__resize-line';

describe('IgxGrid - Filtering Row UI actions #grid', () => {
    configureTestSuite((() => {
        return TestBed.configureTestingModule({
            imports: [
                NoopAnimationsModule,
                IgxGridFilteringComponent,
                IgxGridFilteringScrollComponent,
                IgxGridFilteringMCHComponent,
                IgxGridFilteringTemplateComponent,
                IgxGridDatesFilteringComponent
            ]
        });
    }));

    describe(null, () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;
        const today = SampleTestData.today;

        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
        }));

        // UI tests string column, empty input
        it('UI tests on string column changing conditions', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            expect(grid.rowList.length).toEqual(8);

            // iterate over not unary conditions when input is empty
            // starts with
            GridFunctions.openFilterDDAndSelectCondition(fix, 2);

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);

            // does not contain
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);

            // iterate over unary conditions
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);

            expect(grid.rowList.length).toEqual(4);
            verifyFilterRowUI(input, close, reset, false);

            GridFunctions.openFilterDDAndSelectCondition(fix, 0);

            expect(grid.rowList.length).toEqual(4);
            verifyFilterRowUI(input, close, reset, false);
        }));

        // UI tests string column with value in input
        it('UI tests on string column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];

            expect(grid.rowList.length).toEqual(8);

            // iterate over not unary conditions and fill the input
            // starts with
            GridFunctions.openFilterDDAndSelectCondition(fix, 2);
            GridFunctions.typeValueInFilterRowInput('Net', fix, input);
            tick();
            fix.detectChanges();

            verifyFilterUIPosition(filterUIRow, grid);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(1);
            expect(grid.getCellByColumn(0, 'ID').value).toEqual(2);

            // ends with
            GridFunctions.openFilterDDAndSelectCondition(fix, 3);
            GridFunctions.typeValueInFilterRowInput('script', fix, input);

            expect(grid.rowList.length).toEqual(2);
            verifyFilterRowUI(input, close, reset, false);

            // does not contain
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);
            GridFunctions.typeValueInFilterRowInput('script', fix, input);

            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(6);
        }));

        // UI tests number column
        it('UI tests on number column changing conditions', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'Downloads');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            let input = filterUIRow.query(By.directive(IgxInputDirective));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);

            // does not equal
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);

            // greater than
            GridFunctions.openFilterDDAndSelectCondition(fix, 2);

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);

            // iterate over unary conditions
            // empty
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);

            expect(grid.rowList.length).toEqual(1);
            verifyFilterRowUI(input, close, reset, false);

            // not empty
            GridFunctions.openFilterDDAndSelectCondition(fix, 7);

            expect(grid.rowList.length).toEqual(7);
            verifyFilterRowUI(input, close, reset, false);

            // changing from unary to not unary condition when input is empty - filtering should keep its state
            // open dropdown
            GridFunctions.openFilterDDAndSelectCondition(fix, 0);

            input = filterUIRow.query(By.directive(IgxInputDirective));
            expect(grid.rowList.length).toEqual(7);
            verifyFilterRowUI(input, close, reset, false);
        }));

        it('UI tests on number column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'Downloads');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];

            // iterate over not unary conditions and fill the input
            // does not equal
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);
            GridFunctions.typeValueInFilterRowInput(100, fix, input);

            expect(grid.rowList.length).toEqual(7);
            verifyFilterRowUI(input, close, reset, false);

            // less than
            GridFunctions.openFilterDDAndSelectCondition(fix, 3);
            expect(grid.rowList.length).toEqual(3);
            verifyFilterRowUI(input, close, reset, false);

            // greater than or equal to
            GridFunctions.openFilterDDAndSelectCondition(fix, 4);
            GridFunctions.typeValueInFilterRowInput(254, fix, input);

            expect(grid.rowList.length).toEqual(3);
            verifyFilterRowUI(input, close, reset, false);
        }));

        // UI tests boolean column
        it('UI tests on boolean column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'Released');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            tick();

            expect(grid.rowList.length).toEqual(8);

            verifyFilterUIPosition(filterUIRow, grid);

            // false condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 2);

            expect(grid.rowList.length).toEqual(2);
            verifyFilterRowUI(input, close, reset, false);

            // true condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);

            expect(grid.rowList.length).toEqual(3);
            verifyFilterRowUI(input, close, reset, false);

            // (all) condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 0);

            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset, false);

            // not null condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);

            expect(grid.rowList.length).toEqual(6);
            verifyFilterRowUI(input, close, reset, false);
        }));

        it('UI tests on boolean column open dropdown', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'Released');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const prefix = GridFunctions.getFilterRowPrefix(fix);

            input.triggerEventHandler('click', null);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix);

            UIInteractions.triggerEventHandlerKeyDown(' ', prefix);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix, false);

            UIInteractions.triggerEventHandlerKeyDown('Enter', input);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix);

            UIInteractions.triggerEventHandlerKeyDown('Tab', prefix);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix, false);

            UIInteractions.triggerEventHandlerKeyDown(' ', input);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix);
        }));

        // UI tests date column
        it('UI - should correctly filter date column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const expectedResults = GridFunctions.createDateFilterConditions(grid, today);

            // Today condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 4);

            expect(grid.rowList.length).toEqual(1);
            verifyFilterRowUI(input, close, reset, false);
            verifyFilterUIPosition(filterUIRow, grid);

            expect(grid.rowList.length).toEqual(1);

            // This Month condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(expectedResults[5]);

            // Last Month condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 7);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(expectedResults[0]);

            // Empty condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 12);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(2);
        }));

        it('UI - should correctly filter date column by \'equals\' filtering conditions', fakeAsync(() => {
            pending('This should be tested in the e2e test');
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            GridFunctions.openFilterDDAndSelectCondition(fix, 0);

            input.triggerEventHandler('click', null);
            tick();
            fix.detectChanges();

            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            const calendar = outlet.getElementsByClassName('igx-calendar')[0];

            const currentDay = calendar.querySelector('.igx-calendar__date--current');

            currentDay.dispatchEvent(new Event('click'));

            flush();
            fix.detectChanges();
            input.triggerEventHandler('change', null);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(1);
        }));

        it('Should correctly select month from month view datepicker/calendar component', fakeAsync(() => {
            pending('This should be tested in the e2e test');
            const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL));
            filteringCells[4].query(By.css('igx-chip')).nativeElement.click();
            tick();
            fix.detectChanges();
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const filterIcon = filterUIRow.query(By.css('igx-icon'));
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            filterIcon.nativeElement.click();
            tick();
            fix.detectChanges();

            input.nativeElement.click();
            tick();
            fix.detectChanges();

            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            let calendar = outlet.getElementsByClassName('igx-calendar')[0];

            calendar.querySelector('.igx-calendar__date--current');
            const monthView = calendar.querySelector('.igx-calendar-picker__date');

            monthView.dispatchEvent(new Event('click'));
            tick();
            fix.detectChanges();

            const firstMonth = calendar.querySelector('.igx-calendar__month');
            const firstMonthText = (firstMonth as HTMLElement).innerText;
            firstMonth.dispatchEvent(new Event('click'));
            tick();
            fix.detectChanges();

            calendar = outlet.getElementsByClassName('igx-calendar')[0];
            const month = calendar.querySelector('.igx-calendar-picker__date');

            expect(month.innerHTML.trim()).toEqual(firstMonthText);
        }));

        it('Should correctly select year from year view datepicker/calendar component', fakeAsync(() => {
            pending('This should be tested in the e2e test');
            const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL));
            filteringCells[4].query(By.css('igx-chip')).nativeElement.click();
            tick();
            fix.detectChanges();
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const filterIcon = filterUIRow.query(By.css('igx-icon'));
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            filterIcon.nativeElement.click();
            tick();
            fix.detectChanges();

            input.nativeElement.click();
            tick();
            fix.detectChanges();

            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            let calendar = outlet.getElementsByClassName('igx-calendar')[0];

            const monthView = calendar.querySelectorAll('.igx-calendar-picker__date')[1];
            monthView.dispatchEvent(new Event('click'));
            tick();
            fix.detectChanges();

            const firstMonth = calendar.querySelectorAll('.igx-calendar__year')[0];
            firstMonth.dispatchEvent(new Event('click'));
            tick();
            fix.detectChanges();

            calendar = outlet.getElementsByClassName('igx-calendar')[0];
            const month = calendar.querySelectorAll('.igx-calendar-picker__date')[1];

            const expectedResult = today.getFullYear() - 3;
            expect(month.innerHTML.trim()).toEqual(expectedResult.toString());
        }));

        // UI tests custom column
        it('UI tests on custom column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'AnotherField');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];

            GridFunctions.typeValueInFilterRowInput('a', fix, input);

            expect(grid.rowList.length).toEqual(1);
            expect(grid.getCellByColumn(0, 'AnotherField').value).toMatch('custom');
            verifyFilterRowUI(input, close, reset, false);
        }));

        it('Removing second condition removes the And/Or button', fakeAsync(() => {
            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression = {
                fieldName: 'ProductName',
                searchVal: 'g',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };
            const expression1 = {
                fieldName: 'ProductName',
                searchVal: 'I',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };
            filteringExpressionsTree.filteringOperands.push(expression);
            filteringExpressionsTree.filteringOperands.push(expression1);
            grid.filter('ProductName', null, filteringExpressionsTree);
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            verifyFilterUIPosition(filterUIRow, grid);

            expect(grid.rowList.length).toEqual(2);
            let andButton = fix.debugElement.queryAll(By.css('#operand'));
            expect(andButton.length).toEqual(1);

            // remove the second chip
            const secondChip = filterUIRow.queryAll(By.css('igx-chip'))[1];
            ControlsFunction.clickChipRemoveButton(secondChip.nativeElement);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(3);
            andButton = fix.debugElement.queryAll(By.css('#operand'));
            expect(andButton.length).toEqual(0);
        }));

        it('When filter column with value 0 and dataType number, filtering chip should be applied', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'Downloads');

            GridFunctions.typeValueInFilterRowInput(0, fix);

            GridFunctions.closeFilterRow(fix);

            const gridheaders = fix.debugElement.queryAll(By.css('igx-grid-header'));
            const headerOfTypeNumber = gridheaders.find(gh => gh.nativeElement.classList.contains('igx-grid-th--number'));
            const filterCellsForTypeNumber = headerOfTypeNumber.parent.query(By.css(FILTER_UI_CELL));
            expect(filterCellsForTypeNumber.queryAll(By.css('.igx-filtering-chips')).length).toBe(1);
        }));

        it('Should correctly create FilteringExpressionsTree and populate filterUI.', fakeAsync(() => {
            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression = {
                fieldName: 'ProductName',
                searchVal: 'Ignite',
                condition: IgxStringFilteringOperand.instance().condition('startsWith')
            };

            filteringExpressionsTree.filteringOperands.push(expression);
            grid.filteringExpressionsTree = filteringExpressionsTree;

            fix.detectChanges();

            expect(grid.rowList.length).toEqual(2);

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            GridFunctions.openFilterDD(fix.debugElement);
            fix.detectChanges();

            const filterUIContainer = fix.debugElement.queryAll(By.css(FILTER_UI_ROW))[0];
            const input = filterUIContainer.query(By.directive(IgxInputDirective));

            expect(ControlsFunction.getDropDownSelectedItem(filterUIContainer).nativeElement.textContent).toMatch('Starts With');
            expect(input.nativeElement.value).toMatch('Ignite');
        }));

        it('Should complete the filter when clicking the commit icon', fakeAsync(() => {
            const filterValue = 'an';
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            GridFunctions.typeValueInFilterRowInput(filterValue, fix);

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const filterChip = filterUIRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();

            grid.filteringRow.onCommitClick();
            tick(100);
            fix.detectChanges();

            expect(filterChip.componentInstance.selected).toBeFalsy();
        }));

        it('Should complete the filter when focusing out of the input', fakeAsync(() => {
            const filterValue = 'an';
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(16); // onConditionsChanged rAF

            GridFunctions.typeValueInFilterRowInput(filterValue, fix);

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const filterChip = filterUIRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();

            grid.nativeElement.focus();
            fix.detectChanges();
            tick(100);

            expect(filterChip.componentInstance.selected).toBeFalsy();
        }));

        it('UI - should use dropdown mode for the date picker', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const datePicker = filterUIRow.query(By.css('igx-date-picker'));
            expect(datePicker.componentInstance.mode).toBe('dropdown');
        }));

        it('Should not select all filter chips when switching columns', fakeAsync(() => {
            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression = {
                fieldName: 'ProductName',
                searchVal: 'Ignite',
                condition: IgxStringFilteringOperand.instance().condition('startsWith')
            };
            const expression1 = {
                fieldName: 'ProductName',
                searchVal: 'Angular',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };
            filteringExpressionsTree.filteringOperands.push(expression);
            filteringExpressionsTree.filteringOperands.push(expression1);
            grid.filter('ProductName', null, filteringExpressionsTree);
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'Downloads');

            const columnProductName = GridFunctions.getColumnHeader('ProductName', fix);
            columnProductName.triggerEventHandler('click', { stopPropagation: () => { } });
            fix.detectChanges();

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const filterChips = filterUIRow.queryAll(By.directive(IgxChipComponent));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            expect(filterChips.length).toEqual(2);
            expect(filterChips[0].componentInstance.selected).toBeFalsy();
            expect(filterChips[1].componentInstance.selected).toBeFalsy();
            expect(input.nativeElement.value).toMatch('');
        }));

        it('should render Filter chip for filterable columns and render empty cell for a column when filterable is set to false',
            fakeAsync(() => {
                grid.width = '1500px';
                fix.detectChanges();

                const filteringCells = GridFunctions.getFilteringCells(fix);
                const filteringChips = GridFunctions.getFilteringChips(fix);
                expect(filteringCells.length).toBe(8);
                expect(filteringChips.length).toBe(7);

                let idCellChips = GridFunctions.getFilteringChipPerIndex(fix, 0);
                expect(idCellChips.length).toBe(0);

                grid.getColumnByName('ID').filterable = true;
                fix.detectChanges();
                // tick(100);

                idCellChips = GridFunctions.getFilteringChipPerIndex(fix, 0);
                expect(idCellChips.length).toBe(1);
            }));

        it('should render correct input and dropdown in filter row for different column types',
            fakeAsync(/** showHideArrowButtons rAF */() => {
                pending('This should be tested in the e2e test');
                const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL));
                const stringCellChip = filteringCells[1].query(By.css('igx-chip'));
                const numberCellChip = filteringCells[2].query(By.css('igx-chip'));
                const boolCellChip = filteringCells[3].query(By.css('igx-chip'));
                const dateCellChip = filteringCells[4].query(By.css('igx-chip'));
                // open for string
                stringCellChip.triggerEventHandler('click', null);
                fix.detectChanges();

                checkUIForType('string', fix.debugElement);

                // close
                let filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
                let close = filterUIRow.queryAll(By.css('button'))[1];
                close.nativeElement.click();
                fix.detectChanges();

                // open for number
                numberCellChip.nativeElement.click();
                fix.detectChanges();
                checkUIForType('number', fix.debugElement);

                // close
                filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
                close = filterUIRow.queryAll(By.css('button'))[1];
                close.nativeElement.click();
                fix.detectChanges();

                // open for date
                dateCellChip.nativeElement.click();
                fix.detectChanges();
                checkUIForType('date', fix.debugElement);

                // close
                filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
                close = filterUIRow.queryAll(By.css('button'))[1];
                close.nativeElement.click();
                fix.detectChanges();

                // open for bool
                boolCellChip.nativeElement.click();
                fix.detectChanges();
                checkUIForType('bool', fix.debugElement);

                // close
                filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
                close = filterUIRow.queryAll(By.css('button'))[1];
                close.nativeElement.click();
                fix.detectChanges();
            }));

        it('should apply  multiple conditions to grid immediately while the filter row is still open', fakeAsync(() => {
            pending('This should be tested in the e2e test');
            const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL));
            const stringCellChip = filteringCells[1].query(By.css('igx-chip'));
            const numberCellChip = filteringCells[2].query(By.css('igx-chip'));
            const boolCellChip = filteringCells[3].query(By.css('igx-chip'));
            const dateCellChip = filteringCells[4].query(By.css('igx-chip'));
            // open for string
            stringCellChip.nativeElement.click();
            fix.detectChanges();

            GridFunctions.filterBy('Starts With', 'I', fix);
            expect(grid.rowList.length).toEqual(2);
            GridFunctions.filterBy('Ends With', 'r', fix);
            expect(grid.rowList.length).toEqual(1);

            // Reset and Close
            GridFunctions.resetFilterRow(fix);
            GridFunctions.closeFilterRow(fix);

            // open for number
            numberCellChip.nativeElement.click();
            fix.detectChanges();

            GridFunctions.filterBy('Less Than', '100', fix);
            expect(grid.rowList.length).toEqual(3);
            GridFunctions.filterBy('Greater Than', '10', fix);
            expect(grid.rowList.length).toEqual(1);

            // Reset and Close
            GridFunctions.resetFilterRow(fix);
            GridFunctions.closeFilterRow(fix);

            // open for bool
            boolCellChip.nativeElement.click();
            fix.detectChanges();

            GridFunctions.filterBy('False', '', fix);
            expect(grid.rowList.length).toEqual(2);
            GridFunctions.filterBy('Empty', '', fix);
            expect(grid.rowList.length).toEqual(3);

            // Reset and Close
            GridFunctions.resetFilterRow(fix);
            GridFunctions.closeFilterRow(fix);

            // open for date
            dateCellChip.nativeElement.click();
            fix.detectChanges();

            GridFunctions.filterBy('Today', '', fix);
            expect(grid.rowList.length).toEqual(1);
            GridFunctions.filterBy('Null', '', fix);
            expect(grid.rowList.length).toEqual(0);
        }));

        it('should render navigation arrows in the filtering row when chips don\'t fit.', fakeAsync(() => {
            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            for (let i = 0; i < 10; i++) {
                const expression = {
                    fieldName: 'ProductName',
                    searchVal: 'I',
                    condition: IgxStringFilteringOperand.instance().condition('startsWith')
                };
                filteringExpressionsTree.filteringOperands.push(expression);
            }
            grid.filter('ProductName', null, filteringExpressionsTree);
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(500);
            fix.detectChanges();

            expect(GridFunctions.getFilterRowLeftArrowButton(fix)).not.toBe(null);
            expect(GridFunctions.getFilterRowRightArrowButton(fix)).not.toBe(null);
        }));

        it('should update UI when chip is removed from filter row.', fakeAsync(() => {
            grid.filter('ProductName', 'I', IgxStringFilteringOperand.instance().condition('startsWith'));
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(2);

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // remove from row
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            GridFunctions.removeFilterChipByIndex(0, filterUIRow);
            tick(100);
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(8);
        }));

        it('should not render chip in header if condition that requires value is applied and then value is cleared in filter row.',
            fakeAsync(() => {
                grid.filter('ProductName', 'I', IgxStringFilteringOperand.instance().condition('startsWith'));
                fix.detectChanges();

                GridFunctions.clickFilterCellChip(fix, 'ProductName');
                tick(100);
                fix.detectChanges();

                const clearButton = GridFunctions.getFilterRowInputClearIcon(fix);
                clearButton.triggerEventHandler('click', null);
                tick(100);
                fix.detectChanges();

                GridFunctions.closeFilterRow(fix);
                tick(100);
                fix.detectChanges();

                // check no condition is applied
                expect(grid.rowList.length).toEqual(8);

                const filteringChips = GridFunctions.getFilteringChips(fix);
                expect(GridFunctions.getChipText(filteringChips[1])).toEqual('Filter');
            }));

        it('should reset the filter chips area when changing grid width', fakeAsync(() => {
            grid.width = '300px';
            fix.detectChanges();
            tick(100);

            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression1 = {
                fieldName: 'ProductName',
                searchVal: 'Ignite',
                condition: IgxStringFilteringOperand.instance().condition('startsWith')
            };

            const expression2 = {
                fieldName: 'ProductName',
                searchVal: 'test',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };

            filteringExpressionsTree.filteringOperands.push(expression1);
            filteringExpressionsTree.filteringOperands.push(expression2);
            grid.filter('ProductName', null, filteringExpressionsTree);
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(100);
            fix.detectChanges();

            expect(GridFunctions.getFilterRowLeftArrowButton(fix)).not.toBeNull();
            expect(GridFunctions.getFilterRowRightArrowButton(fix)).not.toBeNull();

            grid.width = '900px';
            fix.detectChanges();
            tick(200);

            expect(GridFunctions.getFilterRowLeftArrowButton(fix)).toBeNull();
            expect(GridFunctions.getFilterRowRightArrowButton(fix)).toBeNull();
        }));

        it('Should correctly update filtering row rendered when changing current column by clicking on a header.', fakeAsync(() => {
            pending('This should be tested in the e2e test');
            const headers = fix.debugElement.queryAll(By.directive(IgxGridHeaderComponent));
            const numberHeader = headers[2];
            const boolHeader = headers[3];
            const dateHeader = headers[4];
            const initialChips = fix.debugElement.queryAll(By.directive(IgxChipComponent));
            const stringCellChip = initialChips[0].nativeElement;

            stringCellChip.click();
            fix.detectChanges();

            checkUIForType('string', fix.debugElement);

            // Click on number column.
            numberHeader.nativeElement.click();
            fix.detectChanges();

            checkUIForType('number', fix.debugElement);

            // Click on boolean column
            boolHeader.nativeElement.click();
            fix.detectChanges();

            checkUIForType('bool', fix.debugElement);

            // Click on date column
            dateHeader.nativeElement.click();
            fix.detectChanges();

            checkUIForType('date', fix.debugElement);
        }));

        it('Should correctly render read-only input when selecting read-only condition and should create a chip.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            GridFunctions.openFilterDD(fix.debugElement);
            fix.detectChanges();

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const dropdownList = fix.debugElement.query(By.css('div.igx-drop-down__list-scroll'));
            const input = filteringRow.query(By.directive(IgxInputDirective));

            GridFunctions.selectFilteringCondition('Empty', dropdownList);
            fix.detectChanges();

            const chips = filteringRow.queryAll(By.directive(IgxChipComponent));
            expect(chips.length).toEqual(1);
            expect(chips[0].componentInstance.selected).toBeTruthy();
            expect(GridFunctions.getChipText(chips[0])).toEqual('Empty');
            expect(input.properties.readOnly).toBeTruthy();
        }));

        it('Should focus input .', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Open dropdown
            GridFunctions.openFilterDD(fix.debugElement);
            fix.detectChanges();

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const dropdownList = fix.debugElement.query(By.css('div.igx-drop-down__list-scroll'));
            const input = filteringRow.query(By.directive(IgxInputDirective));

            // Select condition with input
            GridFunctions.selectFilteringCondition('Contains', dropdownList);

            // Check focus is kept
            expect(document.activeElement).toEqual(input.nativeElement);

            // Set input and confirm
            GridFunctions.typeValueInFilterRowInput('a', fix, input);

            // Check a chip is created after input and is marked as selected.
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();
            expect(input.componentInstance.value).toEqual('a');

            UIInteractions.triggerEventHandlerKeyDown('Enter', input);
            fix.detectChanges();

            // Check focus is kept and chips is no longer selected.
            expect(filterChip.componentInstance.selected).toBeFalsy();
            expect(grid.rowList.length).toEqual(3);
            expect(document.activeElement).toEqual(input.nativeElement);
            expect(input.componentInstance.value).toEqual(null);

            GridFunctions.clickChip(filterChip);
            fix.detectChanges();

            expect(document.activeElement).toEqual(input.nativeElement);
            expect(input.componentInstance.value).toEqual('a');
            expect(filterChip.componentInstance.selected).toBeTruthy();
        }));

        it('should update UI when filtering via the API.', fakeAsync(() => {
            grid.width = '1600px';
            grid.columnList.get(1).width = '400px';
            fix.detectChanges();

            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression = {
                fieldName: 'ProductName',
                searchVal: 'Ignite',
                condition: IgxStringFilteringOperand.instance().condition('startsWith')
            };
            const expression1 = {
                fieldName: 'ProductName',
                searchVal: 'Angular',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };
            filteringExpressionsTree.filteringOperands.push(expression);
            filteringExpressionsTree.filteringOperands.push(expression1);
            grid.filter('ProductName', null, filteringExpressionsTree);
            grid.filter('Released', true, IgxBooleanFilteringOperand.instance().condition('false'));
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(0);
            const filteringCells = GridFunctions.getFilteringCells(fix);
            const stringCellChips = filteringCells[1].queryAll(By.css('igx-chip'));
            const boolCellChips = filteringCells[3].queryAll(By.css('igx-chip'));
            const strConnector = filteringCells[1].query(By.css('.igx-filtering-chips__connector'));

            expect(strConnector.nativeElement.textContent.trim()).toBe('And');
            expect(stringCellChips.length).toBe(2);
            expect(boolCellChips.length).toBe(1);

            expect(GridFunctions.getChipText(stringCellChips[0])).toBe('Ignite');
            expect(GridFunctions.getChipText(stringCellChips[1])).toBe('Angular');
            expect(GridFunctions.getChipText(boolCellChips[0])).toBe('False');
        }));

        it('should display view more icon in filter cell if chips don\'t fit in the cell.', fakeAsync(() => {
            grid.columnList.get(1).width = '200px';
            fix.detectChanges();

            const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            const expression = {
                fieldName: 'ProductName',
                searchVal: 'Ignite',
                condition: IgxStringFilteringOperand.instance().condition('startsWith')
            };
            const expression1 = {
                fieldName: 'ProductName',
                searchVal: 'for',
                condition: IgxStringFilteringOperand.instance().condition('contains')
            };
            filteringExpressionsTree.filteringOperands.push(expression);
            filteringExpressionsTree.filteringOperands.push(expression1);
            grid.filter('ProductName', null, filteringExpressionsTree);
            fix.detectChanges();

            // check 1 chip and view more icon is displayed.
            const chips = GridFunctions.getFilterChipsForColumn('ProductName', fix);
            expect(chips.length).toEqual(1);
            const fcIndicator = GridFunctions.getFilterIndicatorForColumn('ProductName', fix);

            const indicatorBadge = fcIndicator[0].query(By.directive(IgxBadgeComponent));
            expect(indicatorBadge).toBeTruthy();
            expect(indicatorBadge.nativeElement.innerText.trim()).toEqual('1');
        }));

        it('should select chip when open it from filter cell', fakeAsync(() => {
            grid.filter('ProductName', 'Ignite', IgxStringFilteringOperand.instance().condition('startsWith'));
            fix.detectChanges();

            GridFunctions.clickFilterCellChipUI(fix, 'ProductName');
            tick(100);
            fix.detectChanges();

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            const input = filteringRow.query(By.directive(IgxInputDirective));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();
            expect(input.componentInstance.value).toEqual('Ignite');

        }));

        it('Should allow setting filtering conditions through filteringExpressionsTree.', fakeAsync(() => {
            grid.columnList.get(1).width = '200px';
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'o', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            const colChips = GridFunctions.getFilterChipsForColumn('ProductName', fix);
            const colOperands = GridFunctions.getFilterOperandsForColumn('ProductName', fix);
            const colIndicator = GridFunctions.getFilterIndicatorForColumn('ProductName', fix);

            expect(grid.rowList.length).toEqual(2);
            expect(colChips.length).toEqual(1);
            expect(GridFunctions.getChipText(colChips[0])).toEqual('a');
            expect(colOperands.length).toEqual(0);

            const indicatorBadge = colIndicator[0].query(By.directive(IgxBadgeComponent));
            expect(indicatorBadge).toBeTruthy();
            expect(indicatorBadge.nativeElement.innerText.trim()).toEqual('1');
        }));

        it('Should close FilterRow when Escape is pressed.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            let filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));

            UIInteractions.triggerKeyDownEvtUponElem('Escape', filteringRow.nativeElement, true);
            tick(100);
            fix.detectChanges();

            filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeNull();
        }));

        it('Should correctly load default resource strings for filter row', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeDefined();

            const editingBtns = filteringRow.query(By.css('.igx-grid__filtering-row-editing-buttons'));
            const reset = editingBtns.queryAll(By.css('button'))[0];

            expect(reset.nativeElement.childNodes[1].textContent.trim()).toBe('Reset');
        }));

        it('Should correctly change resource strings for filter row using Changei18n.', fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringComponent);
            const strings = getCurrentResourceStrings();
            strings.igx_grid_filter = 'My filter';
            strings.igx_grid_filter_row_close = 'My close';
            changei18n(strings);
            fix.detectChanges();

            const initialChips = GridFunctions.getFilteringChips(fix);
            expect(GridFunctions.getChipText(initialChips[0])).toBe('My filter');

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeDefined();

            const editingBtns = filteringRow.query(By.css('.igx-grid__filtering-row-editing-buttons'));
            const reset = editingBtns.queryAll(By.css('button'))[0];
            const close = editingBtns.queryAll(By.css('button'))[1];

            expect(close.nativeElement.childNodes[1].textContent.trim()).toBe('My close');
            expect(reset.nativeElement.childNodes[1].textContent.trim()).toBe('Reset');

            changei18n({
                igx_grid_filter: 'Filter',
                igx_grid_filter_row_close: 'Close'
            });
        }));

        it('Should correctly change resource strings for filter row.', fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringComponent);
            grid = fix.componentInstance.grid;
            grid.resourceStrings = Object.assign({}, grid.resourceStrings, {
                igx_grid_filter: 'My filter',
                igx_grid_filter_row_close: 'My close'
            });
            fix.detectChanges();

            const initialChips = GridFunctions.getFilteringChips(fix);
            expect(GridFunctions.getChipText(initialChips[0])).toBe('My filter');

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeDefined();

            const editingBtns = filteringRow.query(By.css('.igx-grid__filtering-row-editing-buttons'));
            const reset = editingBtns.queryAll(By.css('button'))[0];
            const close = editingBtns.queryAll(By.css('button'))[1];

            expect(close.nativeElement.childNodes[1].textContent.trim()).toBe('My close');
            expect(reset.nativeElement.childNodes[1].textContent.trim()).toBe('Reset');
        }));

        it('should correctly apply locale to datePicker.', fakeAsync(() => {
            fix.detectChanges();

            grid.locale = 'de-DE';
            tick(300);
            fix.detectChanges();

            const initialChips = fix.debugElement.queryAll(By.directive(IgxChipComponent));
            const dateCellChip = initialChips[3].nativeElement;

            dateCellChip.click();
            fix.detectChanges();

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const datePicker = filteringRow.query(By.directive(IgxDatePickerComponent));
            datePicker.componentInstance.getEditElement().click();
            tick();
            fix.detectChanges();

            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            const calendar = outlet.getElementsByClassName('igx-calendar')[0];

            const sundayLabel = calendar.querySelectorAll('.igx-calendar__label')[0].innerHTML;

            expect(sundayLabel.trim()).toEqual('Mo');
        }));

        it('Should size grid correctly if enable/disable filtering in run time.', fakeAsync(() => {
            const head = grid.theadRow.nativeElement;
            const body = grid.nativeElement.querySelector('.igx-grid__tbody');

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);

            fix.componentInstance.activateFiltering(false);
            fix.detectChanges();

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);

            fix.componentInstance.activateFiltering(true);
            fix.detectChanges();

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);
        }));

        it('Should remove FilterRow, when allowFiltering is set to false.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            let filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeDefined();

            grid.allowFiltering = false;
            fix.detectChanges();

            filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeNull();
        }));

        it('should open \'conditions dropdown\' on prefix click and should close it on second click.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const prefix = GridFunctions.getFilterRowPrefix(fix);
            const event = {
                currentTarget: prefix.nativeElement,
                preventDefault: () => { },
                stopPropagation: () => { }
            };

            // Click prefix to open conditions dropdown
            prefix.triggerEventHandler('click', event);
            tick(100);
            fix.detectChanges();

            // Verify dropdown is opened
            GridFunctions.verifyFilteringDropDownIsOpened(fix);

            // Click prefix again to close conditions dropdown
            prefix.triggerEventHandler('click', event);
            tick(100);
            fix.detectChanges();

            // Verify dropdown is closed
            GridFunctions.verifyFilteringDropDownIsOpened(fix, false);
        }));

        it('should close \'conditions dropdown\' when navigate with Tab key', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const prefix = GridFunctions.getFilterRowPrefix(fix);
            const event = {
                currentTarget: prefix.nativeElement,
                preventDefault: () => { },
                stopPropagation: () => { }
            };

            // Click prefix to open conditions dropdown
            prefix.triggerEventHandler('click', event);
            tick(100);
            fix.detectChanges();

            // Verify dropdown is opened
            GridFunctions.verifyFilteringDropDownIsOpened(fix);

            // Press Tab key
            UIInteractions.triggerKeyDownEvtUponElem('Tab', prefix.nativeElement, true);
            tick(100);
            fix.detectChanges();

            // Verify dropdown is closed
            GridFunctions.verifyFilteringDropDownIsOpened(fix, false);
        }));

        it('should open \'conditions dropdown\' when press Alt+KyeDown on the input', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            UIInteractions.triggerEventHandlerKeyDown('ArrowDown', input, true);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            GridFunctions.verifyFilteringDropDownIsOpened(fix);
        }));

        it('should close filter row on Escape key pressed on the input', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            let filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            UIInteractions.triggerEventHandlerKeyDown('Escape', input);
            tick(100);
            fix.detectChanges();

            filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).toBeNull();
            expect(grid.filteringService.isFilterRowVisible).toBeFalsy();
        }));

        it('Should not commit the input when null value is added', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];
            const input = filterUIRow.query(By.directive(IgxInputDirective));

            expect(grid.rowList.length).toEqual(8);

            // select unary conditions
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);

            let filterChip = filterUIRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();
            expect(grid.rowList.length).toEqual(4);
            verifyFilterRowUI(input, close, reset, false);

            // select not unary conditions
            GridFunctions.openFilterDDAndSelectCondition(fix, 1);

            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();
            expect(grid.rowList.length).toEqual(4);
            verifyFilterRowUI(input, close, reset, false);

            // submit the input with empty value
            UIInteractions.triggerEventHandlerKeyDown('Enter', input);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            filterChip = filterUIRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeNull();
            expect(grid.rowList.length).toEqual(8);
            verifyFilterRowUI(input, close, reset);
        }));

        it('Should navigate keyboard focus correctly between the filter row and the grid cells.', fakeAsync(() => {
            pending(`The test needs refactoring. The dispatchEvent doesn't focus elements with tabindex over them.
                Also, the focus is now persistent over the tbody element`);
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const cell = grid.gridAPI.get_cell_by_index(0, 'ID');
            UIInteractions.simulateClickAndSelectEvent(cell);
            fix.detectChanges();

            cell.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true }));
            fix.detectChanges();

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const closeButton = filterUIRow.queryAll(By.css('button'))[1];
            expect(document.activeElement).toBe(closeButton.nativeElement);

            filterUIRow.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
            fix.detectChanges();
            tick();
            expect(document.activeElement).toBe(cell.nativeElement);
        }));

        it('should hide chip arrows when the grid is narrow and column is not filtered', fakeAsync(() => {
            grid.width = '400px';
            tick(200);
            fix.detectChanges();

            // Click string filter chip to show filter row.
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(200);

            // Verify arrows and chip area are not visible because there is no active filtering for the column.
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const chipArea = filteringRow.query(By.css('igx-chip-area'));
            expect(GridFunctions.getFilterRowLeftArrowButton(fix)).toBeNull();
            expect(GridFunctions.getFilterRowRightArrowButton(fix)).toBeNull();
            expect(chipArea).toBeNull('chipArea is present');
        }));

        it('Should remove first chip and filter by the remaining ones.', fakeAsync(() => {
            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'z', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'n', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'g', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(0);
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(200);

            // remove first chip
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            GridFunctions.removeFilterChipByIndex(0, filteringRow);
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(3);
            expect(filteringRow.queryAll(By.css('igx-chip')).length).toEqual(2);
        }));

        it('Should remove middle chip and filter by the remaining ones.', fakeAsync(() => {
            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'n', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'z', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'g', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(0);
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(200);

            // remove middle chip
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            GridFunctions.removeFilterChipByIndex(1, filteringRow);
            fix.detectChanges();
            expect(grid.rowList.length).toEqual(3);
            expect(filteringRow.queryAll(By.css('igx-chip')).length).toEqual(2);
        }));

        it('Verify filter cell chip is scrolled into view on click.', async () => {
            grid.width = '470px';
            await wait(100);
            fix.detectChanges();
            GridFunctions.getGridHeader(grid).nativeElement.focus(); //
            fix.detectChanges();

            // Verify 'ReleaseDate' filter chip is not fully visible.
            let chip = GridFunctions.getFilterChipsForColumn('ReleaseDate', fix)[0].nativeElement;
            let chipRect = chip.getBoundingClientRect();
            let gridRect = grid.nativeElement.getBoundingClientRect();
            expect(chipRect.right > gridRect.right).toBe(true,
                'chip should not be fully visible and thus not within grid');

            GridFunctions.clickFilterCellChipUI(fix, 'ReleaseDate');
            await wait(100);
            fix.detectChanges();

            grid.filteringRow.close();
            await wait();
            fix.detectChanges();

            // Verify 'ReleaseDate' filter chip is fully visible.
            chip = GridFunctions.getFilterChipsForColumn('ReleaseDate', fix)[0].nativeElement;
            chipRect = chip.getBoundingClientRect();
            gridRect = grid.nativeElement.getBoundingClientRect();
            expect(chipRect.left > gridRect.left && chipRect.right < gridRect.right).toBe(true,
                'chip should be fully visible and within grid');
        });

        it('Verify condition chips are scrolled into/(out of) view by using arrow buttons.', (async () => {
            grid.width = '700px';
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            await wait(150);
            fix.detectChanges();

            verifyMultipleChipsVisibility(fix, [true, false, false]);

            grid.filteringRow.scrollChipsOnArrowPress('right');
            await wait(150);
            fix.detectChanges();
            grid.filteringRow.scrollChipsOnArrowPress('right');
            await wait(150);

            fix.detectChanges();
            verifyMultipleChipsVisibility(fix, [false, true, false]);

            grid.filteringRow.scrollChipsOnArrowPress('left');
            await wait(150);
            fix.detectChanges();
            grid.filteringRow.scrollChipsOnArrowPress('left');
            await wait(150);
            fix.detectChanges();
            verifyMultipleChipsVisibility(fix, [true, false, false]);
        }));

        it('Should navigate from left arrow button to first condition chip Tab.', (async () => {
            grid.width = '700px';
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Add first chip.
            GridFunctions.typeValueInFilterRowInput('a', fix);
            await wait(16);
            fix.detectChanges();
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            fix.detectChanges();
            // Add second chip.
            GridFunctions.typeValueInFilterRowInput('e', fix);
            await wait(16);
            fix.detectChanges();
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            fix.detectChanges();
            // Add third chip.
            GridFunctions.typeValueInFilterRowInput('i', fix);
            await wait(16);
            fix.detectChanges();
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            fix.detectChanges();

            // Verify first chip is not in view.
            verifyChipVisibility(fix, 0, false);

            const leftArrowButton = GridFunctions.getFilterRowLeftArrowButton(fix).nativeElement;
            leftArrowButton.focus();
            await wait(16);
            fix.detectChanges();
            leftArrowButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }));
            fix.detectChanges();
            await wait(100);

            // Verify first chip is in view.
            verifyChipVisibility(fix, 0, true);
        }));

        it('Should toggle the selection of a condition chip when using \'Enter\' key.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Add chip.
            GridFunctions.typeValueInFilterRowInput('a', fix);
            tick(100);
            GridFunctions.submitFilterRowInput(fix);
            tick(100);

            // Verify chip is not selected.
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeFalsy();

            filterChip.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
            tick(100);
            fix.detectChanges();

            // Verify chip is selected.
            expect(filterChip.componentInstance.selected).toBeTruthy();

            filterChip.nativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
            fix.detectChanges();
            tick(100);

            // Verify chip is not selected.
            expect(filterChip.componentInstance.selected).toBeFalsy();
        }));

        it('Should commit the value in the input when pressing \'Enter\' on commit icon in input.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Type 'ang' in the filter row input.
            GridFunctions.typeValueInFilterRowInput('ang', fix);

            // Verify chip is selected (in edit mode).
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();

            // Press 'Enter' on the commit icon.
            const inputCommitIcon = GridFunctions.getFilterRowInputCommitIcon(fix);
            UIInteractions.triggerEventHandlerKeyDown('Enter', inputCommitIcon);
            tick(200);
            fix.detectChanges();

            // Verify chip is not selected (it is committed).
            expect(filterChip.componentInstance.selected).toBeFalsy();
        }));

        it('Should clear the value in the input when pressing \'Enter\' on clear icon in input.', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Type 'ang' in the filter row input.
            GridFunctions.typeValueInFilterRowInput('ang', fix);

            // Verify chip is selected (in edit mode).
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();

            // Press 'Enter' on the clear icon.
            const inputClearIcon = GridFunctions.getFilterRowInputClearIcon(fix);
            UIInteractions.triggerEventHandlerKeyDown('Enter', inputClearIcon);
            tick(200);
            fix.detectChanges();

            // Verify there are no chips since we cleared the input.
            const conditionChips = filteringRow.queryAll(By.directive(IgxChipComponent));
            expect(conditionChips.length).toBe(0);
        }));

        it(`Should open/close filterRow for respective column when pressing 'ctrl + shift + l' on its filterCell chip.`,
            fakeAsync(() => {
                // Verify filterRow is not opened.
                let filterUIRow = grid.theadRow.filterRow;
                expect(filterUIRow).toBeUndefined();

                const releaseDateColumn = GridFunctions.getColumnHeader('ReleaseDate', fix);
                UIInteractions.simulateClickAndSelectEvent(releaseDateColumn);
                fix.detectChanges();

                UIInteractions.triggerKeyDownEvtUponElem('l', releaseDateColumn.nativeElement, true, false, true, true);
                tick(200);
                fix.detectChanges();

                // Verify filterRow is opened for the 'ReleaseDate' column.
                filterUIRow = grid.theadRow.filterRow;
                expect(filterUIRow).toBeDefined();
                const headerGroupsFiltering = grid.headerGroupsList.filter(group => group.isFiltered);
                expect(headerGroupsFiltering.length).toBe(1);
                expect(headerGroupsFiltering[0].column.field).toMatch('ReleaseDate');

                UIInteractions.triggerKeyDownEvtUponElem('l', filterUIRow.nativeElement, true, false, true, true);
                tick(200);
                fix.detectChanges();

                filterUIRow = grid.theadRow.filterRow;
                expect(filterUIRow).toBeUndefined();
        }));

        it('Should navigate to first cell of grid when pressing \'Tab\' on the last filterCell chip.', fakeAsync(() => {
            pending('Should be fixed with headers navigation');
            const filterCellChip = GridFunctions.getFilterChipsForColumn('AnotherField', fix)[0];
            UIInteractions.triggerKeyDownEvtUponElem('Tab', filterCellChip.nativeElement, true);
            tick(200);
            fix.detectChanges();

            const firstCell: any = GridFunctions.getRowCells(fix, 0)[0].nativeElement;
            expect(document.activeElement).toBe(firstCell);
        }));

        it(`Should remove first condition chip when click 'clear' button and focus 'more' icon.`, fakeAsync(() => {
            grid.getColumnByName('ProductName').width = '160px';
            tick(DEBOUNCETIME);
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Verify active chip and its text.
            let filterCellChip = GridFunctions.getFilterChipsForColumn('ProductName', fix)[0];
            expect(GridFunctions.getChipText(filterCellChip)).toBe('a');

            // Remove active chip.
            ControlsFunction.clickChipRemoveButton(filterCellChip.nativeElement);
            tick(50);
            fix.detectChanges();

            const header = GridFunctions.getGridHeader(grid);
            expect(document.activeElement).toBe(header.nativeElement);

            // Verify new chip text.
            filterCellChip = GridFunctions.getFilterChipsForColumn('ProductName', fix)[0];
            expect(GridFunctions.getChipText(filterCellChip)).toBe('e');
        }));

        it(`Should focus 'grid header' when close filter row.`, fakeAsync(() => {
            grid.getColumnByName('ProductName').width = '80px';
            tick(DEBOUNCETIME);
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Click more icon
            const moreIcon = GridFunctions.getFilterIndicatorForColumn('ProductName', fix)[0];
            moreIcon.triggerEventHandler('click', null);
            tick(100);
            fix.detectChanges();

            // verify first chip is selected
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const filterChip = filteringRow.query(By.directive(IgxChipComponent));
            expect(filterChip).toBeTruthy();
            expect(filterChip.componentInstance.selected).toBeTruthy();

            // close filter row
            GridFunctions.closeFilterRow(fix);
            tick(DEBOUNCETIME);

            const header = GridFunctions.getGridHeader(grid);
            expect(document.activeElement).toEqual(header.nativeElement);
        }));

        it('Should update active element when click \'clear\' button of last chip and there is no \'more\' icon.', fakeAsync(() => {
            pending('This this is not valid anymore, so we should probably dellete it.');
            grid.getColumnByName('ProductName').width = '350px';
            tick(DEBOUNCETIME);
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Verify chips count.
            expect(GridFunctions.getFilterChipsForColumn('ProductName', fix).length).toBe(3, 'incorrect chips count');

            // Verify last chip text.
            let lastFilterCellChip = GridFunctions.getFilterChipsForColumn('ProductName', fix)[2];
            expect(GridFunctions.getChipText(lastFilterCellChip)).toBe('i');
            // Remove last chip.
            // Remove active chip.
            ControlsFunction.clickChipRemoveButton(lastFilterCellChip.nativeElement);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            // Verify chips count.
            expect(GridFunctions.getFilterChipsForColumn('ProductName', fix).length).toBe(2, 'incorrect chips count');
            // Verify new last chip text.
            lastFilterCellChip = GridFunctions.getFilterChipsForColumn('ProductName', fix)[1];
            expect(GridFunctions.getChipText(lastFilterCellChip)).toBe('e');

            // Verify that 'clear' icon div of the new last chip is now active.

            const clearIconDiv = ControlsFunction.getChipRemoveButton(lastFilterCellChip.nativeElement);
            expect(document.activeElement).toBe(clearIconDiv);
        }));

        it('Should open filterRow when clicking \'more\' icon', fakeAsync(() => {
            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Click 'more' icon
            const filterCell = GridFunctions.getFilterCell(fix, 'ProductName');
            const moreIcon: any = Array.from(filterCell.queryAll(By.css('igx-icon')))
                .find((ic: any) => ic.nativeElement.innerText === 'filter_list');
            moreIcon.nativeElement.click();
            tick(100);
            fix.detectChanges();

            // Verify filterRow is opened.
            expect(fix.debugElement.query(By.css(FILTER_UI_ROW))).not.toBeNull();

            // Verify first chip is selected (in edit mode).
            const chipDiv = GridFunctions.getFilterConditionChip(fix, 0).querySelector('.igx-chip__item');
            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(true, 'chip is not selected');
        }));

        it('Should not throw error when deleting the last chip', (async () => {
            pending('This should be tested in the e2e test');
            grid.width = '700px';
            fix.detectChanges();
            await wait(100);

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Add first chip.
            GridFunctions.typeValueInFilterRowInput('a', fix);
            await wait(16);
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            // Add second chip.
            GridFunctions.typeValueInFilterRowInput('e', fix);
            await wait(16);
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            // Add third chip.
            GridFunctions.typeValueInFilterRowInput('i', fix);
            await wait(16);
            GridFunctions.submitFilterRowInput(fix);
            await wait(100);
            // Add fourth chip.
            GridFunctions.typeValueInFilterRowInput('o', fix);
            await wait(16);
            GridFunctions.submitFilterRowInput(fix);
            await wait(200);

            verifyMultipleChipsVisibility(fix, [false, false, false, true]);
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const chips = filterUIRow.queryAll(By.directive(IgxChipComponent));
            expect(chips.length).toBe(4);

            const leftArrowButton = GridFunctions.getFilterRowLeftArrowButton(fix).nativeElement;
            expect(leftArrowButton).toBeTruthy('Left scroll arrow should be visible');

            const rightArrowButton = GridFunctions.getFilterRowRightArrowButton(fix).nativeElement;
            expect(rightArrowButton).toBeTruthy('Right scroll arrow should be visible');
            expect(grid.rowList.length).toBe(2);

            let chipToRemove = filterUIRow.componentInstance.expressionsList[3];
            expect(() => {
                filterUIRow.componentInstance.onChipRemoved(null, chipToRemove);
            })
                .not.toThrowError(/'id' of undefined/);
            fix.detectChanges();
            await wait(500);
            fix.detectChanges();

            chipToRemove = filterUIRow.componentInstance.expressionsList[2];
            expect(() => {
                filterUIRow.componentInstance.onChipRemoved(null, chipToRemove);
            })
                .not.toThrowError(/'id' of undefined/);
            fix.detectChanges();
            await wait(100);
        }));

        it('should scroll correct chip in view when one is deleted', async () => {
            grid.width = '800px';
            fix.detectChanges();
            await wait(DEBOUNCETIME);

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'e', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'n', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            await wait(DEBOUNCETIME);

            verifyMultipleChipsVisibility(fix, [true, true, false, false]);

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            GridFunctions.removeFilterChipByIndex(1, filterUIRow);
            // wait for chip to be scrolled in view
            fix.detectChanges();
            await wait(300);

            verifyMultipleChipsVisibility(fix, [true, true, false]);
            let chips = filterUIRow.queryAll(By.directive(IgxChipComponent));
            expect(chips.length).toBe(3);

            GridFunctions.removeFilterChipByIndex(0, filterUIRow);
            // wait for chip to be scrolled in view
            fix.detectChanges();
            await wait(300);
            fix.detectChanges();

            verifyMultipleChipsVisibility(fix, [true, true]);
            chips = filterUIRow.queryAll(By.directive(IgxChipComponent));
            expect(chips.length).toBe(2);
        });

        it('Unary conditions should be committable', fakeAsync(() => {
            grid.height = '700px';
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Check that the filterRow is opened
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            // Select Empty condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);

            const chip = filterUIRow.query(By.directive(IgxChipComponent));
            expect(chip.componentInstance.selected).toBeTruthy();
            grid.nativeElement.focus();
            grid.filteringRow.onInputGroupFocusout();
            fix.detectChanges();
            tick(100);
            expect(chip.componentInstance.selected).toBeFalsy();

            GridFunctions.clickChip(chip);
            fix.detectChanges();
            tick(100);
            expect(chip.componentInstance.selected).toBeTruthy();
        }));

        it('Should close filterRow when changing filterMode from \'quickFilter\' to \'excelStyleFilter\'', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Add a condition chip without submitting it.
            GridFunctions.typeValueInFilterRowInput('a', fix);
            tick(DEBOUNCETIME);

            // Change filterMode to 'excelStyleFilter`
            grid.filterMode = FilterMode.excelStyleFilter;
            fix.detectChanges();

            // Verify the the filterRow is closed.
            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).toBeNull('filterRow is visible');

            // Verify the ESF icons are visible.
            const thead = grid.theadRow.nativeElement;
            const filterIcons = thead.querySelectorAll('.igx-excel-filter__icon');
            expect(filterIcons.length).toEqual(6, 'incorrect esf filter icons count');

            // Verify the condition was submitted.
            const header = GridFunctions.getColumnHeader('ProductName', fix);
            const activeFilterIcon = header.nativeElement.querySelector('.igx-excel-filter__icon--filtered');
            expect(activeFilterIcon).toBeDefined('no active filter icon was found');
        }));

        it('Should clear non-unary conditions with null searchVal when close', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            fix.detectChanges();

            GridFunctions.openFilterDD(fix.debugElement);
            const dropdownList = fix.debugElement.query(By.css('div.igx-drop-down__list.igx-toggle'));
            GridFunctions.selectFilteringCondition('Empty', dropdownList);
            fix.detectChanges();
            GridFunctions.openFilterDD(fix.debugElement);
            GridFunctions.selectFilteringCondition('Contains', dropdownList);
            fix.detectChanges();
            GridFunctions.closeFilterRow(fix);

            const headerChip = GridFunctions.getFilterChipsForColumn('ProductName', fix);
            expect(headerChip.length).toBe(1);
        }));

        it('Should commit the input and new chip after focus out and should edit chip without creating new one.', fakeAsync(() => {
            // Click date filter chip to show filter row.
            const dateFilterCellChip = GridFunctions.getFilterChipsForColumn('ReleaseDate', fix)[0];
            dateFilterCellChip.nativeElement.click();
            tick(100);
            fix.detectChanges();

            // Click input to open calendar.
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const inputDebugElement = filteringRow.query(By.directive(IgxInputDirective));
            const input = inputDebugElement.nativeElement;
            input.click();
            tick(100);
            fix.detectChanges();

            // Click the today date.
            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            let calendar = outlet.getElementsByClassName('igx-calendar')[0];
            const todayDayItem: HTMLElement = calendar.querySelector('.igx-calendar__date--current');
            todayDayItem.focus();
            todayDayItem.click();
            grid.filteringRow.onInputGroupFocusout();
            tick(100);
            fix.detectChanges();

            // Verify the newly added chip is selected.
            const chip = GridFunctions.getFilterConditionChip(fix, 0);
            const chipDiv = chip.querySelector('.igx-chip__item');

            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(true, 'initial chip is committed');

            // Focus out
            grid.nativeElement.focus();
            grid.filteringRow.onInputGroupFocusout();
            tick(200);
            fix.detectChanges();

            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(false, 'initial chip is not committed');
            expect(input.value).toBe('', 'initial input value is present and not committed');

            chip.click();
            tick(200);
            fix.detectChanges();

            // Open calendar
            input.click();
            tick(100);
            fix.detectChanges();

            calendar = outlet.getElementsByClassName('igx-calendar')[0];

            // View years
            const yearView: HTMLElement = calendar.querySelectorAll('.igx-calendar-picker__date')[1] as HTMLElement;
            yearView.click();
            tick(100);
            fix.detectChanges();

            // Select the first year
            const firstYear: HTMLElement = calendar.querySelectorAll('.igx-calendar__year')[0] as HTMLElement;
            firstYear.click();
            tick(100);
            fix.detectChanges();

            // Select the first day
            const firstDayItem: HTMLElement = calendar.querySelector('.igx-calendar__date');
            firstDayItem.focus();
            firstDayItem.click();
            grid.filteringRow.onInputGroupFocusout();
            tick(200);
            fix.detectChanges();

            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(true, 'chip is committed');

            // Focus out
            grid.nativeElement.focus();
            grid.filteringRow.onInputGroupFocusout();
            tick(200);
            fix.detectChanges();
            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(false, 'chip is selected');

            // Check if we still have only one committed chip
            const chipsLength = GridFunctions.getAllFilterConditionChips(fix).length;

            expect(chipsLength).toBe(1, 'there is more than one chip');
            expect(chipDiv.classList.contains('igx-chip__item--selected')).toBe(false, 'chip is not committed');
            expect(input.value).toBe('', 'input value is present and not committed');
        }));

        it('should not retain expression values in cell filter after calling grid clearFilter() method.', fakeAsync(() => {
            // Click on 'ProductName' filter chip
            GridFunctions.clickFilterCellChipUI(fix, 'ProductName');
            fix.detectChanges();

            // Enter expression
            GridFunctions.typeValueInFilterRowInput('NetAdvantage', fix);
            tick(DEBOUNCETIME);
            GridFunctions.closeFilterRow(fix);
            fix.detectChanges();

            // Verify filtered data
            expect(grid.filteredData.length).toEqual(1);

            // Clear filters of all columns
            grid.clearFilter();
            fix.detectChanges();

            // Verify filtered data
            expect(grid.filteredData).toBeNull();

            // Click on 'ProductName' filter chip
            GridFunctions.clickFilterCellChipUI(fix, 'ProductName');
            fix.detectChanges();

            // Verify there are no chips since we cleared all filters
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const conditionChips = filteringRow.queryAll(By.directive(IgxChipComponent));
            expect(conditionChips.length).toBe(0);

            // Verify filtered data
            expect(grid.filteredData).toBeNull();
        }));

        it('should not reset expression when input is cleared', fakeAsync(() => {
            grid.filter('ProductName', 'I', IgxStringFilteringOperand.instance().condition('startsWith'));
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            grid.filteringRow.onClearClick();
            tick(100);
            fix.detectChanges();

            expect(grid.filteringRow.expression.condition.name).toEqual('startsWith');
        }));

        it('should reset expression when the condition is unary', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // iterate over unary conditions
            // empty
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);
            expect(grid.filteringRow.expression.condition.name).toEqual('empty');

            const filterUIRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const reset = filterUIRow.queryAll(By.css('button'))[0];

            reset.triggerEventHandler('click', null);
            tick(100);
            fix.detectChanges();

            expect(grid.filteringRow.expression.condition.name).toEqual('contains');
        }));

        it('should reset expression to selected unary condition', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const datePicker = filterUIRow.query(By.directive(IgxDatePickerComponent));

            // Equals condition
            datePicker.triggerEventHandler('click', null);
            tick();
            fix.detectChanges();

            const currentDay = document.querySelector('.igx-calendar__date--current');

            currentDay.dispatchEvent(new Event('click'));
            tick();
            fix.detectChanges();

            expect(grid.filteringRow.expression.condition.name).toEqual('equals');
            expect(grid.rowList.length).toEqual(1);

            // This Month condition
            const expectedResults = GridFunctions.createDateFilterConditions(grid, today);
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);
            tick();
            fix.detectChanges();

            expect(grid.filteringRow.expression.condition.name).toEqual('thisMonth');
            expect(grid.rowList.length).toEqual(expectedResults[5]);

            const conditionChips = filterUIRow.queryAll(By.directive(IgxChipComponent));
            expect(conditionChips.length).toBe(1);
        }));

        it('Should filter by cells formatted data when using FormattedValuesFilteringStrategy', fakeAsync(() => {
            const formattedStrategy = new FormattedValuesFilteringStrategy(['Downloads']);
            grid.filterStrategy = formattedStrategy;
            const downloadsFormatter = (val: number): number => {
                if (!val || val > 0 && val < 100) {
                    return 1;
                } else if (val >= 100 && val < 500) {
                    return 2;
                } else {
                    return 3;
                }
            };
            grid.columnList.get(2).formatter = downloadsFormatter;
            fix.detectChanges();

            GridFunctions.clickFilterCellChipUI(fix, 'Downloads');
            fix.detectChanges();

            GridFunctions.typeValueInFilterRowInput('3', fix);
            tick(DEBOUNCETIME);
            GridFunctions.closeFilterRow(fix);
            fix.detectChanges();

            // const cells = GridFunctions.getColumnCells(fix, 'Downloads');
            const rows = GridFunctions.getRows(fix);
            expect(rows.length).toEqual(2);
        }));

        it('Should filter by cells formatted data when using FormattedValuesFilteringStrategy with rowData', fakeAsync(() => {
            const formattedStrategy = new FormattedValuesFilteringStrategy(['ProductName']);
            grid.filterStrategy = formattedStrategy;
            const anotherFieldFormatter = (value: any, rowData: any) => rowData.ID + ':' + value;
            grid.columnList.get(1).formatter = anotherFieldFormatter;
            fix.detectChanges();

            GridFunctions.clickFilterCellChipUI(fix, 'ProductName');
            fix.detectChanges();

            GridFunctions.typeValueInFilterRowInput('1:', fix);
            tick(DEBOUNCETIME);
            GridFunctions.closeFilterRow(fix);
            fix.detectChanges();

            const rows = GridFunctions.getRows(fix);
            expect(rows.length).toEqual(1);
        }));

        it('Should remove pending chip via its close button #9333', fakeAsync(() => {
            GridFunctions.clickFilterCellChipUI(fix, 'Downloads');
            fix.detectChanges();

            GridFunctions.typeValueInFilterRowInput('3', fix);
            tick(DEBOUNCETIME);
            const inputGroup = fix.debugElement.query(By.directive(IgxInputGroupComponent));
            const filterRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const pendingChip = filterRow.queryAll(By.directive(IgxChipComponent))[0];
            const chipCloseButton = pendingChip.query(By.css('div.igx-chip__remove')).nativeElement;

            chipCloseButton.dispatchEvent(new Event('mousedown'));
            inputGroup.nativeElement.dispatchEvent(new Event('focusout'));
            chipCloseButton.dispatchEvent(new Event('mouseup'));
            chipCloseButton.dispatchEvent(new Event('click'));
            tick(DEBOUNCETIME);
            fix.detectChanges();

            const chips = filterRow.queryAll(By.directive(IgxChipComponent));
            expect(chips.length).toEqual(0, 'No chips should be present');
        }));

        it('Should not throw error when pressing Backspace in empty dateTime filter.', fakeAsync(() => {
            spyOn(console, 'error');

            GridFunctions.clickFilterCellChipUI(fix, 'ReleaseDateTime');
            tick(DEBOUNCETIME);
            fix.detectChanges();

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            GridFunctions.typeValueInFilterRowInput('', fix, input);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            UIInteractions.triggerEventHandlerKeyDown('Backspace', input);
            fix.detectChanges();

            expect(console.error).not.toHaveBeenCalled();
        }));
    });

    describe('Integration scenarios', () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;

        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
        }));

        // Filtering + Row Selectors
        it('should display the Row Selector header checkbox above the filter row.', fakeAsync(() => {
            grid.rowSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const frElem = filteringRow.nativeElement;
            const chkBoxElem = GridSelectionFunctions.getRowCheckboxInput(GridSelectionFunctions.getHeaderRow(fix));
            expect(frElem.offsetTop).toBeGreaterThanOrEqual(chkBoxElem.offsetTop + chkBoxElem.clientHeight);
        }));

        // Filtering + Moving
        it('should move chip under the correct column when column is moved and filter row should open for correct column.',
            fakeAsync(() => {
                grid.filter('ProductName', 'Angular', IgxStringFilteringOperand.instance().condition('contains'));
                fix.detectChanges();

                // swap columns
                const stringCol = grid.getColumnByName('ProductName');
                const numberCol = grid.getColumnByName('Downloads');
                grid.moveColumn(stringCol, numberCol);
                tick();
                fix.detectChanges();

                // check UI in filter cell is correct after moving
                const filteringCells = GridFunctions.getFilteringCells(fix);

                expect(GridFunctions.getChipText(filteringCells[2])).toEqual('Angular');
                expect(GridFunctions.getChipText(filteringCells[1])).toEqual('Filter');
            }));

        // Filtering + Hiding
        it('should not display filter cell for hidden columns and chips should show under correct column.', fakeAsync(() => {
            grid.filter('ProductName', 'Angular', IgxStringFilteringOperand.instance().condition('contains'));
            fix.detectChanges();

            let filteringCells = GridFunctions.getFilteringCells(fix);
            expect(filteringCells.length).toEqual(8);

            // hide column
            grid.getColumnByName('ID').hidden = true;
            fix.detectChanges();

            filteringCells = GridFunctions.getFilteringCells(fix);
            expect(filteringCells.length).toEqual(7);
            expect(GridFunctions.getChipText(filteringCells[0])).toEqual('Angular');

            grid.getColumnByName('ProductName').hidden = true;
            fix.detectChanges();

            filteringCells = GridFunctions.getFilteringCells(fix);
            expect(filteringCells.length).toEqual(6);

            for (const filterCell of filteringCells) {
                expect(GridFunctions.getChipText(filterCell)).toEqual('Filter');
            }
        }));

        it('Should close filter row when hide the current column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            // Check that the filterRow is opened
            let filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).not.toBeNull();

            // Add first chip.
            GridFunctions.typeValueInFilterRowInput('a', fix);
            tick(100);

            grid.getColumnByName('ProductName').hidden = true;
            tick(100);
            fix.detectChanges();

            // Check that the filterRow is closed
            filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).toBeNull();
            expect(grid.rowList.length).toBe(3, 'filter is not applied');
        }));

        it('Should keep existing column filter after hiding another column.', fakeAsync(() => {
            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'x', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'y', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'i', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'g', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'n', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Open filter row for 'ProductName' column and add 4 condition chips.
            GridFunctions.clickFilterCellChip(fix, 'ProductName');
            tick(200);
            fix.detectChanges();

            // Change second operator to 'Or' and verify the results.
            GridFunctions.clickChipOperator(fix, 1);
            fix.detectChanges();
            GridFunctions.clickChipOperatorValue(fix, 'Or');
            tick(100);
            fix.detectChanges();
            expect(grid.rowList.length).toEqual(2);
            expect(GridFunctions.getCurrentCellFromGrid(grid, 0, 1).value).toBe('Ignite UI for JavaScript');
            expect(GridFunctions.getCurrentCellFromGrid(grid, 1, 1).value).toBe('Ignite UI for Angular');

            // Hide another column and verify the filtering results remain the same.
            const column = grid.columnList.find((c) => c.field === 'Released');
            column.hidden = true;
            fix.detectChanges();
            expect(grid.rowList.length).toEqual(2);
            expect(GridFunctions.getCurrentCellFromGrid(grid, 0, 1).value).toBe('Ignite UI for JavaScript');
            expect(GridFunctions.getCurrentCellFromGrid(grid, 1, 1).value).toBe('Ignite UI for Angular');
        }));

        // Filtering + Grouping
        it('should display the header expand/collapse icon for groupby above the filter row.', fakeAsync(() => {
            grid.getColumnByName('ProductName').groupable = true;
            grid.groupBy({
                fieldName: 'ProductName',
                dir: SortingDirection.Asc,
                ignoreCase: false,
                strategy: DefaultSortingStrategy.instance()
            });
            fix.detectChanges();

            GridFunctions.clickFilterCellChip(fix, 'ProductName');

            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const frElem = filteringRow.nativeElement;
            const expandBtn = fix.debugElement.query(By.css('.igx-grid__group-expand-btn'));
            const expandBtnElem = expandBtn.nativeElement;
            expect(frElem.offsetTop).toBeGreaterThanOrEqual(expandBtnElem.offsetTop + expandBtnElem.clientHeight);
        }));

        // Filtering + Pinning
        it('should position chips correctly after pinning column.', () => {
            grid.filter('ProductName', 'Angular', IgxStringFilteringOperand.instance().condition('contains'));
            fix.detectChanges();

            grid.getColumnByName('ProductName').pinned = true;
            fix.detectChanges();

            // check chips is under correct column
            const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL));
            const stringCellChip = filteringCells[0].query(By.css('igx-chip'));
            expect(GridFunctions.getChipText(stringCellChip)).toEqual('Angular');
        });

        it('Should display view more indicator when column is resized so not all filters are visible.', fakeAsync(() => {
            grid.columnList.get(1).width = '250px';
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'a', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'o', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            let colChips = GridFunctions.getFilterChipsForColumn('ProductName', fix);
            let colOperands = GridFunctions.getFilterOperandsForColumn('ProductName', fix);
            let colIndicator = GridFunctions.getFilterIndicatorForColumn('ProductName', fix);

            expect(colChips.length).toEqual(2);
            expect(colOperands.length).toEqual(1);
            expect(colIndicator.length).toEqual(0);

            // Enable resizing
            fix.componentInstance.resizable = true;
            fix.detectChanges();
            grid.cdr.detectChanges();

            // Make 'ProductName' column smaller

            const headers: DebugElement[] = fix.debugElement.queryAll(By.directive(IgxGridHeaderGroupComponent));
            const headerResArea = headers[1].children[2].nativeElement;
            UIInteractions.simulateMouseEvent('mousedown', headerResArea, 200, 0);
            tick(200);
            const resizer = fix.debugElement.queryAll(By.css(GRID_RESIZE_CLASS))[0].nativeElement;
            expect(resizer).toBeDefined();
            UIInteractions.simulateMouseEvent('mousemove', resizer, 100, 5);
            UIInteractions.simulateMouseEvent('mouseup', resizer, 100, 5);
            fix.detectChanges();

            colChips = GridFunctions.getFilterChipsForColumn('ProductName', fix);
            colOperands = GridFunctions.getFilterOperandsForColumn('ProductName', fix);
            colIndicator = GridFunctions.getFilterIndicatorForColumn('ProductName', fix);

            expect(colChips.length).toEqual(1);
            expect(GridFunctions.getChipText(colChips[0])).toEqual('a');
            expect(colOperands.length).toEqual(0);
            expect(colIndicator.length).toEqual(1);

            const indicatorBadge = colIndicator[0].query(By.directive(IgxBadgeComponent));
            expect(indicatorBadge).toBeTruthy();
            expect(indicatorBadge.nativeElement.innerText.trim()).toEqual('1');
        }));

        it('Should correctly resize the current column that filtering the row is rendered for.', fakeAsync(() => {
            grid.columnList.get(1).width = '250px';
            fix.detectChanges();

            // Enable resizing
            grid.columnList.forEach(col => col.resizable = true);
            fix.detectChanges();

            const initialChips = fix.debugElement.queryAll(By.directive(IgxChipComponent));
            const stringCellChip = initialChips[0].nativeElement;
            stringCellChip.click();
            fix.detectChanges();

            const headers: DebugElement[] = fix.debugElement.queryAll(By.directive(IgxGridHeaderGroupComponent));
            const headerResArea = headers[1].children[2].nativeElement;
            let filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));

            expect(filteringRow).toBeTruthy();
            expect(headers[1].nativeElement.offsetWidth).toEqual(250);

            UIInteractions.simulateMouseEvent('mousedown', headerResArea, 200, 0);
            tick(200);
            const resizer = fix.debugElement.queryAll(By.css(GRID_RESIZE_CLASS))[0].nativeElement;
            expect(resizer).toBeDefined();
            UIInteractions.simulateMouseEvent('mousemove', resizer, 100, 5);
            UIInteractions.simulateMouseEvent('mouseup', resizer, 100, 5);
            fix.detectChanges();

            filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            expect(filteringRow).toBeTruthy();
            expect(headers[1].nativeElement.offsetWidth).toEqual(150);
        }));

        // Filtering + Resizing
        it('Should correctly render all filtering chips when column is resized so all filter are visible.', fakeAsync(() => {
            grid.columnList.get(2).width = '100px';
            fix.detectChanges();

            // Add initial filtering conditions
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 25, condition: IgxNumberFilteringOperand.instance().condition('greaterThan') },
                { fieldName: 'Downloads', searchVal: 200, condition: IgxNumberFilteringOperand.instance().condition('lessThan') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            // Enable resizing
            grid.columnList.forEach(col => col.resizable = true);
            fix.detectChanges();
            grid.cdr.detectChanges();

            let colChips = GridFunctions.getFilterChipsForColumn('Downloads', fix);
            let colOperands = GridFunctions.getFilterOperandsForColumn('Downloads', fix);
            let colIndicator = GridFunctions.getFilterIndicatorForColumn('Downloads', fix);

            expect(colChips.length).toEqual(0);
            expect(colOperands.length).toEqual(0);
            expect(colIndicator.length).toEqual(1);

            const indicatorBadge = colIndicator[0].query(By.directive(IgxBadgeComponent));
            expect(indicatorBadge).toBeTruthy();
            expect(indicatorBadge.nativeElement.innerText.trim()).toEqual('2');

            // Make 'Downloads' column bigger
            const headers: DebugElement[] = fix.debugElement.queryAll(By.directive(IgxGridHeaderGroupComponent));
            const headerResArea = headers[2].children[2].nativeElement;
            UIInteractions.simulateMouseEvent('mousedown', headerResArea, 100, 0);
            tick(200);
            const resizer = fix.debugElement.queryAll(By.css(GRID_RESIZE_CLASS))[0].nativeElement;
            expect(resizer).toBeDefined();
            UIInteractions.simulateMouseEvent('mousemove', resizer, 300, 5);
            UIInteractions.simulateMouseEvent('mouseup', resizer, 300, 5);
            fix.detectChanges();

            colChips = GridFunctions.getFilterChipsForColumn('Downloads', fix);
            colOperands = GridFunctions.getFilterOperandsForColumn('Downloads', fix);
            colIndicator = GridFunctions.getFilterIndicatorForColumn('Downloads', fix);

            expect(colChips.length).toEqual(2);
            expect(colOperands.length).toEqual(1);
            expect(colOperands[0].nativeElement.innerText).toEqual('AND');
            expect(colIndicator.length).toEqual(0);
        }));

        it('UI focusing grid\'s body content does not throw a console error after filtering. (issue 8930)', fakeAsync(() => {
            spyOn(console, 'error');
            GridFunctions.clickFilterCellChipUI(fix, 'ProductName');
            fix.detectChanges();

            GridFunctions.applyFilter('Jav', fix);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            GridFunctions.applyFilter('xy', fix);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            const tBodyContent = GridFunctions.getGridContent(fix);
            tBodyContent.triggerEventHandler('focus', null);

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            GridFunctions.removeFilterChipByIndex(1, filterUIRow);
            tick();
            fix.detectChanges();

            tBodyContent.triggerEventHandler('focus', null);
            tick();
            expect(console.error).not.toHaveBeenCalled();
        }));
    });

    describe(null, () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringMCHComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
        }));

        // Filtering + Column Groups
        it('should size correctly the header based on display density.', () => {
            grid.displayDensity = "comfortable";
            fix.detectChanges();

            const thead = GridFunctions.getGridHeader(grid).nativeElement;
            expect(thead.getBoundingClientRect().height).toEqual(grid.defaultRowHeight * 4 + 1);

            grid.displayDensity = "cosy";
            fix.detectChanges();
            expect(thead.getBoundingClientRect().height).toEqual(grid.defaultRowHeight * 4 + 1);

            grid.displayDensity = "compact";
            fix.detectChanges();
            expect(thead.getBoundingClientRect().height).toEqual(grid.defaultRowHeight * 4 + 1);

        });

        it('should position filter row correctly when grid has column groups.', fakeAsync(() => {
            const thead = GridFunctions.getGridHeader(grid).nativeElement;

            const filteringCells = GridFunctions.getFilteringCells(fix);
            const cellElem = filteringCells[0].nativeElement;
            expect(cellElem.offsetParent.offsetHeight + cellElem.offsetHeight + 1).toBeCloseTo(thead.clientHeight, 10);

            GridFunctions.clickFilterCellChip(fix, 'ID');

            // check if it is positioned at the bottom of the thead.
            const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
            const frElem = filteringRow.nativeElement;
            expect(frElem.offsetTop + frElem.clientHeight + 1).toEqual(thead.clientHeight);
        }));

        it('should position filter row and chips correctly when grid has column groups and one is hidden.',
            fakeAsync(() => {
                const filteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And, 'ProductName');
                const expression = {
                    fieldName: 'ProductName',
                    searchVal: 'Ignite',
                    condition: IgxStringFilteringOperand.instance().condition('startsWith')
                };
                filteringExpressionsTree.filteringOperands.push(expression);
                grid.filteringExpressionsTree = filteringExpressionsTree;
                fix.detectChanges();
                let filteringCells = GridFunctions.getFilteringCells(fix);
                expect(filteringCells.length).toEqual(6);

                const groupCol = grid.getColumnByName('General');
                groupCol.hidden = true;
                fix.detectChanges();

                filteringCells = GridFunctions.getFilteringCells(fix);
                expect(filteringCells.length).toEqual(1);

                GridFunctions.clickFilterCellChip(fix, 'AnotherField');
                fix.detectChanges();

                // check if it is positioned at the bottom of the thead.
                const theadWrapper = grid.theadRow.nativeElement.firstElementChild;
                const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
                const frElem = filteringRow.nativeElement;
                expect(frElem.offsetTop + frElem.clientHeight).toEqual(theadWrapper.clientHeight);

                GridFunctions.closeFilterRow(fix);

                groupCol.hidden = false;
                fix.detectChanges();

                filteringCells = GridFunctions.getFilteringCells(fix);
                expect(filteringCells.length).toEqual(6);

                expect(GridFunctions.getChipText(filteringCells[1])).toEqual('Ignite');
            }));

        it('Should size grid correctly if enable/disable filtering in run time - MCH.', fakeAsync(() => {
            const head = grid.theadRow.nativeElement;
            const body = grid.nativeElement.querySelector('.igx-grid__tbody');

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);

            grid.allowFiltering = false;
            fix.detectChanges();

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);

            grid.allowFiltering = false;
            fix.detectChanges();

            expect(head.getBoundingClientRect().bottom).toEqual(body.getBoundingClientRect().top);
        }));
    });

    describe(null, () => {
        let fix; let grid;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringScrollComponent);
            grid = fix.componentInstance.grid;
            fix.detectChanges();
        }));

        it('Should correctly update empty filter cells when scrolling horizontally.', async () => {
            let emptyFilterCells = fix.debugElement.queryAll(By.directive(IgxGridFilteringCellComponent))
                .filter((cell) => cell.nativeElement.children.length === 0);
            expect(emptyFilterCells.length).toEqual(1);

            let emptyFilterHeader = emptyFilterCells[0].parent.query(By.directive(IgxGridHeaderComponent));
            expect(emptyFilterHeader.componentInstance.column.field).toEqual('Downloads');

            // Scroll to the right
            grid.headerContainer.getScroll().scrollLeft = 300;
            await wait();
            fix.detectChanges();

            emptyFilterCells = fix.debugElement.queryAll(By.directive(IgxGridFilteringCellComponent))
                .filter((cell) => cell.nativeElement.children.length === 0);
            expect(emptyFilterCells.length).toEqual(1);

            emptyFilterHeader = emptyFilterCells[0].parent.query(By.directive(IgxGridHeaderComponent));
            expect(emptyFilterHeader.componentInstance.column.field).toEqual('Downloads');
        });
    });

    describe(null, () => {
        let fix;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringTemplateComponent);
            fix.detectChanges();
        }));

        it('Should render custom filter template instead of default one.', fakeAsync(() => {
            // Verify default filter template is not present.
            expect(GridFunctions.getFilterCell(fix, 'ProductName').query(By.css('.igx-filtering-chips'))).toBeNull(
                '\'ProductName\' default filter chips area template was found.');
            expect(GridFunctions.getFilterCell(fix, 'Downloads').query(By.css('.igx-filtering-chips'))).toBeNull(
                '\'Downloads\' default filter chips area template was found.');
            expect(GridFunctions.getFilterCell(fix, 'Released').query(By.css('.igx-filtering-chips'))).toBeNull(
                '\'Released\' default filter chips area template was found.');
            expect(GridFunctions.getFilterCell(fix, 'ReleaseDate').query(By.css('.igx-filtering-chips'))).toBeNull(
                '\'ReleaseDate\' default filter chips area template was found.');

            // Verify the custom filter template is present.
            expect(GridFunctions.getFilterCell(fix, 'ProductName').query(By.css('.custom-filter'))).not.toBeNull(
                '\'ProductName\' customer filter template was not found.');
            expect(GridFunctions.getFilterCell(fix, 'Downloads').query(By.css('.custom-filter'))).not.toBeNull(
                '\'Downloads\' customer filter template was not found.');
            expect(GridFunctions.getFilterCell(fix, 'Released').query(By.css('.custom-filter'))).not.toBeNull(
                '\'Released\' customer filter template was not found.');
            expect(GridFunctions.getFilterCell(fix, 'ReleaseDate').query(By.css('.custom-filter'))).not.toBeNull(
                '\'ReleaseDate\' customer filter template was not found.');
        }));

        it('Should close default filter template when clicking on a column with custom one.', fakeAsync(() => {
            // Click on a column with default filter
            GridFunctions.clickFilterCellChip(fix, 'Licensed');
            fix.detectChanges();

            // Verify filter row is visible
            let filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).not.toBeNull();

            // Click on a column with custom filter
            const header = GridFunctions.getColumnHeaderTitleByIndex(fix, 1);
            header.click();
            fix.detectChanges();

            // Expect the filter row is closed
            filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            expect(filterUIRow).toBeNull('Default filter template was found on a column with custom filtering.');
        }));
    });

    describe(null, () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;
        const today = SampleTestData.today;

        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridDatesFilteringComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
        }));

        // UI tests date column // date values are ISO 8601 strings
        it('UI - should correctly filter ISO 8601 date column', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const reset = filterUIRow.queryAll(By.css('button'))[0];
            const close = filterUIRow.queryAll(By.css('button'))[1];
            const input = filterUIRow.query(By.directive(IgxInputDirective));
            const expectedResults = GridFunctions.createDateFilterConditions(grid, today);

            // Today condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 4);

            expect(grid.rowList.length).toEqual(1);
            verifyFilterRowUI(input, close, reset, false);
            verifyFilterUIPosition(filterUIRow, grid);

            expect(grid.rowList.length).toEqual(1);

            // This Month condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 6);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(expectedResults[5]);

            // Last Month condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 7);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(expectedResults[0]);

            // Empty condition
            GridFunctions.openFilterDDAndSelectCondition(fix, 12);
            verifyFilterRowUI(input, close, reset, false);
            expect(grid.rowList.length).toEqual(2);
        }));

        it('UI - should correctly filter ISO 8601 date column by \'equals\' filtering conditions', fakeAsync(() => {
            GridFunctions.clickFilterCellChip(fix, 'ReleaseDate');

            const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
            const input = filterUIRow.query(By.css('.igx-date-picker__input-date'));
            const datePicker = filterUIRow.query(By.directive(IgxDatePickerComponent));

            GridFunctions.openFilterDDAndSelectCondition(fix, 0);

            datePicker.triggerEventHandler('click', null);
            tick();
            fix.detectChanges();

            const outlet = document.getElementsByClassName('igx-grid__outlet')[0];
            const calendar = outlet.getElementsByClassName('igx-calendar')[0];

            const currentDay = calendar.querySelector('.igx-calendar__date--current');

            currentDay.dispatchEvent(new Event('click'));

            flush();
            fix.detectChanges();
            input.triggerEventHandler('change', null);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toEqual(1);
        }));
    });

    describe('Filtering events', () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;

        const cal = SampleTestData.timeGenerator;
        const today = SampleTestData.today;

        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridDatesFilteringComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;

            spyOn(grid.filtering, 'emit');
            spyOn(grid.filteringDone, 'emit');
        }));

        it('Should emit filteringDone when we clicked reset - Date column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                cal.timedelta(today, 'day', 15),
                'ReleaseDate',
                IgxDateFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when we clicked reset - String column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                'search',
                'ProductName',
                IgxStringFilteringOperand.instance().condition('contains')
            );
        }));

        it('Should emit filteringDone when we clicked reset - Number column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                100,
                'Downloads',
                IgxNumberFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when we clicked reset - Currency column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                100000,
                'Revenue',
                IgxNumberFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when we clicked reset - Boolean column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                true,
                'Released',
                IgxBooleanFilteringOperand.instance().condition('true')
            );
        }));

        it('Should emit filteringDone when we clicked reset - DateTime column type', fakeAsync(() => {
            emitFilteringDoneOnResetClick(
                fix,
                grid,
                cal.timedelta(today, 'hour', 1),
                'ReleaseTime',
                IgxDateTimeFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - Date column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                cal.timedelta(today, 'day', 15),
                'ReleaseDate',
                IgxDateFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - String column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                'search',
                'ProductName',
                IgxStringFilteringOperand.instance().condition('contains')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - Number column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                3,
                'Downloads',
                IgxNumberFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - Currency column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                100000,
                'Revenue',
                IgxNumberFilteringOperand.instance().condition('equals')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - Boolean column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                true,
                'Released',
                IgxBooleanFilteringOperand.instance().condition('true')
            );
        }));

        it('Should emit filteringDone when clear the input of filteringUI - DateTime column type', fakeAsync(() => {
            emitFilteringDoneOnInputClear(
                fix,
                grid,
                cal.timedelta(today, 'hour', 1),
                'ReleaseTime',
                IgxDateTimeFilteringOperand.instance().condition('equals')
            );
        }));

        it('should update UI when chip is removed from header cell - Date column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                cal.timedelta(today, 'day', 15),
                'ReleaseDate',
                IgxDateFilteringOperand.instance().condition('equals'),
                1,
                4
            );
        }));

        it('should update UI when chip is removed from header cell - String column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                'I',
                'ProductName',
                IgxStringFilteringOperand.instance().condition('startsWith'),
                2,
                1
            );
        }));

        it('should update UI when chip is removed from header cell - Number column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                100,
                'Downloads',
                IgxNumberFilteringOperand.instance().condition('equals'),
                1,
                2
            );
        }));

        it('should update UI when chip is removed from header cell - Currency column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                100000,
                'Revenue',
                IgxNumberFilteringOperand.instance().condition('equals'),
                1,
                7
            );
        }));

        it('should update UI when chip is removed from header cell - Boolean column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                true,
                'Released',
                IgxBooleanFilteringOperand.instance().condition('true'),
                3,
                3
            );
        }));

        it('should update UI when chip is removed from header cell - DateTime column type.', fakeAsync(() => {
            verifyRemoveChipFromHeader(
                fix,
                grid,
                cal.timedelta(today, 'hour', 1),
                'ReleaseTime',
                IgxDateTimeFilteringOperand.instance().condition('equals'),
                3,
                6
            );
        }));

        it('should emit filteringDone after chip is close from filtering row - Date column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'ReleaseDate', 4);
        }));

        it('should emit filteringDone after chip is close from filtering row - String column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'ProductName', 9);
        }));

        it('should emit filteringDone after chip is close from filtering row - Number column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'Downloads', 7);
        }));

        it('should emit filteringDone after chip is close from filtering row - Currency column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'Revenue', 7);
        }));

        it('should emit filteringDone after chip is close from filtering row - Boolean column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'Released', 1);
        }));

        it('should emit filteringDone after chip is close from filtering row - DateTime column type', fakeAsync(() => {
            closeChipFromFiteringUIRow(fix, grid, 'ReleaseTime', 4);
        }));
    });
});

describe('IgxGrid - Filtering actions - Excel style filtering #grid', () => {
    configureTestSuite((() => {
        return TestBed.configureTestingModule({
            imports: [
                NoopAnimationsModule,
                IgxGridFilteringComponent,
                IgxGridFilteringESFEmptyTemplatesComponent,
                IgxGridFilteringESFTemplatesComponent,
                IgxGridFilteringESFLoadOnDemandComponent,
                IgxGridFilteringMCHComponent,
                IgxGridExternalESFComponent,
                IgxGridExternalESFTemplateComponent
            ]
        });
    }));

    describe(null, () => {
        let fix: ComponentFixture<IgxGridFilteringComponent>;
        let grid: IgxGridComponent;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
            grid.filterMode = FilterMode.excelStyleFilter;
            fix.detectChanges();
        }));

        it('Should sort the grid properly, when clicking Ascending button.', fakeAsync(() => {
            grid.columnList.get(2).sortable = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const sortAsc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[0];

            UIInteractions.simulateClickEvent(sortAsc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions[0].fieldName).toEqual('Downloads');
            expect(grid.sortingExpressions[0].dir).toEqual(SortingDirection.Asc);
            ControlsFunction.verifyButtonIsSelected(sortAsc);

            UIInteractions.simulateClickEvent(sortAsc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions.length).toEqual(0);
            ControlsFunction.verifyButtonIsSelected(sortAsc, false);
        }));

        it('Should sort the grid properly, when clicking Descending button.', fakeAsync(() => {
            grid.columnList.get(2).sortable = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const sortDesc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[1];

            UIInteractions.simulateClickEvent(sortDesc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions[0].fieldName).toEqual('Downloads');
            expect(grid.sortingExpressions[0].dir).toEqual(SortingDirection.Desc);
            ControlsFunction.verifyButtonIsSelected(sortDesc);

            UIInteractions.simulateClickEvent(sortDesc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions.length).toEqual(0);
            ControlsFunction.verifyButtonIsSelected(sortDesc, false);
        }));

        it('Should (sort ASC)/(sort DESC) when clicking the respective sort button.', fakeAsync(() => {
            grid.columnList.get(2).sortable = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const sortAsc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[0];
            const sortDesc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[1];

            UIInteractions.simulateClickEvent(sortDesc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions[0].fieldName).toEqual('Downloads');
            expect(grid.sortingExpressions[0].dir).toEqual(SortingDirection.Desc);
            ControlsFunction.verifyButtonIsSelected(sortDesc);

            UIInteractions.simulateClickEvent(sortAsc);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(grid.sortingExpressions[0].fieldName).toEqual('Downloads');
            expect(grid.sortingExpressions[0].dir).toEqual(SortingDirection.Asc);
            ControlsFunction.verifyButtonIsSelected(sortAsc);
            ControlsFunction.verifyButtonIsSelected(sortDesc, false);
        }));

        it('Should toggle correct Ascending/Descending button on opening when sorting is applied.', fakeAsync(() => {
            grid.columnList.get(2).sortable = true;
            grid.sortingExpressions.push({ dir: SortingDirection.Asc, fieldName: 'Downloads' });
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const sortAsc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[0];
            const sortDesc = GridFunctions.getExcelStyleFilteringSortButtons(fix)[1];

            ControlsFunction.verifyButtonIsSelected(sortAsc);
            ControlsFunction.verifyButtonIsSelected(sortDesc, false);
        }));

        it('Should move column left/right when clicking buttons.', fakeAsync(() => {
            grid.moving = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            const moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];

            moveLeft.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[2].field).toBe('ProductName');
            expect(grid.columns[1].field).toBe('Downloads');

            moveLeft.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[1].field).toBe('ID');
            expect(grid.columns[0].field).toBe('Downloads');
            ControlsFunction.verifyButtonIsDisabled(moveLeft);

            moveRight.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[0].field).toBe('ID');
            expect(grid.columns[1].field).toBe('Downloads');
            ControlsFunction.verifyButtonIsDisabled(moveLeft, false);
        }));

        it('Should right pin and unpin column after moving it left/right when clicking buttons.', fakeAsync(() => {
            grid.pinning.columns = 1;

            const columnToPin = grid.columnList.get(grid.columnList.length - 2);
            columnToPin.pinned = true;
            fix.detectChanges();

            expect(grid.pinnedColumns.length).toBe(1);

            const columnToMove = grid.unpinnedColumns[grid.unpinnedColumns.length - 1];
            grid.moving = true;

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, columnToMove.field);

            let moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            let moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];

            moveRight.click();
            fix.detectChanges();

            expect(grid.pinnedColumns.length).toBe(2);

            expect(grid.pinnedColumns[0].field).toBe(columnToMove.field);
            expect(grid.pinnedColumns[1].field).toBe(columnToPin.field);

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, columnToMove.field);
            moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];
            moveRight.click();
            fix.detectChanges();

            expect(grid.pinnedColumns[0].field).toBe(columnToPin.field);
            expect(grid.pinnedColumns[1].field).toBe(columnToMove.field);

            moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            moveLeft.click();
            fix.detectChanges();

            moveLeft.click();
            fix.detectChanges();

            expect(grid.pinnedColumns.length).toBe(1);
            expect(grid.pinnedColumns[0].field).toBe(columnToPin.field);
        }));

        it('Should pin column when clicking buttons.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.getExcelFilteringPinContainer(fix).click();
            fix.detectChanges();

            expect(grid.pinnedColumns[0].field).toEqual('Downloads');
        }));

        it('Should unpin column when clicking buttons.', fakeAsync(() => {
            grid.columnList.get(2).pinned = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.getExcelFilteringUnpinContainer(fix).click();
            fix.detectChanges();

            expect(grid.pinnedColumns.length).toEqual(0);
        }));

        it('Should hide column when click on button.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            spyOn(grid.columnVisibilityChanged, 'emit');
            spyOn(grid.columnVisibilityChanging, 'emit');
            GridFunctions.getExcelFilteringHideContainer(fix).click();
            fix.detectChanges();

            const args = { column: grid.getColumnByName('Downloads'), newValue: true };
            expect(grid.columnVisibilityChanging.emit).toHaveBeenCalledTimes(1);
            expect(grid.columnVisibilityChanging.emit).toHaveBeenCalledWith({ ...args, cancel: false });
            expect(grid.columnVisibilityChanged.emit).toHaveBeenCalledTimes(1);
            expect(grid.columnVisibilityChanged.emit).toHaveBeenCalledWith(args);

            GridFunctions.verifyColumnIsHidden(grid.columnList.get(2), true, 7);
        }));

        it('Should not select values in list if two values with And operator are entered.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.And, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            expect(grid.filteredData.length).toEqual(1);

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkboxes: any[] = Array.from(GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu));
            checkboxes.forEach(c => expect(c.checked).toBeFalsy());
        }));

        it('Should not select values in list if two values with Or operator are entered and contains operand.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'Angular', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'Ignite', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            expect(grid.filteredData.length).toEqual(2);

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkboxes: any[] = Array.from(GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu));
            checkboxes.forEach(c => expect(c.checked).toBeFalsy());
        }));

        it('Should select values in list if two values with Or operator are entered and they are in the list below.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            expect(grid.filteredData.length).toEqual(2);

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkboxes: any[] = Array.from(GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu));

            fix.detectChanges();

            expect(checkboxes[0].checked && checkboxes[0].indeterminate).toBeTruthy();
            expect(!checkboxes[1].checked && !checkboxes[1].indeterminate).toBeTruthy();
            expect(!checkboxes[2].checked && !checkboxes[2].indeterminate).toBeTruthy();
            expect(checkboxes[3].checked && !checkboxes[3].indeterminate).toBeTruthy();
        }));

        it('Should change filter when changing And/Or operator.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();

            expect(grid.filteredData.length).toEqual(2);

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 10);
            tick(100);

            const andButton = GridFunctions.getExcelCustomFilteringExpressionAndButton(fix);
            andButton.click();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            expect(grid.filteredData.length).toEqual(0);
        }));

        it('Should change filter when changing operator.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();

            expect(grid.filteredData.length).toEqual(2);

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 10);
            tick();

            // select second expression's operator
            GridFunctions.setOperatorESF(fix, 1, 2);

            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            expect(grid.filteredData.length).toEqual(5);
        }));

        it('Should not be able to exit custom dialog when press tab on apply button', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick();

            const applyButton = GridFunctions.getApplyExcelStyleCustomFiltering(fix);
            applyButton.focus();
            fix.detectChanges();

            expect(document.activeElement).toBe(applyButton);

            UIInteractions.triggerKeyDownEvtUponElem('Tab', applyButton, true);
            fix.detectChanges();

            expect(document.activeElement).toBe(applyButton);
        }));

        it('Should populate custom filter dialog.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('lessThan') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 10);
            tick();

            // Verify inputs values
            expect(GridFunctions.getExcelFilteringInput(fix, 0).value).toEqual('254');
            expect(GridFunctions.getExcelFilteringInput(fix, 1).value).toEqual('20');

            // Verify Drop Down values
            expect(GridFunctions.getExcelFilteringDDInput(fix, 0).value).toEqual('Equals');
            expect(GridFunctions.getExcelFilteringDDInput(fix, 1).value).toEqual('Less Than');
        }));

        it('Should display friendly conditions\' names in custom filter dialog.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 1);
            tick(100);

            const firstValue = GridFunctions.getExcelFilteringDDInput(fix, 0).value;

            expect(firstValue).toMatch('Does Not Contain');
        }));

        it('Should clear the filter when click Clear filter item.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.clickClearFilterInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.filteredData).toBeNull();
        }));

        it('Should clear filter when pressing \'Enter\' on the clear filter button in ESF.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'Angular', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'Ignite', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            expect(grid.filteredData.length).toEqual(2);

            const clearFilterButton = GridFunctions.getClearFilterInExcelStyleFiltering(fix);
            clearFilterButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
            tick(100);
            fix.detectChanges();

            expect(grid.filteredData).toBeNull();
        }));

        it('Should set the \'aria-disabled\' attribute for the ESF dialog clear filter button element with role=\'menuitem\'.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            expect(grid.filteredData).toBeNull();
            let clearFilterButtonMenuTiemRole = GridFunctions.getClearFilterInExcelStyleFiltering(fix);
            expect(clearFilterButtonMenuTiemRole.getAttribute('aria-disabled')).toBe('true');

            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'ProductName');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'ProductName', searchVal: 'Angular', condition: IgxStringFilteringOperand.instance().condition('contains') },
                { fieldName: 'ProductName', searchVal: 'Ignite', condition: IgxStringFilteringOperand.instance().condition('contains') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            expect(grid.filteredData.length).toEqual(2);
            clearFilterButtonMenuTiemRole = GridFunctions.getClearFilterInExcelStyleFiltering(fix);
            expect(clearFilterButtonMenuTiemRole.getAttribute('aria-disabled')).toBe('false');

            const clearFilterButton = GridFunctions.getClearFilterInExcelStyleFiltering(fix);
            clearFilterButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
            tick(100);
            fix.detectChanges();

            expect(grid.filteredData).toBeNull();
            clearFilterButtonMenuTiemRole = GridFunctions.getClearFilterInExcelStyleFiltering(fix);
            expect(clearFilterButtonMenuTiemRole.getAttribute('aria-disabled')).toBe('true');
        }));

        it('Should update filter icon when dialog is closed and the filter has been changed.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix);

            checkbox[0].click();
            tick();
            fix.detectChanges();

            checkbox[2].click();
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.filteredData.length).toEqual(1);

            let filterIcon = GridFunctions.getExcelFilterIcon(fix, 'Downloads');
            expect(filterIcon).toBeNull();

            filterIcon = GridFunctions.getExcelFilterIconFiltered(fix, 'Downloads');
            expect(filterIcon).toBeDefined();
        }));

        it('Should filter grid via custom dialog.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(100);

            // set first expression's value
            GridFunctions.setInputValueESF(fix, 0, 0);

            // select second expression's operator
            GridFunctions.setOperatorESF(fix, 1, 1);

            // set second expression's value
            GridFunctions.setInputValueESF(fix, 1, 20);

            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should filter grid via custom dialog - 3 expressions.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Released');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(100);

            // select second expression's operator
            GridFunctions.setOperatorESF(fix, 1, 1);

            GridFunctions.clickAddFilterExcelStyleCustomFiltering(fix);
            fix.detectChanges();

            // select third expression's operator
            GridFunctions.setOperatorESF(fix, 2, 4);

            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            expect(grid.filteredData.length).toEqual(3);
        }));

        it('Should clear filter from custom dialog.', fakeAsync(() => {
            const gridFilteringExpressionsTree = new FilteringExpressionsTree(FilteringLogic.And);
            const columnsFilteringTree = new FilteringExpressionsTree(FilteringLogic.Or, 'Downloads');
            columnsFilteringTree.filteringOperands = [
                { fieldName: 'Downloads', searchVal: 254, condition: IgxNumberFilteringOperand.instance().condition('equals') },
                { fieldName: 'Downloads', searchVal: 20, condition: IgxNumberFilteringOperand.instance().condition('equals') }
            ];
            gridFilteringExpressionsTree.filteringOperands.push(columnsFilteringTree);
            grid.filteringExpressionsTree = gridFilteringExpressionsTree;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            tick();
            fix.detectChanges();

            GridFunctions.clickOperatorFromCascadeMenu(fix, 10);
            tick(100);

            GridFunctions.clickClearFilterExcelStyleCustomFiltering(fix);
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            expect(grid.filteredData).toBeNull();
        }));

        it('Should pin/unpin column when clicking pin/unpin icon in header', fakeAsync(() => {
            grid.displayDensity = DisplayDensity.cosy;
            tick(200);
            fix.detectChanges();

            // Open excel style filtering component and pin 'ProductName' column through header icon
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickPinIconInExcelStyleFiltering(fix);
            tick(200);
            fix.detectChanges();
            // Verify Excel menu is closed
            expect(GridFunctions.getExcelStyleFilteringComponent(fix)).toBeNull();
            const column = grid.getColumnByName('ProductName');
            GridFunctions.verifyColumnIsPinned(column, true, 1);

            // Open excel style filtering component and UNpin 'ProductName' column through header icon
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickPinIconInExcelStyleFiltering(fix);
            tick(200);
            fix.detectChanges();

            GridFunctions.verifyColumnIsPinned(column, false, 0);
        }));

        it('Should hide column when clicking hide icon in header', fakeAsync(() => {
            grid.displayDensity = DisplayDensity.compact;
            tick(200);
            fix.detectChanges();
            spyOn(grid.columnVisibilityChanging, 'emit');
            spyOn(grid.columnVisibilityChanged, 'emit');

            const column = grid.columnList.find((col) => col.field === 'ProductName');
            GridFunctions.verifyColumnIsHidden(column, false, 8);

            // Open excel style filtering component and hide 'ProductName' column through header icon
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            GridFunctions.clickHideIconInExcelStyleFiltering(fix);
            tick(200);
            fix.detectChanges();

            // Verify Excel menu is closed
            expect(GridFunctions.getExcelStyleFilteringComponent(fix)).toBeNull();
            const args = { column, newValue: true };
            expect(grid.columnVisibilityChanging.emit).toHaveBeenCalledTimes(1);
            expect(grid.columnVisibilityChanging.emit).toHaveBeenCalledWith({ ...args, cancel: false });
            expect(grid.columnVisibilityChanged.emit).toHaveBeenCalledTimes(1);
            expect(grid.columnVisibilityChanged.emit).toHaveBeenCalledWith(args);
        }));

        it('Should move pinned column correctly by using move buttons', fakeAsync(() => {
            grid.moving = true;
            const productNameCol = grid.getColumnByName('ProductName');
            const idCol = grid.getColumnByName('ID');
            productNameCol.pinned = true;
            idCol.pinned = true;
            fix.detectChanges();

            expect(productNameCol.pinned).toBe(true);

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            // Move 'ProductName' one step to the right. (should move)
            let moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];
            UIInteractions.simulateClickEvent(moveRight);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 0).innerText).toBe('ID');
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 1).innerText).toBe('ProductName');
            expect(productNameCol.pinned).toBe(true);

            // Move 'ProductName' one step to the left. (should move)
            const moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            UIInteractions.simulateClickEvent(moveLeft);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 0).innerText).toBe('ProductName');
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 1).innerText).toBe('ID');
            expect(productNameCol.pinned).toBe(true);

            // Try move 'ProductName' one step to the left. (Button should be disabled since it's already first)
            const moveComponent = GridFunctions.getExcelFilteringMoveComponent(fix);
            ControlsFunction.verifyButtonIsDisabled(moveComponent.querySelectorAll('button')[0]);

            // Move 'ProductName' two steps to the right. (should move)
            moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];
            UIInteractions.simulateClickEvent(moveRight);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            UIInteractions.simulateClickEvent(moveRight);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 0).innerText).toBe('ID');
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 1).innerText).toBe('ProductName');
            expect(productNameCol.pinned).toBe(false);
        }));

        it('Should move unpinned column correctly by using move buttons', fakeAsync(() => {
            grid.moving = true;
            const productNameCol = grid.getColumnByName('ProductName');
            const downloadsCol = grid.getColumnByName('Downloads');
            productNameCol.pinned = true;
            fix.detectChanges();
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 0).innerText).toBe('ProductName');
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 2).innerText).toBe('Downloads');
            expect(downloadsCol.pinned).toBe(false);

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            UIInteractions.simulateClickEvent(moveLeft);
            tick(DEBOUNCETIME);
            fix.detectChanges();
            UIInteractions.simulateClickEvent(moveLeft);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 0).innerText).toBe('ProductName');
            expect(GridFunctions.getColumnHeaderTitleByIndex(fix, 1).innerText).toBe('Downloads');
            expect(downloadsCol.pinned).toBe(true);
        }));

        it('Should filter and clear the excel search component correctly', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'ProductName');
            fix.detectChanges();
            await wait(100);
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');

            // Type string in search box.
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'ignite', fix);
            fix.detectChanges();
            await wait(100);

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(4, 'incorrect rendered list items count');

            // Clear filtering of ESF search.
            const clearIcon: any = Array.from(searchComponent.querySelectorAll('igx-icon'))
                .find((icon: any) => icon.innerText === 'clear');
            clearIcon.click();
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
        });

        it('Should allow to input commas in excel search component input field when column dataType is number.', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'Downloads');
            fix.detectChanges();
            await wait(100);
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);

            // Type 1,000 in search box.
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '1,000', fix);
            fix.detectChanges();
            await wait(100);

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(3, 'incorrect rendered list items count');

            // Type non-numerical symbol in search box.
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'a', fix);
            fix.detectChanges();
            await wait(100);

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(inputNativeElement.value).toBe('', 'incorrect rendered list items count');
            expect(listItems.length).toBe(8, 'incorrect rendered list items count');
        });

        it('Should enable/disable the apply button correctly.', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'ProductName');
            fix.detectChanges();
            await wait(100);
            // Verify there are filtered-in results and that apply button is enabled.
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            let applyButton = GridFunctions.getApplyButtonExcelStyleFiltering(fix) as HTMLElement;
            expect(listItems.length).toBe(6, 'ESF search result should NOT be empty');
            ControlsFunction.verifyButtonIsDisabled(applyButton, false);

            // Verify the apply button is disabled when all items are unchecked (when unchecking 'Select All').
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            checkbox[0].click(); // Select All
            fix.detectChanges();
            await wait(100);
            applyButton = GridFunctions.getApplyButtonExcelStyleFiltering(fix);
            ControlsFunction.verifyButtonIsDisabled(applyButton);
        });

        it('display density is properly applied on the excel style filtering component', fakeAsync(() => {
            const column = grid.columnList.find((c) => c.field === 'ProductName');
            column.sortable = true;
            grid.moving = true;
            fix.detectChanges();

            // Open excel style filtering component and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            verifyExcelStyleFilteringDisplayDensity(fix, DisplayDensity.comfortable);
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.compact;
            tick(200);
            fix.detectChanges();

            // Open excel style filtering component and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilteringDisplayDensity(fix, DisplayDensity.compact);
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.cosy;
            tick(200);
            fix.detectChanges();

            // Open excel style filtering component and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilteringDisplayDensity(fix, DisplayDensity.cosy);
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();
        }));

        it('display density is properly applied on the column selection container', fakeAsync(() => {
            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            tick(100);
            fix.detectChanges();

            let columnSelectionContainer = GridFunctions.getExcelFilteringColumnSelectionContainer(fix);
            let headerIcons = GridFunctions.getExcelFilteringHeaderIcons(fix);

            expect(columnSelectionContainer).not.toBeNull();
            expect(headerIcons.length).toEqual(0);

            grid.displayDensity = DisplayDensity.compact;
            fix.detectChanges();

            columnSelectionContainer = GridFunctions.getExcelFilteringColumnSelectionContainer(fix);
            headerIcons = GridFunctions.getExcelFilteringHeaderIcons(fix);
            const columnSelectionIcon = headerIcons.find((bi: any) => bi.innerText === 'done');

            expect(columnSelectionContainer.tagName).toEqual('BUTTON');
            expect(columnSelectionIcon).not.toBeNull();

        }));

        it('display density is properly applied on the excel style custom filtering dialog', fakeAsync(() => {
            const column = grid.columnList.find((c) => c.field === 'ProductName');
            column.sortable = true;
            grid.moving = true;
            fix.detectChanges();

            // Open excel style custom filtering dialog and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);

            verifyExcelCustomFilterDisplayDensity(fix, DisplayDensity.comfortable);
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            grid.displayDensity = DisplayDensity.cosy;
            tick(200);
            fix.detectChanges();

            // Open excel style custom filtering dialog and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);

            verifyExcelCustomFilterDisplayDensity(fix, DisplayDensity.cosy);
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            grid.displayDensity = DisplayDensity.compact;
            tick(200);
            fix.detectChanges();

            // Open excel style custom filtering dialog and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);

            verifyExcelCustomFilterDisplayDensity(fix, DisplayDensity.compact);
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);
        }));

        it('display density is properly applied on the excel style cascade dropdown', fakeAsync(() => {
            const gridNativeElement = fix.debugElement.query(By.css('igx-grid')).nativeElement;

            // Open excel style cascade operators dropdown and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();

            verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.comfortable);

            GridFunctions.clickCancelExcelStyleFiltering(fix);
            tick();
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.cosy;
            tick(200);
            fix.detectChanges();

            // Open excel style cascade operators dropdown and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();

            verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.cosy);

            GridFunctions.clickCancelExcelStyleFiltering(fix);
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.compact;
            tick(200);
            fix.detectChanges();

            // Open excel style cascade operators dropdown and verify its display density
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.compact);
        }));

        it('display density is properly applied on the excel custom dialog\'s default expression dropdown',
            fakeAsync(() => {
                const gridNativeElement = fix.debugElement.query(By.css('igx-grid')).nativeElement;

                // Open excel style custom filtering dialog.
                GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

                GridFunctions.clickExcelFilterCascadeButton(fix);
                fix.detectChanges();
                GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
                tick(200);

                // Click the left input to open the operators dropdown and verify its display density.
                let conditionsInput = GridFunctions.getExcelFilteringDDInput(fix, 0);
                conditionsInput.click();
                tick(100);
                fix.detectChanges();

                verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.comfortable);
                GridFunctions.clickCancelExcelStyleCustomFiltering(fix);
                tick(100);
                fix.detectChanges();

                // Change display density
                grid.displayDensity = DisplayDensity.cosy;
                tick(200);
                fix.detectChanges();

                // Open excel style custom filtering dialog.
                GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

                GridFunctions.clickExcelFilterCascadeButton(fix);
                fix.detectChanges();
                GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
                tick(200);

                // Click the left input to open the operators dropdown and verify its display density.
                conditionsInput = GridFunctions.getExcelFilteringDDInput(fix, 0);
                conditionsInput.click();
                tick(100);
                fix.detectChanges();

                verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.cosy);
            }));

        it('display density is properly applied on the excel custom dialog\'s date expression dropdown',
            fakeAsync(() => {
                const gridNativeElement = fix.debugElement.query(By.css('igx-grid')).nativeElement;
                GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ReleaseDate');

                GridFunctions.clickExcelFilterCascadeButton(fix);
                fix.detectChanges();
                GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
                tick(200);

                // Click the left input to open the operators dropdown and verify its display density.
                let conditionsInput = GridFunctions.getExcelFilteringDDInput(fix, 0, true);
                conditionsInput.click();
                tick(100);
                fix.detectChanges();

                verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.comfortable);

                GridFunctions.clickCancelExcelStyleCustomFiltering(fix);
                tick(100);
                fix.detectChanges();

                // Change display density
                grid.displayDensity = DisplayDensity.cosy;
                tick(200);
                fix.detectChanges();

                // Open excel style custom filtering dialog.
                GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ReleaseDate');

                GridFunctions.clickExcelFilterCascadeButton(fix);
                fix.detectChanges();
                GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
                tick(200);

                // Click the left input to open the operators dropdown and verify its display density.
                conditionsInput = GridFunctions.getExcelFilteringDDInput(fix, 0, true);
                conditionsInput.click();
                tick(100);
                fix.detectChanges();
                verifyGridSubmenuDisplayDensity(gridNativeElement, DisplayDensity.cosy);
            }));

        it('Should include \'false\' value in results when searching.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Released');

            // Type string in search box.
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'false', fix);
            tick(100);
            fix.detectChanges();

            // Verify that the first item is 'Select All' and the third item is 'false'.
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(3, 'incorrect rendered list items count');
            expect(listItems[0].innerText).toBe('Select all search results');
            expect(listItems[2].innerText).toBe('False');
        }));

        it('should scroll items in search list correctly', (async () => {
            // Add additional rows as prerequisite for the test
            for (let index = 0; index < 30; index++) {
                const newRow = {
                    Downloads: index,
                    ID: index + 100,
                    ProductName: 'New Product ' + index,
                    ReleaseDate: new Date(),
                    Released: false,
                    AnotherField: 'z'
                };
                grid.addRow(newRow);
            }
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.compact;
            await wait(100);
            fix.detectChanges();

            // Open excel style filtering component
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            await wait(100);
            fix.detectChanges();

            // Scroll the search list to the bottom.
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const scrollbar = GridFunctions.getExcelStyleSearchComponentScrollbar(fix);
            scrollbar.scrollTop = 3000;
            await wait(100);
            fix.detectChanges();

            // Verify scrollbar's scrollTop.
            expect(scrollbar.scrollTop >= 670 && scrollbar.scrollTop <= 700).toBe(true,
                'search scrollbar has incorrect scrollTop: ' + scrollbar.scrollTop);
            // Verify display container height.
            const displayContainer = searchComponent.querySelector('igx-display-container');
            const displayContainerRect = displayContainer.getBoundingClientRect();
            expect(displayContainerRect.height).toBe(216, 'incorrect search display container height');
            // Verify rendered list items count.
            const listItems = displayContainer.querySelectorAll('igx-list-item');
            expect(listItems.length).toBe(9, 'incorrect rendered list items count');
        }));

        it('should correctly display all items in search list after filtering it', (async () => {
            // Add additional rows as prerequisite for the test
            for (let index = 0; index < 4; index++) {
                const newRow = {
                    Downloads: index,
                    ID: index + 100,
                    ProductName: 'New Sales Product ' + index,
                    ReleaseDate: new Date(),
                    Released: false,
                    AnotherField: 'z'
                };
                grid.addRow(newRow);
            }
            fix.detectChanges();

            // Open excel style filtering component
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            await wait(200);
            fix.detectChanges();

            // Scroll the search list to the middle.
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const displayContainer = searchComponent.querySelector('igx-display-container') as HTMLElement;
            const scrollbar = GridFunctions.getExcelStyleSearchComponentScrollbar(fix);
            scrollbar.scrollTop = displayContainer.getBoundingClientRect().height / 2;
            await wait(200);
            fix.detectChanges();
            await wait(100);

            // Type string in search box
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'sale', fix);
            await wait(200);
            fix.detectChanges();

            // Verify the display container is within the bounds of the list
            const displayContainerRect = displayContainer.getBoundingClientRect();
            const listNativeElement = searchComponent.querySelector('.igx-list');
            const listRect = listNativeElement.getBoundingClientRect();
            expect(displayContainerRect.top >= listRect.top).toBe(true, 'displayContainer starts above list');
            expect(displayContainerRect.bottom <= listRect.bottom).toBe(true, 'displayContainer ends below list');
        }));

        it('Should not treat \'Select All\' as a search result.', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'ProductName');
            fix.detectChanges();
            await wait(100);

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix, excelMenu);
            const input = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6);

            UIInteractions.clickAndSendInputElementValue(input, 'a', fix);
            await wait(100);
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(5);

            UIInteractions.clickAndSendInputElementValue(input, 'al', fix);
            await wait(100);
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(0);
        });

        it('Column formatter should skip the \'SelectAll\' list item', fakeAsync(() => {
            grid.columnList.get(4).formatter = (val: Date) => new Intl.DateTimeFormat('bg-BG').format(val);
            grid.cdr.detectChanges();

            // Open excel style filtering component
            try {
                GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ReleaseDate');
            } catch (ex) {
                expect(ex).toBeNull();
            }

            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems[0].innerText).toBe('Select All');
        }));

        it('should keep newly added filter expression in view', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            // Click 'Add Filter' button.
            GridFunctions.clickAddFilterExcelStyleCustomFiltering(fix);
            tick(200);

            // Verify last expression is currently in view inside the expressions container.
            const customFilterMenu = GridFunctions.getExcelStyleCustomFilteringDialog(fix);
            const expressionsContainer = customFilterMenu.querySelector('.igx-excel-filter__secondary-main');
            const expressions = GridFunctions.getExcelCustomFilteringDefaultExpressions(fix);
            const lastExpression = expressions[expressions.length - 1];
            const lastExpressionRect = lastExpression.getBoundingClientRect();
            const expressionsContainerRect = expressionsContainer.getBoundingClientRect();
            expect(lastExpressionRect.top >= expressionsContainerRect.top).toBe(true,
                'lastExpression starts above expressionsContainer');
            expect(lastExpressionRect.bottom <= expressionsContainerRect.bottom).toBe(true,
                'lastExpression ends below expressionsContainer');

            // Verify addFilter button is currently in view beneath the last expression.
            const addFilterButton = GridFunctions.getAddFilterExcelStyleCustomFiltering(fix);
            const addFilterButtonRect = addFilterButton.getBoundingClientRect();
            expect(addFilterButtonRect.top >= lastExpressionRect.bottom).toBe(true,
                'addFilterButton overlaps lastExpression');
            expect(addFilterButtonRect.bottom <= expressionsContainerRect.bottom).toBe(true,
                'addFilterButton ends below expressionsContainer');
        }));

        it('Should generate "equals" conditions when selecting two values.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu);

            checkbox[0].click(); // Select All
            tick();
            fix.detectChanges();

            checkbox[2].click(); // Ignite UI for Angular
            checkbox[3].click(); // Ignite UI for JavaScript
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix, excelMenu);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toBe(2);
            const operands =
                (grid.filteringExpressionsTree.filteringOperands[0] as IFilteringExpressionsTree)
                    .filteringOperands as IFilteringExpression[];
            expect(operands.length).toBe(2);
            verifyFilteringExpression(operands[0], 'ProductName', 'equals', 'Ignite UI for Angular');
            verifyFilteringExpression(operands[1], 'ProductName', 'equals', 'Ignite UI for JavaScript');
        }));

        it('Should generate "in" condition when selecting more than two values.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu);

            checkbox[0].click(); // Select All
            tick();
            fix.detectChanges();

            checkbox[2].click(); // Ignite UI for Angular
            checkbox[3].click(); // Ignite UI for JavaScript
            checkbox[4].click(); // NetAdvantage
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix, excelMenu);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toBe(3);
            const operands =
                (grid.filteringExpressionsTree.filteringOperands[0] as IFilteringExpressionsTree)
                    .filteringOperands as IFilteringExpression[];
            expect(operands.length).toBe(1);
            verifyFilteringExpression(operands[0], 'ProductName', 'in',
                new Set(['Ignite UI for Angular', 'Ignite UI for JavaScript', 'NetAdvantage']));
        }));

        it('Should not throw error when selecting more than two values and column dataType is date.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ReleaseDate');

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu);

            checkbox[0].click(); // Select All
            tick();
            fix.detectChanges();

            checkbox[2].click();
            checkbox[3].click();
            checkbox[4].click();
            checkbox[6].click();
            tick();
            fix.detectChanges();

            expect(() => {
                GridFunctions.clickApplyExcelStyleFiltering(fix, excelMenu);
            }).not.toThrowError();
        }));

        it('Should generate "in" and "empty" conditions when selecting more than two values including (Blanks).', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu);

            checkbox[0].click(); // Select All
            tick();
            fix.detectChanges();

            checkbox[1].click(); // (Blanks)
            checkbox[2].click(); // Ignite UI for Angular
            checkbox[3].click(); // Ignite UI for JavaScript
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix, excelMenu);
            tick();
            fix.detectChanges();

            expect(grid.rowList.length).toBe(6);
            const operands =
                (grid.filteringExpressionsTree.filteringOperands[0] as IFilteringExpressionsTree)
                    .filteringOperands as IFilteringExpression[];
            expect(operands.length).toBe(2);
            verifyFilteringExpression(operands[0], 'ProductName', 'in',
                new Set(['Ignite UI for Angular', 'Ignite UI for JavaScript']));
            verifyFilteringExpression(operands[1], 'ProductName', 'empty', null);
        }));

        it('should not display search scrollbar when not needed for the current display density', (async () => {
            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            grid.getCellByColumn(3, 'ProductName').update('Test');
            fix.detectChanges();

            // Verify scrollbar is visible for 'comfortable'.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            await wait(DEBOUNCETIME);
            fix.detectChanges();

            expect(isExcelSearchScrollBarVisible(fix)).toBe(true, 'excel search scrollbar should be visible');
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.cosy;
            await wait(100);
            fix.detectChanges();

            // Verify scrollbar is NOT visible for 'cosy'.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            await wait(DEBOUNCETIME);
            fix.detectChanges();

            expect(isExcelSearchScrollBarVisible(fix)).toBe(false, 'excel search scrollbar should NOT be visible');
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            grid.displayDensity = DisplayDensity.compact;
            await wait(100);
            fix.detectChanges();

            // Verify scrollbar is NOT visible for 'compact'.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            await wait(DEBOUNCETIME);
            fix.detectChanges();

            expect(isExcelSearchScrollBarVisible(fix)).toBe(false, 'excel search scrollbar should NOT be visible');
        }));

        it('Should cascade filter the available filter options.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();

            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', '0', '20', '100', '127', '254'],
                [true, true, true, true, true, true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'Ignite UI for Angular', 'Ignite UI for JavaScript',
                    'NetAdvantage', 'Some other item with Script'],
                [true, true, true, true, true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'Released');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'False', 'True'],
                [true, true, true, true]);

            toggleExcelStyleFilteringItems(fix, true, 3);

            expect(grid.rowList.length).toBe(5);

            GridFunctions.clickExcelFilterIcon(fix, 'Released');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'False', 'True'],
                [null, true, true, false]);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '100', '254', '702', '1,000'],
                [true, true, true, true, true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'Ignite UI for Angular', 'Ignite UI for JavaScript', 'Some other item with Script'],
                [true, true, true, true, true]);

            toggleExcelStyleFilteringItems(fix, false, 0);
            toggleExcelStyleFilteringItems(fix, true, 2, 3);

            expect(grid.rowList.length).toBe(2);

            GridFunctions.clickExcelFilterIcon(fix, 'Released');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'False'],
                [true, true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'Ignite UI for Angular', 'Ignite UI for JavaScript', 'Some other item with Script'],
                [null, false, true, true, false]);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '254'],
                [true, true, true]);
        }));

        it('Should correctly modify existing filters.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'Ignite UI for Angular', 'Ignite UI for JavaScript',
                    'NetAdvantage', 'Some other item with Script'],
                [true, true, true, true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 1, 5);
            expect(grid.rowList.length).toBe(3);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '127', '254'],
                [true, true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 2);
            expect(grid.rowList.length).toBe(2);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'Ignite UI for Angular', 'Ignite UI for JavaScript'],
                [true, true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '127', '254'],
                [null, true, false, true]);
            toggleExcelStyleFilteringItems(fix, true, 1, 2, 3);
            expect(grid.rowList.length).toBe(1);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '127', '254'],
                [null, false, true, false]);
        }));

        it('Should correctly modify the first one of the existing filters.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '(Blanks)', 'Ignite UI for Angular', 'Ignite UI for JavaScript',
                    'NetAdvantage', 'Some other item with Script'],
                [true, true, true, true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 1, 5);
            expect(grid.rowList.length).toBe(3);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '20', '127', '254'],
                [true, true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 2);
            expect(grid.rowList.length).toBe(2);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'Ignite UI for Angular', 'Ignite UI for JavaScript'],
                [true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 1);
            expect(grid.rowList.length).toBe(1);

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '254'],
                [true, true]);

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'Ignite UI for Angular', 'Ignite UI for JavaScript'],
                [null, false, true]);
        }));

        it('Should display the ESF based on the filterIcon within the grid', async () => {
            // Test prerequisites
            grid.width = '800px';
            for (const column of grid.columnList) {
                column.width = '300px';
            }
            await wait(16);
            fix.detectChanges();

            // Scroll a bit to the right, so the ProductName column is not fully visible.
            GridFunctions.scrollLeft(grid, 500);
            await wait(100);
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            fix.detectChanges();

            // Verify that the left, top and right borders of the ESF are within the grid.
            const gridNativeElement = fix.debugElement.query(By.css('igx-grid')).nativeElement;
            const gridRect = gridNativeElement.getBoundingClientRect();
            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const excelMenuRect = excelMenu.getBoundingClientRect();
            expect(excelMenuRect.left >= gridRect.left).toBe(true, 'ESF spans outside the grid on the left');
            expect(excelMenuRect.top >= gridRect.top).toBe(true, 'ESF spans outside the grid on the top');
            expect(excelMenuRect.right <= gridRect.right).toBe(true, 'ESF spans outside the grid on the right');
        });

        it('Should add/remove expressions in custom filter dialog through UI correctly.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            // Verify expressions count.
            let expressions = GridFunctions.getExcelCustomFilteringDefaultExpressions(fix);
            expect(expressions.length).toBe(2);

            // Add two new expressions.
            GridFunctions.clickAddFilterExcelStyleCustomFiltering(fix);
            tick(100);
            fix.detectChanges();
            GridFunctions.clickAddFilterExcelStyleCustomFiltering(fix);
            tick(100);
            fix.detectChanges();

            // Verify expressions count.
            expressions = GridFunctions.getExcelCustomFilteringDefaultExpressions(fix);
            expect(expressions.length).toBe(4);

            // Remove last expression by clicking its remove icon.
            let expr: any = expressions[3];
            let removeIcon: any = Array.from(expr.querySelectorAll('igx-icon'))
                .find((icon: any) => icon.innerText === 'cancel');
            removeIcon.click();
            fix.detectChanges();

            // Verify expressions count.
            expressions = Array.from(GridFunctions.getExcelCustomFilteringDefaultExpressions(fix));
            expect(expressions.length).toBe(3);

            // Remove second expression by clicking its remove icon.
            expr = expressions[1];
            removeIcon = Array.from(expr.querySelectorAll('igx-icon'))
                .find((icon: any) => icon.innerText === 'cancel');
            removeIcon.click();
            fix.detectChanges();

            // Verify expressions count.
            expressions = GridFunctions.getExcelCustomFilteringDefaultExpressions(fix);
            expect(expressions.length).toBe(2);
        }));

        it('Should keep selected operator of custom expression the same when clicking it.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            // Verify 'And' button is selected on first expression.
            let andButton = GridFunctions.getExcelCustomFilteringExpressionAndButton(fix);
            ControlsFunction.verifyButtonIsSelected(andButton);

            // Click the 'And' button.
            andButton.click();
            tick(100);
            fix.detectChanges();

            // Verify that selected button remains the same.
            andButton = GridFunctions.getExcelCustomFilteringExpressionAndButton(fix);
            ControlsFunction.verifyButtonIsSelected(andButton);

            const orButton = GridFunctions.getExcelCustomFilteringExpressionOrButton(fix);
            ControlsFunction.verifyButtonIsSelected(orButton, false);
        }));

        it('Should select the button operator in custom expression when pressing \'Enter\' on it.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const andButton = GridFunctions.getExcelCustomFilteringExpressionAndButton(fix);
            const orButton = GridFunctions.getExcelCustomFilteringExpressionOrButton(fix);

            // Verify 'and' is selected.
            ControlsFunction.verifyButtonIsSelected(andButton);
            ControlsFunction.verifyButtonIsSelected(orButton, false);

            // Press 'Enter' on 'or' button and verify it gets selected.
            UIInteractions.triggerKeyDownEvtUponElem('Enter', orButton, true);
            fix.detectChanges();

            ControlsFunction.verifyButtonIsSelected(andButton, false);
            ControlsFunction.verifyButtonIsSelected(orButton);

            // Press 'Enter' on 'and' button and verify it gets selected.
            UIInteractions.triggerKeyDownEvtUponElem('Enter', andButton, true);
            fix.detectChanges();

            ControlsFunction.verifyButtonIsSelected(andButton);
            ControlsFunction.verifyButtonIsSelected(orButton, false);
        }));

        it('Should open conditions dropdown of custom expression with \'Alt + Arrow Down\'.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDefaultExpressions(fix)[0];
            const conditionsInput = GridFunctions.getExcelFilteringDDInput(fix);

            // Dropdown should be hidden.
            let operatorsDropdownToggle = expr.querySelector('.igx-toggle--hidden');
            expect(operatorsDropdownToggle).not.toBeNull();

            // Press 'Alt + Arrow Down' to open operators dropdown.
            UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', conditionsInput, true, true);
            tick(100);
            fix.detectChanges();

            // Dropdown should be visible.
            operatorsDropdownToggle = expr.querySelector('.igx-toggle--hidden');
            expect(operatorsDropdownToggle).toBeNull();

            // Click-off to close dropdown.
            expr.click();
            tick(100);
            fix.detectChanges();

            // Dropdown should be hidden.
            operatorsDropdownToggle = expr.querySelector('.igx-toggle--hidden');
            expect(operatorsDropdownToggle).not.toBeNull();
        }));

        it('Should open calendar when clicking date-picker of custom expression.', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');

            // Verify calendar is not opened.
            let calendar = expr.querySelector('igx-calendar');
            expect(calendar).toBeNull();

            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();
            expect(datePicker).not.toBeNull();

            // Verify calendar is opened.
            calendar = grid.nativeElement.querySelector('igx-calendar');
            expect(calendar).not.toBeNull();

            // Click-off to close calendar.
            expr.click();
            tick(100);
            fix.detectChanges();

            // Verify calendar is not opened.
            calendar = fix.debugElement.query(By.directive(IgxCalendarComponent));
            expect(calendar).toBeNull();
        }));

        it('Should filter grid through custom date filter dialog.', fakeAsync(() => {
            const column = grid.getColumnByName('ReleaseDate');
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();

            // Click today item.
            const calendar = document.querySelector('igx-calendar');
            const todayItem = calendar.querySelector('.igx-calendar__date--current');
            (todayItem as HTMLElement).click();
            tick(100);
            fix.detectChanges();
            flush();

            // Verify the results are with 'today' date.
            const filteredDate = SampleTestData.today;
            const inputText = formatDate(filteredDate, column.pipeArgs.format, grid.locale);
            expect((input as HTMLInputElement).value).toMatch(inputText);

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            const pipe = new DatePipe(grid.locale);
            const cellText = pipe.transform(filteredDate, column.pipeArgs.format, column.pipeArgs.timezone);
            const cell = GridFunctions.getColumnCells(fix, 'ReleaseDate')[0].nativeElement;
            expect(cell.innerText).toMatch(cellText);
            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should take pipeArgs weekStart property as calendar\'s default.', fakeAsync(() => {
            const column = grid.getColumnByName('ReleaseDate');

            column.pipeArgs = {
                digitsInfo: '3.4-4',
                currencyCode: 'USD',
                display: 'symbol-narrow',
                weekStart: 5,
            };
            fix.detectChanges();

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();

            // Get Calendar component.
            const calendar = document.querySelector('igx-calendar');

            const daysOfWeek = calendar.querySelector('.igx-calendar__body-row');
            const weekStart = daysOfWeek.firstElementChild as HTMLSpanElement;

            expect(weekStart.innerText).toMatch('Fri');
        }));

        it('Should filter grid with ISO 8601 dates through custom date filter dialog', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.toISOString() : null;
                return newRec;
            });
            fix.detectChanges();
            const column = grid.getColumnByName('ReleaseDate');

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();

            // Click today item.
            const calendar = document.querySelector('igx-calendar');
            const todayItem = calendar.querySelector('.igx-calendar__date--current');
            (todayItem as HTMLElement).click();
            tick(100);
            fix.detectChanges();
            flush();

            // Verify the results are with 'today' date.
            const filteredDate = SampleTestData.today;
            const inputText = formatDate(filteredDate, column.pipeArgs.format, grid.locale);
            expect((input as HTMLInputElement).value).toMatch(inputText);

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            const pipe = new DatePipe(grid.locale);
            const cellText = pipe.transform(filteredDate, column.pipeArgs.format, column.pipeArgs.timezone);
            const cell = GridFunctions.getColumnCells(fix, 'ReleaseDate')[0].nativeElement;
            expect(cell.innerText).toMatch(cellText);
            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should filter grid with miliseconds dates through custom date filter dialog', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.getTime() : null;
                return newRec;
            });
            fix.detectChanges();
            const column = grid.getColumnByName('ReleaseDate');

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();

            // Click today item.
            const calendar = document.querySelector('igx-calendar');
            const todayItem = calendar.querySelector('.igx-calendar__date--current');
            (todayItem as HTMLElement).click();
            tick(100);
            fix.detectChanges();
            flush();

            // Verify the results are with 'today' date.
            const filteredDate = SampleTestData.today;
            const inputText = formatDate(filteredDate, column.pipeArgs.format, grid.locale);
            expect((input as HTMLInputElement).value).toMatch(inputText);

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            const pipe = new DatePipe(grid.locale);
            const cellText = pipe.transform(filteredDate, column.pipeArgs.format, column.pipeArgs.timezone);
            const cell = GridFunctions.getColumnCells(fix, 'ReleaseDate')[0].nativeElement;
            expect(cell.innerText).toMatch(cellText);
            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should filter grid through custom date filter dialog when using pipeArgs for the column', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.toISOString() : null;
                return newRec;
            });

            const formatOptions = {
                timezone: 'utc',
            };
            const column = grid.getColumnByName('ReleaseDate');
            column.pipeArgs = formatOptions;
            fix.detectChanges();

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();

            // Click today item.
            const calendar = document.querySelector('igx-calendar');
            const todayItem = calendar.querySelector('.igx-calendar__date--current');
            (todayItem as HTMLElement).click();
            tick(100);
            fix.detectChanges();
            flush();

            // Verify the results are with 'today' date.
            const filteredDate = SampleTestData.today;
            const inputText = formatDate(filteredDate, column.pipeArgs.format, grid.locale);
            expect((input as HTMLInputElement).value).toMatch(inputText);

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            const pipe = new DatePipe(grid.locale);
            const cellText = pipe.transform(filteredDate, column.pipeArgs.format, column.pipeArgs.timezone);
            const cell = GridFunctions.getColumnCells(fix, 'ReleaseDate')[0].nativeElement;
            expect(cell.innerText).toMatch(cellText);
            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should filter grid through custom date filter dialog when using pipeArgs and formatter for the column', fakeAsync(() => {
            const pipe = new DatePipe('fr-FR');
            const formatOptions = {
                timezone: 'utc',
                format: 'longDate'
            };
            const column = grid.getColumnByName('ReleaseDate');
            column.pipeArgs = formatOptions;
            grid.getColumnByName('ReleaseDate').formatter = ((value: any) => {
                const val = value !== null && value !== undefined && value !== '' ? pipe.transform(value, 'longDate') : 'No value!';
                return val;
            });
            fix.detectChanges();

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickExcelFilterCascadeButton(fix);
            fix.detectChanges();
            GridFunctions.clickOperatorFromCascadeMenu(fix, 0);
            tick(200);

            const expr = GridFunctions.getExcelCustomFilteringDateExpressions(fix)[0];
            const datePicker = expr.querySelector('igx-date-picker');
            const input = datePicker.querySelector('input');
            UIInteractions.simulateClickEvent(input);
            fix.detectChanges();
            tick(350); // calendar animationDone timeout

            // Click today item.
            const calendar = document.querySelector('igx-calendar');
            const todayItem = calendar.querySelector('.igx-calendar__date--current');
            (todayItem as HTMLElement).click();
            tick();
            fix.detectChanges();

            // Verify the results are with 'today' date.
            const filteredDate = SampleTestData.today;
            const inputText = column.formatter(filteredDate, null);
            expect((input as HTMLInputElement).value).toMatch(inputText);

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleCustomFiltering(fix);

            const cellText = column.formatter(filteredDate, null);
            const cell = GridFunctions.getColumnCells(fix, 'ReleaseDate')[0].nativeElement;
            expect(cell.innerText).toMatch(cellText);
            expect(grid.filteredData.length).toEqual(1);
        }));

        it('Should correctly update \'SelectAll\' based on checkboxes.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const visibleListItems = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            const thirdItemCbInput = visibleListItems[2];

            // Verify 'Select All' checkbox is not indeterminate.
            const selectAllCheckbox = visibleListItems[0];
            ControlsFunction.verifyCheckboxState(selectAllCheckbox.parentElement);

            // Uncheck third list item.
            UIInteractions.simulateClickEvent(thirdItemCbInput);
            tick(100);
            fix.detectChanges();

            // Verify 'Select All' checkbox is indeterminate.
            ControlsFunction.verifyCheckboxState(selectAllCheckbox.parentElement, true, true);

            // Check third list item again.
            UIInteractions.simulateClickEvent(thirdItemCbInput);
            tick(100);
            fix.detectChanges();

            // Verify 'Select All' checkbox is not indeterminate.
            ControlsFunction.verifyCheckboxState(selectAllCheckbox.parentElement);
        }));

        it('Should correctly update all items based on \'SelectAll\' checkbox.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const visibleListItems = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            const dataListItems = visibleListItems.slice(1, visibleListItems.length);

            // Verify all visible data list items are checked.
            for (const dataListItem of dataListItems) {
                ControlsFunction.verifyCheckboxState(dataListItem.parentElement);
            }

            // Click 'Select All' checkbox.
            let selectAllCbInput = visibleListItems[0];
            UIInteractions.simulateClickEvent(selectAllCbInput);
            tick(100);
            fix.detectChanges();

            // Verify all visible data list items are unchecked.
            for (const dataListItem of dataListItems) {
                ControlsFunction.verifyCheckboxState(dataListItem.parentElement, false);
            }

            // Click 'Select All' checkbox.
            selectAllCbInput = visibleListItems[0];
            UIInteractions.simulateClickEvent(selectAllCbInput);
            tick(100);
            fix.detectChanges();

            // Verify all visible data list items are checked.
            for (const dataListItem of dataListItems) {
                ControlsFunction.verifyCheckboxState(dataListItem.parentElement);
            }
        }));

        it('Should correctly update all \'SelectAll\' checkbox when not a single item is checked.', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'Released');
            fix.detectChanges();
            await wait(100);

            const visibleListItems = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            expect(visibleListItems.length).toBe(4);

            // Verify 'Select All' checkbox is checked.
            ControlsFunction.verifyCheckboxState(visibleListItems[0].parentElement);


            // Uncheck second, third and fourth list items.
            const secondListItemCbInput = visibleListItems[1];
            const thirdListItemCbInput = visibleListItems[2];
            const fourthListItemCbInput = visibleListItems[3];
            secondListItemCbInput.click();
            fix.detectChanges();
            thirdListItemCbInput.click();
            fix.detectChanges();
            fourthListItemCbInput.click();
            fix.detectChanges();

            // Verify 'Select All' checkbox is unchecked.
            ControlsFunction.verifyCheckboxState(visibleListItems[0].parentElement, false);
        });

        it('Should open custom filter dropdown when pressing \'Enter\' on custom filter cascade button.', fakeAsync(() => {
            grid.width = '700px';
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'AnotherField');
            tick(100);
            fix.detectChanges();

            const cascadeButton = GridFunctions.getExcelFilterCascadeButton(fix);

            // Verify that custom filter dropdown (the submenu) is not visible.
            let subMenu = fix.nativeElement.querySelector('.igx-drop-down__list.igx-toggle--hidden');
            expect(subMenu).not.toBeNull();

            cascadeButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
            tick(100);
            fix.detectChanges();


            // Verify that custom filter dropdown (the submenu) is visible.
            subMenu = fix.nativeElement.querySelector('.igx-drop-down__list.igx-toggle--hidden');
            expect(subMenu).toBeNull();
        }));

        it('Should close ESF when pressing \'Escape\'.', fakeAsync(() => {
            let excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            // Verify ESF is not visible.
            expect(excelMenu).toBeNull();

            GridFunctions.clickExcelFilterIcon(fix, 'Released');
            tick(100);
            fix.detectChanges();

            // Verify ESF is visible.
            excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            expect(excelMenu).not.toBeNull();

            excelMenu.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
            tick(100);
            fix.detectChanges();

            // Verify ESF is not visible.
            excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            expect(excelMenu).toBeNull();
        }));

        it('Should open/close ESF menu for respective column when pressing \'ctrl + shift + l\' on its filterCell chip.',
            fakeAsync(() => {
                let excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
                // Verify ESF is not visible.
                expect(excelMenu).toBeNull();

                const downloadsColumn = GridFunctions.getColumnHeader('Downloads', fix);
                UIInteractions.simulateClickAndSelectEvent(downloadsColumn);
                fix.detectChanges();

                UIInteractions.triggerKeyDownEvtUponElem('l', downloadsColumn.nativeElement, true, false, true, true);
                tick(200);
                fix.detectChanges();

                // Verify ESF menu is opened for the 'Downloads' column.
                excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
                expect(excelMenu).not.toBeNull();

                // Press the combination again.
                UIInteractions.triggerKeyDownEvtUponElem('l', excelMenu, true, false, true, true);
                tick(200);
                fix.detectChanges();

                excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
                expect(excelMenu).toBeNull();
            }));

        it('Should filter date by input string', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);

            const todayDateFull = SampleTestData.today;
            const todayDate = todayDateFull.getDate().toString();
            const dayOfWeek = todayDateFull.toString().substring(0, 3);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, todayDate, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBeGreaterThan(2);
            for (let i = 2; i < listItems.length; i++) {
                expect(listItems[i].textContent.toString().indexOf(todayDate) > -1).toBeTruthy();
            }

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, dayOfWeek, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
        }));

        it('Should filter ISO 8601 date by input string', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.toISOString() : null;
                return newRec;
            });
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);

            const todayDateFull = SampleTestData.today;
            const todayDate = todayDateFull.getDate().toString();
            const dayOfWeek = todayDateFull.toString().substring(0, 3);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, todayDate, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBeGreaterThan(2);
            for (let i = 2; i < listItems.length; i++) {
                expect(listItems[i].textContent.toString().indexOf(todayDate) > -1).toBeTruthy();
            }

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, dayOfWeek, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
        }));

        it('Should filter miliseconds date by input string', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.getTime() : null;
                return newRec;
            });
            fix.detectChanges();

            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(100);
            fix.detectChanges();

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);

            const todayDateFull = SampleTestData.today;
            const todayDate = todayDateFull.getDate().toString();
            const dayOfWeek = todayDateFull.toString().substring(0, 3);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, todayDate, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBeGreaterThan(2);
            for (let i = 2; i < listItems.length; i++) {
                expect(listItems[i].textContent.toString().indexOf(todayDate) > -1).toBeTruthy();
            }

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, dayOfWeek, fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
        }));

        it('Should ignore duplicate records when column\'s filteringIgnoreCase is true', fakeAsync(() => {
            const column = grid.getColumnByName('AnotherField');
            expect(column.filteringIgnoreCase).toBeTrue();

            GridFunctions.clickExcelFilterIcon(fix, 'AnotherField');
            tick(100);
            fix.detectChanges();

            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'a', 'Custom'],
                [true, true, true]);
        }));

        it('Should not ignore duplicate records when column\'s filteringIgnoreCase is false', fakeAsync(() => {
            const column = grid.getColumnByName('AnotherField');
            column.filteringIgnoreCase = false;
            expect(column.filteringIgnoreCase).toBeFalse();

            GridFunctions.clickExcelFilterIcon(fix, 'AnotherField');
            tick(100);
            fix.detectChanges();

            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'a', 'Custom', 'custoM', 'custom'],
                [true, true, true, true, true]);
        }));

        it('Should display "Add to current filter selection" button on typing in input', fakeAsync(() => {
            // Open excel style filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');
            // Type string in search box.
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '2', fix);
            tick(100);
            fix.detectChanges();

            // Verify that the second item is 'Add to current filter selection'.
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
            expect(listItems[1].innerText).toBe('Add current selection to filter');
        }));

        it('Should filter grid the same way as in Excel', fakeAsync(() => {
            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            // Type string in search box.
            let searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '2', fix);
            tick(100);
            fix.detectChanges();

            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent)
                .splice(2)
                .map(c => c.innerText)
                .sort();

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            tick(100);
            fix.detectChanges();

            // Get the results and verify that they match the list items.
            let gridCellValues = GridFunctions.getColumnCells(fix, 'Downloads')
                .map(c => c.nativeElement.innerText)
                .sort();

            expect(gridCellValues.length).toEqual(4);
            expect(gridCellValues).toEqual(listItems);

            // Open excel style custom filtering dialog again.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            // Type string in search box.
            searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '5', fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent)
                .splice(2)
                .map(c => c.innerText)
                .sort();

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            tick(100);
            fix.detectChanges();

            // Get the results and verify that they match the list items.
            gridCellValues = GridFunctions.getColumnCells(fix, 'Downloads')
                .map(c => c.nativeElement.innerText)
                .sort();

            expect(gridCellValues.length).toEqual(1);
            expect(gridCellValues).toEqual(listItems);
        }));

        it('Should disable the apply button when there are no results.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const applyButton = GridFunctions.getApplyButtonExcelStyleFiltering(fix) as HTMLElement;
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '3', fix);
            tick(100);
            fix.detectChanges();

            ControlsFunction.verifyButtonIsDisabled(applyButton);
        }));

        it('Should add list items to current filtered items when "Add to current filter selection" is selected.', fakeAsync(() => {
            const totalListItems = [];

            // Open excel style custom filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            // Type string in search box.
            let searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '5', fix);
            fix.detectChanges();
            tick();

            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent)
                .splice(2)
                .map(c => c.innerText);

            listItems.forEach(c => totalListItems.push(c));

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();
            tick();

            // Get the results and verify that they match the list items.
            let gridCellValues = GridFunctions.getColumnCells(fix, 'Downloads')
                .map(c => c.nativeElement.innerText);

            expect(gridCellValues.length).toEqual(1);
            expect(gridCellValues).toEqual(totalListItems);

            // Open excel style custom filtering dialog again.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            // Type string in search box.
            searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '7', fix);
            fix.detectChanges();
            tick();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent)
                .splice(2)
                .map(c => c.innerText);

            listItems.forEach(c => totalListItems.push(c));
            totalListItems.sort();

            // Select 'Add to current filter selection'.
            const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            checkbox[1].click();
            fix.detectChanges();
            tick();

            // Click 'apply' button to apply filter.
            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();
            tick();

            // Get the results and verify that they match the list items.
            gridCellValues = GridFunctions.getColumnCells(fix, 'Downloads')
                .map(c => c.nativeElement.innerText)
                .sort();

            expect(gridCellValues.length).toEqual(3);
            expect(gridCellValues).toEqual(totalListItems);

            // Open excel style custom filtering dialog again.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            fix.detectChanges();
            tick();

            // Get checkboxes and verify 'Select All' is indeterminate.
            const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            const checkboxes: any[] = Array.from(GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu));
            expect(checkboxes[0].indeterminate).toBeTrue();
        }));

        it('Should commit and close ESF on pressing \'Enter\'', fakeAsync(() => {
            // Open excel style filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            let excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

            // Verify ESF is visible.
            expect(excelMenu).not.toBeNull();

            // Type string in search box.
            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '2', fix);
            tick(100);
            fix.detectChanges();

            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent)
                .splice(2)
                .map(c => c.innerText)
                .sort();

            // Press 'Enter'
            inputNativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
            tick(100);
            fix.detectChanges();

            const gridCellValues = GridFunctions.getColumnCells(fix, 'Downloads')
                .map(c => c.nativeElement.innerText)
                .sort();

            // Verify that excel style filtering dialog is closed and data is filtered.
            excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            expect(excelMenu).toBeNull();
            expect(gridCellValues.length).toEqual(4);
            expect(gridCellValues).toEqual(listItems);
        }));

        it('Should clear input if there is text and \'Escape\' is pressed.', fakeAsync(() => {
            // Open excel style filtering dialog.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            let inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            let excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

            // Verify ESF is visible.
            expect(excelMenu).not.toBeNull();

            // Verify that the dialog is closed on pressing Escape.
            inputNativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
            fix.detectChanges();
            flush();

            excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            expect(excelMenu).toBeNull();

            // Open excel style filtering dialog again and type in the input.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');
            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, '2', fix);

            // Press Escape again and verify that ESF menu is still visible and the input is empty
            inputNativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            fix.detectChanges();
            flush();

            excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
            expect(excelMenu).not.toBeNull();
            expect(inputNativeElement.value).toBe('', 'input isn\'t cleared correctly');
        }));

        it('Should clear search criteria when selecting clear column filters option.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            let checkboxes = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            fix.detectChanges();

            checkboxes[0].click();
            tick();
            fix.detectChanges();

            checkboxes[2].click();
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix);
            tick();
            fix.detectChanges();
            expect(grid.filteredData.length).toEqual(1);

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            flush();

            let inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'Net', fix);

            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(3, 'incorrect rendered list items count');

            GridFunctions.clickClearFilterInExcelStyleFiltering(fix);
            flush();
            expect(grid.filteredData).toBeNull();

            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            expect(inputNativeElement.value).toBe('', 'search criteria is not cleared');

            checkboxes = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            const listItemsCheckboxes = checkboxes.slice(1, checkboxes.length);
            for (const checkbox of listItemsCheckboxes) {
                ControlsFunction.verifyCheckboxState(checkbox.parentElement);
            }
        }));

        it('Should filter cell by its formatted data when using FormattedValueFilteringStrategy', async () => {
            const formattedFilterStrategy = new FormattedValuesFilteringStrategy();
            grid.filterStrategy = formattedFilterStrategy;
            const productNameFormatter = (value: string): string => {
                const val = value ? value.toLowerCase() : '';
                if (val.includes('script')) {
                    return 'Web';
                } else if (val.includes('netadvantage')) {
                    return 'Desktop';
                } else {
                    return 'Other';
                }
            };
            grid.columnList.get(1).formatter = productNameFormatter;

            GridFunctions.clickExcelFilterIcon(fix, grid.columnList.get(1).field);
            await wait(200);
            fix.detectChanges();

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'script', fix);
            await wait(100);
            fix.detectChanges();

            let items = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(items.length).toBe(0);

            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'web', fix);
            await wait(100);
            fix.detectChanges();
            items = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(items.length).toBe(3);
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select all search results', 'Add current selection to filter', 'Web'],
                [true, false, true]);

            inputNativeElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
            await wait(100);
            fix.detectChanges();
            const cellValues = GridFunctions.getColumnCells(fix, 'ProductName').map(c => c.nativeElement.innerText).sort();
            expect(cellValues).toEqual(['Web', 'Web']);
        });

        it('Should display the default True and False resource strings in the search list for boolean column.', async () => {
            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'Released');
            fix.detectChanges();
            await wait(100);

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);

            expect(listItems.length).toBe(4, 'incorrect rendered list items count');
            expect(listItems[2].innerText).toBe('False', 'incorrect list item label');
            expect(listItems[3].innerText).toBe('True', 'incorrect list item label');

            const checkboxes = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            checkboxes[3].click();
            fix.detectChanges();
            await wait(100);

            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();
            await wait(100);

            expect(grid.filteredData.length).toEqual(5);
        });

        it('Should display the custom resource strings when specified in the search list for boolean column.', async () => {
            grid.resourceStrings.igx_grid_filter_false = 'No';
            grid.resourceStrings.igx_grid_filter_true = 'Yes';
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCodeAsync(fix, grid, 'Released');
            fix.detectChanges();
            await wait(100);

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);

            expect(listItems.length).toBe(4, 'incorrect rendered list items count');
            expect(listItems[2].innerText).toBe('No', 'incorrect list item label');
            expect(listItems[3].innerText).toBe('Yes', 'incorrect list item label');
        });

        it('Should sort items in excel style search correctly', fakeAsync(() => {
            const data = [
                {
                    Downloads: 254,
                    ID: 1,
                    ProductName: 'Ignite UI for JavaScript',
                    ReleaseDate: null,
                    ReleaseDateTime: null,
                    ReleaseTime: new Date(2010, 4, 27, 23, 0, 0),
                    Released: false,
                    AnotherField: 'BWord',
                    Revenue: 60000
                },
                {
                    Downloads: 127,
                    ID: 2,
                    ProductName: 'NetAdvantage',
                    ReleaseDate: null,
                    ReleaseDateTime: null,
                    ReleaseTime: new Date(2021, 4, 27, 1, 0, 0),
                    Released: true,
                    AnotherField: 'bWord',
                    Revenue: 50000
                },
                {
                    Downloads: 20,
                    ID: 3,
                    ProductName: 'Ignite UI for Angular',
                    ReleaseDate: null,
                    ReleaseDateTime: null,
                    ReleaseTime: new Date(2015, 4, 27, 12, 0, 0),
                    Released: null,
                    AnotherField: 'aWord',
                    Revenue: 100000
                }
            ];
            fix.componentInstance.data = data;
            fix.detectChanges();

            // Open excel style custom filtering dialog for string column
            GridFunctions.clickExcelFilterIcon(fix, 'AnotherField');
            tick(100);
            fix.detectChanges();

            // Verify items order is case INsensitive
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', 'aWord', 'BWord'],
                [true, true, true]);

            // Open excel style custom filtering dialog for time column
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseTime');
            tick(100);
            fix.detectChanges();

            // Verify items order is based only on time and not date
            verifyExcelStyleFilterAvailableOptions(fix,
                ['Select All', '1:00:00 AM', '12:00:00 PM', '11:00:00 PM'],
                [true, true, true, true]);
        }));
    });

    describe('Templates: ', () => {
        let fix; let grid;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringESFTemplatesComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;
        }));

        it('Should use custom templates for ESF components instead of default ones.', fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringESFEmptyTemplatesComponent);
            fix.detectChanges();
            grid = fix.componentInstance.grid;

            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            const filterableColumns = grid.columnList.filter((c) => c.filterable === true);
            for (const column of filterableColumns) {
                // Open ESF.
                GridFunctions.clickExcelFilterIcon(fix, column.field);
                tick(100);
                fix.detectChanges();
                const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

                // Verify custom column operations template is used.
                expect(excelMenu.querySelector('igx-excel-style-column-operations')).not.toBeNull();

                // Verify custom filter operations template is used.
                expect(excelMenu.querySelector('igx-excel-style-filter-operations')).not.toBeNull();

                // Verify components in default ESF column operations template are not present.
                expect(GridFunctions.getExcelFilteringHeaderComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringSortComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringMoveComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringPinComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringHideComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringColumnSelectionComponent(fix, excelMenu)).toBeNull();

                // Verify components in default ESF filter operations template are not present.
                expect(GridFunctions.getExcelFilteringClearFiltersComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringConditionalFilterComponent(fix, excelMenu)).toBeNull();
                expect(GridFunctions.getExcelFilteringSearchComponent(fix, excelMenu)).toBeNull();
            }
        }));

        it('Should filter and clear the excel search component correctly from template', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');

            const searchComponent = GridFunctions.getExcelStyleSearchComponent(fix);
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');

            // Type string in search box.
            const inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix, searchComponent);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 'ignite', fix);
            tick(100);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(4, 'incorrect rendered list items count');

            // Clear filtering of ESF search.
            const clearIcon: any = Array.from(searchComponent.querySelectorAll('igx-icon'))
                .find((icon: any) => icon.innerText === 'clear');
            clearIcon.click();
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix, searchComponent);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
            tick(100);
        }));

        it('Should move column left/right when clicking buttons from template', fakeAsync(() => {
            grid.moving = true;
            fix.detectChanges();

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');

            const moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            const moveRight = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[1];

            moveLeft.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[2].field).toBe('ProductName');
            expect(grid.columns[1].field).toBe('Downloads');

            moveLeft.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[1].field).toBe('ID');
            expect(grid.columns[0].field).toBe('Downloads');
            ControlsFunction.verifyButtonIsDisabled(moveLeft);

            moveRight.click();
            tick();
            fix.detectChanges();

            expect(grid.columns[0].field).toBe('ID');
            expect(grid.columns[1].field).toBe('Downloads');
            ControlsFunction.verifyButtonIsDisabled(moveLeft, false);
        }));

        it('should select/deselect column in external ESF template when interact with the column selection item through esf menu', () => {
            fix = TestBed.createComponent(IgxGridExternalESFTemplateComponent);
            grid = fix.componentInstance.grid;
            fix.detectChanges();

            // Test in single multiple mode
            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            spyOn(grid.columnSelectionChanging, 'emit');
            const column = grid.getColumnByName('Downloads');
            fix.componentInstance.esf.column = column;
            fix.detectChanges();

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(1);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, true);

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(2);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, false);

            // Test in single selection mode
            grid.columnSelection = GridSelectionMode.single;
            fix.detectChanges();

            grid.selectColumns(['ID']);
            fix.detectChanges();

            const columnId = grid.getColumnByName('ID');
            GridSelectionFunctions.verifyColumnAndCellsSelected(columnId);

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(3);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, true);
            GridSelectionFunctions.verifyColumnAndCellsSelected(columnId, false);

        });

        it('Should reset esf menu with templates on column change', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'Downloads');
            flush();

            let inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            UIInteractions.clickAndSendInputElementValue(inputNativeElement, 20, fix);

            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(3, 'incorrect rendered list items count');

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            flush();

            inputNativeElement = GridFunctions.getExcelStyleSearchComponentInput(fix);
            expect(inputNativeElement.value).toBe('', 'input value didn\'t reset');
        }));

        it('Should reset blank items on column change.', fakeAsync(() => {
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            flush();

            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems[1].innerText).toBe('(Blanks)');

            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'AnotherField');
            flush();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems[1].innerText).not.toBe('(Blanks)');
        }));

        it('Should use custom excel style filter icon instead of default one.', fakeAsync(() => {
            const header = GridFunctions.getColumnHeader('AnotherField', fix);
            fix.detectChanges();
            const icon = GridFunctions.getHeaderFilterIcon(header);
            fix.detectChanges();
            expect(icon).not.toBeNull();
            expect(icon.nativeElement.textContent.toLowerCase().trim()).toBe('filter_alt');
        }));

        it('should allow setting excel style filter icon via Input.', () => {
            grid.excelStyleHeaderIconTemplate = fix.componentInstance.customExcelHeaderIcon;
            fix.detectChanges();
            const header = GridFunctions.getColumnHeader('AnotherField', fix);
            fix.detectChanges();
            const icon = GridFunctions.getHeaderFilterIcon(header);
            fix.detectChanges();
            expect(icon.nativeElement.textContent.toLowerCase().trim()).toBe('search');
        });
    });

    describe('Load values on demand', () => {
        let fix;
        let grid: IgxGridComponent;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringESFLoadOnDemandComponent);
            grid = fix.componentInstance.grid;
            fix.detectChanges();
        }));

        it('Verify unique values are loaded correctly in ESF search component.', fakeAsync(() => {
            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(400);
            fix.detectChanges();

            // Verify items in search have not loaded yet and that the loading indicator is visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).not.toBeNull('esf loading indicator is not visible');

            // Wait for items to load.
            tick(650);

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify unique values are loaded correctly in ESF search component when using filtering strategy.', fakeAsync(() => {
            grid.uniqueColumnValuesStrategy = undefined;
            grid.filterStrategy = new LoadOnDemandFilterStrategy();
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(400);
            fix.detectChanges();

            // Verify items in search have not loaded yet and that the loading indicator is visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).not.toBeNull('esf loading indicator is not visible');

            // Wait for items to load.
            tick(650);

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify unique date values are loaded correctly in ESF search component.', fakeAsync(() => {
            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(400);
            fix.detectChanges();

            // Verify items in search have not loaded yet and that the loading indicator is visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).not.toBeNull('esf loading indicator is not visible');

            // Wait for items to load.
            tick(650);

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify unique ISO 8601 date values are loaded correctly in ESF search component.', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.toISOString() : null;
                return newRec;
            });
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(400);
            fix.detectChanges();

            // Verify items in search have not loaded yet and that the loading indicator is visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).not.toBeNull('esf loading indicator is not visible');

            // Wait for items to load.
            tick(650);

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify unique miliseconds date values are loaded correctly in ESF search component.', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.getTime() : null;
                return newRec;
            });
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(400);
            fix.detectChanges();

            // Verify items in search have not loaded yet and that the loading indicator is visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(0, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).not.toBeNull('esf loading indicator is not visible');

            // Wait for items to load.
            tick(650);

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify date values are displayed in correct format according to column pipeArgs', fakeAsync(() => {
            const downloads = ['Select All', '(Blanks)', '0,00', '20,00', '100,00', '127,00', '254,00', '702,00', '1 000,00'];
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.getTime() : null;
                return newRec;
            });
            const dates = fix.componentInstance.data.filter(el => el.ReleaseDate).map(el => new Date(el.ReleaseDate)).sort((a, b) => a - b);
            grid.locale = 'fr-FR';
            const datePipe = new DatePipe(grid.locale);
            const formatOptions = {
                format: 'longDate',
                digitsInfo: '1.2-2'
            };
            grid.getColumnByName('ReleaseDate').pipeArgs = formatOptions;
            grid.getColumnByName('Downloads').pipeArgs = formatOptions;
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(1050);
            fix.detectChanges();

            // Verify items in search have loaded and that the loading indicator is not visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');

            for (let i = 2; i < listItems.length; i++) {
                const label = datePipe.transform(dates[i - 2], formatOptions.format);
                expect(listItems[i].innerText).toBe(label);
            }

            const loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(1050);
            fix.detectChanges();

            // Verify items in search have loaded and that the loading indicator is not visible.
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');

            listItems.forEach((item, ind) => {
                expect(item.innerText).toBe(downloads[ind]);
            });
        }));

        it('Verify date values are displayed in correct format according to column formatter', fakeAsync(() => {
            fix.componentInstance.data = SampleTestData.excelFilteringData().map(rec => {
                const newRec = Object.assign({}, rec) as any;
                newRec.ReleaseDate = rec.ReleaseDate ? rec.ReleaseDate.getTime() : null;
                return newRec;
            });
            grid.locale = 'fr-FR';
            const datePipe = new DatePipe(grid.locale);
            grid.getColumnByName('ReleaseDate').formatter = ((value: any) => {
                const pipe = new DatePipe('fr-FR');
                const val = value !== null && value !== undefined && value !== '' ? pipe.transform(value, 'longDate') : 'No value!';
                return val;
            });

            const dates = fix.componentInstance.data.map(el => new Date(el.ReleaseDate)).sort((a, b) => a - b);
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(1050);
            fix.detectChanges();

            // Verify items in search have loaded and that the loading indicator is not visible.
            const listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');

            expect(listItems[1].innerText).toBe('(Blanks)');
            for (let i = 2; i < listItems.length; i++) {
                const date = dates[i];
                const label = date !== null && date !== undefined && date !== '' ? datePipe.transform(date, 'longDate') : 'No value!';
                expect(listItems[i].innerText).toBe(label);
            }

            const loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Verify date values are displayed in correct format according to column formatter after filtering', fakeAsync(() => {
            grid.locale = 'fr-FR';
            const datePipe = new DatePipe(grid.locale);
            grid.getColumnByName('ReleaseDate').formatter = ((value: any) => {
                const pipe = new DatePipe('fr-FR');
                const val = value !== null && value !== undefined && value !== '' ? pipe.transform(value, 'longDate') : 'No value!';
                return val;
            });

            const dates = fix.componentInstance.data.filter(d => d.ReleaseDate !== null && d.ReleaseDate !== undefined)
                .map(el => new Date(el.ReleaseDate)).sort((a, b) => a - b);
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(1050);
            fix.detectChanges();

            // Verify items in search have loaded and that the loading indicator is not visible.
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');

            const checkboxElements = GridFunctions.getExcelStyleFilteringCheckboxes(fix);
            checkboxElements[2].click();
            tick();
            fix.detectChanges();

            GridFunctions.clickApplyExcelStyleFiltering(fix);
            fix.detectChanges();

            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ReleaseDate');
            tick(1050);
            fix.detectChanges();

            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');

            expect(listItems[1].innerText).toBe('(Blanks)');
            for (let i = 2; i < listItems.length; i++) {
                const date = dates[i - 2];
                const label = date !== null && date !== undefined && date !== '' ? datePipe.transform(date, 'longDate') : 'No value!';
                expect(listItems[i].innerText).toBe(label);
            }

            const loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Done callback should be executed only once per column', fakeAsync(() => {
            const compInstance = fix.componentInstance as IgxGridFilteringESFLoadOnDemandComponent;
            // Open excel style custom filtering dialog and wait a bit.
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            tick(1000);
            fix.detectChanges();

            // Verify items in search have loaded and that the loading indicator is not visible.
            expect(compInstance.doneCallbackCounter).toBe(1, 'Incorrect done callback execution count');
            let listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(6, 'incorrect rendered list items count');
            let loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');

            GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
            tick(1000);
            fix.detectChanges();
            expect(compInstance.doneCallbackCounter).toBe(2, 'Incorrect done callback execution count');
            listItems = GridFunctions.getExcelStyleSearchComponentListItems(fix);
            expect(listItems.length).toBe(7, 'incorrect rendered list items count');
            loadingIndicator = GridFunctions.getExcelFilteringLoadingIndicator(fix);
            expect(loadingIndicator).toBeNull('esf loading indicator is visible');
        }));

        it('Should not execute done callback for null column', fakeAsync(() => {
            GridFunctions.clickExcelFilterIcon(fix, 'ProductName');
            fix.detectChanges();

            expect(() => {
                GridFunctions.clickExcelFilterIcon(fix, 'Downloads');
                tick(2000);
            }).not.toThrowError(/'dataType' of null/);
        }));
    });

    describe(null, () => {
        let fix: ComponentFixture<any>;
        let grid: IgxGridComponent;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridFilteringMCHComponent);
            grid = fix.componentInstance.grid;
            grid.filterMode = FilterMode.excelStyleFilter;
            fix.detectChanges();
        }));

        it('Should pin column next to already pinned group by moving it to the left.', fakeAsync(() => {
            // Test prerequisites
            grid.width = '1000px';
            fix.detectChanges();
            tick(100);
            // Adjust column widths, so their group can be pinned.
            const columnFields = ['ID', 'ProductName', 'Downloads', 'Released', 'ReleaseDate', 'AnotherField'];
            columnFields.forEach((columnField) => {
                const col = grid.columnList.find((c) => c.field === columnField);
                col.width = '100px';
            });
            fix.detectChanges();
            // Make 'AnotherField' column movable.
            const column = grid.columns.find((c) => c.field === 'AnotherField');
            grid.moving = true;
            fix.detectChanges();

            // Pin the 'General Information' group by pinning its child 'ProductName' column.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'ProductName');
            tick(100);
            fix.detectChanges();
            GridFunctions.clickPinIconInExcelStyleFiltering(fix, false);
            tick(200);
            fix.detectChanges();

            // Verify 'AnotherField' column is not pinned.
            GridFunctions.verifyColumnIsPinned(column, false, 7);

            // Try to pin the 'AnotherField' column by moving it to the left.
            GridFunctions.clickExcelFilterIconFromCode(fix, grid, 'AnotherField');
            const moveLeft = GridFunctions.getExcelStyleFilteringMoveButtons(fix)[0];
            UIInteractions.simulateClickEvent(moveLeft);
            tick(DEBOUNCETIME);
            fix.detectChanges();

            // Verify 'AnotherField' column is successfully pinned next to the column group.
            GridFunctions.verifyColumnIsPinned(column, true, 8);
        }));
    });

    describe('External Excel Style Filtering', () => {
        let fix; let grid;
        beforeEach(fakeAsync(() => {
            fix = TestBed.createComponent(IgxGridExternalESFComponent);
            grid = fix.componentInstance.grid;
            fix.detectChanges();
        }));

        it('Should allow hosting Excel Style filtering component outside of the grid.', fakeAsync(() => {
            // sort
            GridFunctions.clickSortAscInExcelStyleFiltering(fix);
            fix.detectChanges();
            expect(grid.sortingExpressions[0].fieldName).toEqual('ProductName');
            expect(grid.sortingExpressions[0].dir).toEqual(SortingDirection.Asc);

            // pin
            GridFunctions.clickPinIconInExcelStyleFiltering(fix, false);
            fix.detectChanges();
            expect(grid.pinnedColumns[0].field).toEqual('ProductName');

            // filter
            verifyExcelStyleFilterAvailableOptions(fix, ['Select All', '(Blanks)', 'Ignite UI for Angular',
                'Ignite UI for JavaScript', 'NetAdvantage', 'Some other item with Script'],
                [true, true, true, true, true, true]);
            toggleExcelStyleFilteringItems(fix, true, 1, 4);
            expect(grid.rowList.length).toBe(3);

            // hide
            GridFunctions.clickHideIconInExcelStyleFiltering(fix, false);
            fix.detectChanges();
            expect(grid.columnList.get(1).hidden).toBeTruthy();
        }));

        it('Column selection button should be visible/hidden when column is selectable/not selectable', fakeAsync(() => {
            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            let columnSelectionContainer = GridFunctions.getExcelFilteringColumnSelectionContainer(fix);
            expect(columnSelectionContainer).toBeNull();

            const esf = fix.componentInstance.esf;
            esf.column = grid.getColumnByName('Downloads');
            tick();
            fix.detectChanges();

            columnSelectionContainer = GridFunctions.getExcelFilteringColumnSelectionContainer(fix);
            expect(columnSelectionContainer).not.toBeNull();

            grid.columnSelection = GridSelectionMode.none;
            fix.detectChanges();
            fix.componentInstance.esf.cdr.detectChanges();

            columnSelectionContainer = GridFunctions.getExcelFilteringColumnSelectionContainer(fix);
            expect(columnSelectionContainer).toBeNull();
        }));

        it('should select/deselect column when interact with the column selection item through esf menu', fakeAsync(() => {
            // Test in single multiple mode
            grid.columnSelection = GridSelectionMode.multiple;
            fix.detectChanges();

            spyOn(grid.columnSelectionChanging, 'emit');
            const column = grid.getColumnByName('Downloads');
            fix.componentInstance.esf.column = column;
            tick();
            fix.detectChanges();

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(1);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, true);

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(2);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, false);

            // Test in single selection mode
            grid.columnSelection = GridSelectionMode.single;
            fix.detectChanges();

            grid.selectColumns(['ID']);
            fix.detectChanges();

            const columnId = grid.getColumnByName('ID');
            GridSelectionFunctions.verifyColumnAndCellsSelected(columnId);

            GridFunctions.clickColumnSelectionInExcelStyleFiltering(fix);
            fix.detectChanges();

            expect(grid.columnSelectionChanging.emit).toHaveBeenCalledTimes(3);
            GridSelectionFunctions.verifyColumnAndCellsSelected(column, true);
            GridSelectionFunctions.verifyColumnAndCellsSelected(columnId, false);

        }));

    });
});

describe('IgxGrid - Custom Filtering Strategy #grid', () => {
    let fix: ComponentFixture<any>;
    let grid: IgxGridComponent;
    configureTestSuite((() => {
        return TestBed.configureTestingModule({
            imports: [
                NoopAnimationsModule,
                CustomFilteringStrategyComponent
            ]
        });
    }));

    beforeEach(fakeAsync(() => {
        fix = TestBed.createComponent(CustomFilteringStrategyComponent);
        fix.detectChanges();
        grid = fix.componentInstance.grid;
    }));

    it('Should be able to set custom filtering strategy', () => {
        expect(grid.filterStrategy).toBeDefined();
        expect(grid.filterStrategy).toBeInstanceOf(FilteringStrategy);
        grid.filterStrategy = fix.componentInstance.strategy;
        fix.detectChanges();

        expect(grid.filterStrategy).toEqual(fix.componentInstance.strategy);
    });

    it('Should be able to override getFieldValue method', fakeAsync(() => {
        GridFunctions.clickFilterCellChipUI(fix, 'Name'); // Name column contains nested object as a value
        tick(150);
        fix.detectChanges();

        GridFunctions.typeValueInFilterRowInput('ca', fix);
        tick(DEBOUNCETIME);
        GridFunctions.submitFilterRowInput(fix);
        tick(DEBOUNCETIME);
        fix.detectChanges();

        expect(grid.filteredData).toEqual([]);
        GridFunctions.resetFilterRow(fix);
        GridFunctions.closeFilterRow(fix);
        fix.detectChanges();

        // Apply the custom strategy and perform the same filter
        grid.filterStrategy = fix.componentInstance.strategy;
        fix.detectChanges();
        GridFunctions.clickFilterCellChipUI(fix, 'Name'); // Name column contains nested object as a value
        tick(150);
        fix.detectChanges();

        GridFunctions.typeValueInFilterRowInput('ca', fix);
        tick(DEBOUNCETIME);
        GridFunctions.submitFilterRowInput(fix);
        tick(DEBOUNCETIME);
        fix.detectChanges();

        expect(grid.filteredData).toEqual(
            [{ ID: 1, Name: { FirstName: 'Casey', LastName: 'Houston' }, JobTitle: 'Vice President', Company: 'Company A' }]);
    }));

    it('Should be able to override findMatchByExpression method', fakeAsync(() => {
        GridFunctions.clickFilterCellChipUI(fix, 'JobTitle'); // Default strategy is case not sensitive
        tick(150);
        fix.detectChanges();

        GridFunctions.typeValueInFilterRowInput('direct', fix);
        tick(DEBOUNCETIME);
        GridFunctions.submitFilterRowInput(fix);
        tick(DEBOUNCETIME);
        fix.detectChanges();

        expect(grid.filteredData).toEqual([
            { ID: 2, Name: { FirstName: 'Gilberto', LastName: 'Todd' }, JobTitle: 'Director', Company: 'Company C' },
            { ID: 3, Name: { FirstName: 'Tanya', LastName: 'Bennett' }, JobTitle: 'Director', Company: 'Company A' }]);
        GridFunctions.resetFilterRow(fix);
        GridFunctions.closeFilterRow(fix);
        fix.detectChanges();

        // Apply the custom strategy and perform the same filter
        grid.filterStrategy = fix.componentInstance.strategy;
        fix.detectChanges();
        GridFunctions.clickFilterCellChipUI(fix, 'JobTitle');
        tick(150);
        fix.detectChanges();

        GridFunctions.typeValueInFilterRowInput('direct', fix);
        tick(DEBOUNCETIME);
        GridFunctions.submitFilterRowInput(fix);
        tick(DEBOUNCETIME);
        fix.detectChanges();

        expect(grid.filteredData).toEqual([]);
    }));

    it('should use the custom filtering strategy when filter the grid through API method', fakeAsync(() => {
        grid.filterStrategy = fix.componentInstance.strategy;
        fix.detectChanges();
        grid.filter('Name', 'D', IgxStringFilteringOperand.instance().condition('contains'));
        tick(DEBOUNCETIME);
        fix.detectChanges();

        expect(grid.filteredData).toEqual([
            {
                ID: 7, Name: { FirstName: 'Debra', LastName: 'Morton' },
                JobTitle: 'Associate Software Developer', Company: 'Company B'
            },
            { ID: 10, Name: { FirstName: 'Eduardo', LastName: 'Ramirez' }, JobTitle: 'Manager', Company: 'Company E' }]);
    }));
});

const verifyFilterRowUI = (input, closeButton, resetButton, buttonResetDisabled = true) => {
    ControlsFunction.verifyButtonIsDisabled(closeButton.nativeElement, false);
    ControlsFunction.verifyButtonIsDisabled(resetButton.nativeElement, buttonResetDisabled);
    expect(input.nativeElement.offsetHeight).toBeGreaterThan(0);
};

const verifyFilterUIPosition = (filterUIContainer, grid) => {
    const filterUiRightBorder = filterUIContainer.nativeElement.offsetParent.offsetLeft +
        filterUIContainer.nativeElement.offsetLeft + filterUIContainer.nativeElement.offsetWidth;
    expect(filterUiRightBorder).toBeLessThanOrEqual(grid.nativeElement.offsetWidth);
};

const isExcelSearchScrollBarVisible = (fix) => {
    const searchScrollbar = GridFunctions.getExcelStyleSearchComponentScrollbar(fix);
    return searchScrollbar.offsetHeight < searchScrollbar.children[0].offsetHeight;
};

const checkUIForType = (type: string, elem: DebugElement) => {
    let expectedConditions;
    let expectedInputType;
    const isReadOnly = type === 'bool' ? true : false;
    switch (type) {
        case 'string':
            expectedConditions = IgxStringFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'text';
            break;
        case 'number':
            expectedConditions = IgxNumberFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'number';
            break;
        case 'date':
            expectedConditions = IgxDateFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'datePicker';
            break;
        case 'bool':
            expectedConditions = IgxBooleanFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'text';
            break;
        case 'dateTime':
            expectedConditions = IgxDateTimeFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'text';
            break;
        case 'time':
            expectedConditions = IgxTimeFilteringOperand.instance().operations.filter(f => !f.hidden);
            expectedInputType = 'timePicker';
            break;
    }
    GridFunctions.openFilterDD(elem);
    const ddList = elem.query(By.css('div.igx-drop-down__list-scroll'));
    const ddItems = ddList.nativeElement.children;
    // check drop-down conditions
    for (let i = 0; i < expectedConditions.length; i++) {
        const txt = expectedConditions[i].name.split(/(?=[A-Z])/).join(' ').toLowerCase();
        expect(txt).toEqual(ddItems[i].textContent.toLowerCase());
    }
    // check input is correct type
    const filterUIRow = elem.query(By.css(FILTER_UI_ROW));
    if (expectedInputType !== 'datePicker') {
        const input = filterUIRow.query(By.css('.igx-input-group__input'));
        expect(input.nativeElement.type).toBe(expectedInputType);
        expect(input.nativeElement.attributes.hasOwnProperty('readonly')).toBe(isReadOnly);
    } else {
        const datePicker = filterUIRow.query(By.directive(IgxDatePickerComponent));
        expect(datePicker).not.toBe(null);
    }
};

const verifyExcelStyleFilteringDisplayDensity = (fix: ComponentFixture<any>, expectedDisplayDensity: DisplayDensity) => {
    // Get excel style dialog
    const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

    // Verify display density of search input and list.
    const excelSearch = excelMenu.querySelector('igx-excel-style-search');
    const inputGroup = excelSearch.querySelector('igx-input-group');
    const list = excelSearch.querySelector('igx-list');
    expect(inputGroup.classList.contains(getInputGroupDensityClass(expectedDisplayDensity))).toBe(true,
        'incorrect inputGroup density');
    expect(list.classList.contains(getListDensityClass(expectedDisplayDensity))).toBe(true,
        'incorrect list density');

    // Verify display density of all flat and raised buttons in excel stlye dialog.
    const flatButtons: HTMLElement[] = excelMenu.querySelectorAll('.igx-button--flat');
    const raisedButtons: HTMLElement[] = excelMenu.querySelectorAll('.igx-button--raised');
    const buttons: HTMLElement[] = Array.from(flatButtons).concat(Array.from(raisedButtons));
    buttons.forEach((button) => {
        if (expectedDisplayDensity === DisplayDensity.comfortable) {
            // If expected display density is comfortable, then button should not have 'compact' and 'cosy' classes.
            expect(button.classList.contains(getButtonDensityClass(DisplayDensity.compact))).toBe(false,
                'incorrect button density');
            expect(button.classList.contains(getButtonDensityClass(DisplayDensity.cosy))).toBe(false,
                'incorrect button density');
        } else {
            expect(button.classList.contains(getButtonDensityClass(expectedDisplayDensity))).toBe(true,
                'incorrect button density');
        }
    });

    // Verify column pinning and column hiding elements in header area and actions area
    // are shown based on the expected display density.
    verifyPinningHidingDisplayDensity(fix, expectedDisplayDensity);
    // Verify column sorting and column moving buttons are positioned either on right of their
    // respective header or under it, based on the expected display density.
    verifySortMoveDisplayDensity(fix, expectedDisplayDensity);
};

const verifyPinningHidingDisplayDensity = (fix: ComponentFixture<any>, expectedDisplayDensity: DisplayDensity) => {
    // Get excel style dialog
    const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

    // Get column pinning and column hiding icons from header (if present at all)
    const headerTitle = excelMenu.querySelector('h4');
    const headerIcons = GridFunctions.getExcelFilteringHeaderIcons(fix, excelMenu);
    const headerAreaPinIcon: HTMLElement =
        headerIcons.find((buttonIcon: any) => buttonIcon.innerHTML.indexOf('name="pin-left"') !== -1) as HTMLElement;
    const headerAreaUnpinIcon: HTMLElement
        = headerIcons.find((buttonIcon: any) => buttonIcon.innerHTML.indexOf('name="unpin-left"') !== -1) as HTMLElement;
    const headerAreaColumnHidingIcon: HTMLElement =
        headerIcons.find((buttonIcon: any) => buttonIcon.innerText === 'visibility_off') as HTMLElement;

    // Get column pinning and column hiding icons from actionsArea (if present at all)
    const actionsPinArea = GridFunctions.getExcelFilteringPinContainer(fix, excelMenu);
    const actionsAreaColumnHidingIcon = GridFunctions.getExcelFilteringHideContainer(fix, excelMenu);

    if (expectedDisplayDensity === DisplayDensity.comfortable) {
        // Verify icons in header are not present.
        expect(headerAreaPinIcon === null || headerAreaPinIcon === undefined).toBe(true,
            'headerArea pin icon is present');
        expect(headerAreaUnpinIcon === null || headerAreaUnpinIcon === undefined).toBe(true,
            'headerArea unpin icon is present');
        expect(headerAreaColumnHidingIcon === null || headerAreaColumnHidingIcon === undefined).toBe(true,
            'headerArea column hiding icon is present');
        // Verify icons in actions area are present.
        expect(actionsPinArea !== null).toBe(true, 'actionsArea pin/unpin icon is  NOT present');
        expect(actionsAreaColumnHidingIcon).not.toBeNull('actionsArea column hiding icon is  NOT present');
    } else {
        // Verify icons in header are present.
        expect((headerAreaPinIcon !== null) || (headerAreaUnpinIcon !== null)).toBe(true,
            'headerArea pin/unpin icon is  NOT present');
        expect(headerAreaColumnHidingIcon).not.toBeNull('headerArea column hiding icon is  NOT present');
        // Verify icons in actions area are not present.
        expect(actionsPinArea).toBeNull('actionsArea pin icon is present');
        expect(actionsAreaColumnHidingIcon).toBeNull('headerArea column hiding icon is present');
        // Verify icons are on right of the title
        const headerTitleRect = headerTitle.getBoundingClientRect();
        const pinUnpinIconRect = ((headerAreaPinIcon !== null) ? headerAreaPinIcon : headerAreaUnpinIcon).getBoundingClientRect();
        const columnHidingRect = headerAreaColumnHidingIcon.getBoundingClientRect();

        expect(pinUnpinIconRect.left >= headerTitleRect.right).toBe(true,
            'pinUnpin icon is NOT on the right of top header');
        expect(columnHidingRect.left > headerTitleRect.right).toBe(true,
            'columnHiding icon is NOT on the right of top header');
    }
};

const verifySortMoveDisplayDensity = (fix: ComponentFixture<any>, expectedDisplayDensity: DisplayDensity) => {
    // Get excel style dialog.
    const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);

    // Get container of sort component and its header and buttons.
    const sortContainer = GridFunctions.getExcelStyleFilteringSortContainer(fix, excelMenu);
    const sortHeaderRect = sortContainer.querySelector('header').getBoundingClientRect();
    const sortButtons = GridFunctions.getExcelStyleFilteringSortButtons(fix, excelMenu);

    // Get container of move component and its header and buttons.
    const moveContainer = GridFunctions.getExcelStyleFilteringMoveContainer(fix, excelMenu);
    const moveHeaderRect = moveContainer.querySelector('header').getBoundingClientRect();
    const moveButtons = GridFunctions.getExcelStyleFilteringMoveButtons(fix, excelMenu);

    const isCompact = expectedDisplayDensity === DisplayDensity.compact;
    // Verify sort buttons are on right of the sort title if density is 'compact'
    // or that they are under the sort title if density is not 'compact'.
    expect(sortHeaderRect.right <= sortButtons[0].getBoundingClientRect().left).toBe(isCompact,
        'incorrect sort button horizontal position based on the sort title');
    expect(sortHeaderRect.right <= sortButtons[1].getBoundingClientRect().left).toBe(isCompact,
        'incorrect sort button horizontal position based on the sort title');
    expect(sortHeaderRect.bottom <= sortButtons[0].getBoundingClientRect().top).toBe(!isCompact,
        'incorrect sort button vertical position based on the sort title');
    expect(sortHeaderRect.bottom <= sortButtons[1].getBoundingClientRect().top).toBe(!isCompact,
        'incorrect sort button vertical position based on the sort title');
    // Verify move buttons are on right of the move title if density is 'compact'
    // or that they are under the sort title if density is not 'compact'.
    expect(moveHeaderRect.right < moveButtons[0].getBoundingClientRect().left).toBe(isCompact,
        'incorrect move button horizontal position based on the sort title');
    expect(moveHeaderRect.right < moveButtons[1].getBoundingClientRect().left).toBe(isCompact,
        'incorrect move button horizontal position based on the sort title');
    expect(moveHeaderRect.bottom <= moveButtons[0].getBoundingClientRect().top).toBe(!isCompact,
        'incorrect move button vertical position based on the sort title');
    expect(moveHeaderRect.bottom <= moveButtons[1].getBoundingClientRect().top).toBe(!isCompact,
        'incorrect move button vertical position based on the sort title');
};

const verifyExcelCustomFilterDisplayDensity = (fix: ComponentFixture<any>, expectedDisplayDensity: DisplayDensity) => {
    // Excel style filtering custom filter dialog
    const customFilterMenu = GridFunctions.getExcelStyleCustomFilteringDialog(fix);

    // Verify display density of all flat and raised buttons in custom filter dialog.
    const flatButtons = customFilterMenu.querySelectorAll('.igx-button--flat');
    const raisedButtons = customFilterMenu.querySelectorAll('.igx-button--raised');
    const buttons = Array.from(flatButtons).concat(Array.from(raisedButtons));
    buttons.forEach((button) => {
        if (expectedDisplayDensity === DisplayDensity.comfortable) {
            // If expected display density is comfortable, then button should not have 'compact' and 'cosy' classes.
            expect(button.classList.contains(getButtonDensityClass(DisplayDensity.compact))).toBe(false,
                'incorrect button density in custom filter dialog');
            expect(button.classList.contains(getButtonDensityClass(DisplayDensity.cosy))).toBe(false,
                'incorrect button density in custom filter dialog');
        } else {
            expect(button.classList.contains(getButtonDensityClass(expectedDisplayDensity))).toBe(true,
                'incorrect button density in custom filter dialog');
        }
    });

    // Verify display density of all input groups in custom filter dialog.
    const inputGroups = customFilterMenu.querySelectorAll('igx-input-group');
    inputGroups.forEach((inputGroup) => {
        expect(inputGroup.classList.contains(getInputGroupDensityClass(expectedDisplayDensity))).toBe(true,
            'incorrect inputGroup density in custom filter dialog');
    });
};

const verifyGridSubmenuDisplayDensity = (gridNativeElement: HTMLElement, expectedDisplayDensity: DisplayDensity) => {
    const outlet = gridNativeElement.querySelector('.igx-grid__outlet');
    const dropdowns = Array.from(outlet.querySelectorAll('.igx-drop-down__list'));
    const visibleDropdown: any = dropdowns.find((d) => !d.classList.contains('igx-toggle--hidden'));
    const dropdownItems = visibleDropdown.querySelectorAll('igx-drop-down-item');
    dropdownItems.forEach((dropdownItem) => {
        expect(dropdownItem.classList.contains(getDropdownItemDensityClass(expectedDisplayDensity))).toBe(true,
            'incorrect dropdown item density');
    });
};

const getListDensityClass = (displayDensity: DisplayDensity) => {
    let densityClass;
    switch (displayDensity) {
        case DisplayDensity.compact: densityClass = 'igx-list--compact'; break;
        case DisplayDensity.cosy: densityClass = 'igx-list--cosy'; break;
        default: densityClass = 'igx-list'; break;
    }
    return densityClass;
};

const getInputGroupDensityClass = (displayDensity: DisplayDensity) => {
    let densityClass;
    switch (displayDensity) {
        case DisplayDensity.compact: densityClass = 'igx-input-group--compact'; break;
        case DisplayDensity.cosy: densityClass = 'igx-input-group--cosy'; break;
        default: densityClass = 'igx-input-group--comfortable'; break;
    }
    return densityClass;
};

/**
 * Gets the corresponding class that a flat/raised/outlined button
 * has added to it additionally based on displayDensity input.
 */
const getButtonDensityClass = (displayDensity: DisplayDensity) => {
    let densityClass;
    switch (displayDensity) {
        case DisplayDensity.compact: densityClass = 'igx-button--compact'; break;
        case DisplayDensity.cosy: densityClass = 'igx-button--cosy'; break;
        default: densityClass = ''; break;
    }
    return densityClass;
};

const getDropdownItemDensityClass = (displayDensity: DisplayDensity) => {
    let densityClass;
    switch (displayDensity) {
        case DisplayDensity.compact: densityClass = 'igx-drop-down__item--compact'; break;
        case DisplayDensity.cosy: densityClass = 'igx-drop-down__item--cosy'; break;
        default: densityClass = 'igx-drop-down__item'; break;
    }
    return densityClass;
};

const verifyFilteringExpression = (operand: IFilteringExpression, fieldName: string, conditionName: string, searchVal: any) => {
    expect(operand.fieldName).toBe(fieldName);
    expect(operand.condition.name).toBe(conditionName);
    expect(operand.searchVal).toEqual(searchVal);
};

const verifyExcelStyleFilterAvailableOptions = (fix, labels: string[], checked: boolean[]) => {
    const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
    const labelElements: any[] = Array.from(GridFunctions.getExcelStyleSearchComponentListItems(fix, excelMenu));
    const checkboxElements: any[] = Array.from(GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu));

    expect(labelElements.length).toBe(labels.length, 'incorrect rendered list items count');
    labels.forEach((l, index) => {
        expect(l).toEqual(labelElements[index].innerText);
    });
    checked.forEach((c, index) => {
        expect(checkboxElements[index].indeterminate ? null : checkboxElements[index].checked).toEqual(c);
    });
};

const toggleExcelStyleFilteringItems = (fix, shouldApply: boolean, ...itemIndices: number[]) => {
    const excelMenu = GridFunctions.getExcelStyleFilteringComponent(fix);
    const checkbox = GridFunctions.getExcelStyleFilteringCheckboxes(fix, excelMenu);

    for (const index of itemIndices) {
        checkbox[index].click();
    }
    tick();
    fix.detectChanges();

    if (shouldApply) {
        GridFunctions.clickApplyExcelStyleFiltering(fix, excelMenu);
        tick();
        fix.detectChanges();
    }
};

/**
 * Verfiy multiple condition chips on their respective indices (asc order left to right)
 * are whether fully visible or not.
 */
const verifyMultipleChipsVisibility = (fix, expectedVisibilities: boolean[]) => {
    for (let index = 0; index < expectedVisibilities.length; index++) {
        verifyChipVisibility(fix, index, expectedVisibilities[index]);
    }
};

/**
 * Verfiy that the condition chip on the respective index (asc order left to right)
 * is whether fully visible or not.
 */
const verifyChipVisibility = (fix, index: number, shouldBeFullyVisible: boolean) => {
    const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent));
    const visibleChipArea = filteringRow.query(By.css('.igx-grid__filtering-row-main'));
    const visibleChipAreaRect = visibleChipArea.nativeElement.getBoundingClientRect();

    const chip = GridFunctions.getFilterConditionChip(fix, index);
    const chipRect = chip.getBoundingClientRect();

    expect(chipRect.left >= visibleChipAreaRect.left && chipRect.right <= visibleChipAreaRect.right)
        .toBe(shouldBeFullyVisible, 'chip[' + index + '] visibility is incorrect');
};

const emitFilteringDoneOnResetClick = (fix, grid, filterVal: any, columnName: string, condition) => {
    filterGrid(fix, grid, columnName, filterVal, condition);

    const filteringExpressions = grid.filteringExpressionsTree.find(columnName) as FilteringExpressionsTree;
    verifyEmitFilteringDone(grid, filteringExpressions, 1);

    GridFunctions.clickFilterCellChip(fix, columnName);
    GridFunctions.resetFilterRow(fix);

    const emptyFilter = new FilteringExpressionsTree(null, columnName);
    verifyEmitFilteringDone(grid, emptyFilter, 2);

    const filterUiRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
    const reset = filterUiRow.queryAll(By.css('button'))[0];
    expect(reset.nativeElement.classList.contains('igx-button--disabled')).toEqual(true);
};

const emitFilteringDoneOnInputClear = (fix, grid, filterVal, columnName, condition) => {
    filterGrid(fix, grid, columnName, filterVal, condition);

    GridFunctions.clickFilterCellChip(fix, columnName);

    grid.filteringRow.onClearClick();
    tick(100);
    fix.detectChanges();

    const emptyFilter = new FilteringExpressionsTree(null, columnName);
    verifyEmitFilteringDone(grid, emptyFilter, 2);
};

const verifyRemoveChipFromHeader = (fix, grid, filterVal, columnName, condition, rowListLength, cellIndex) => {
    filterGrid(fix, grid, columnName, filterVal, condition);

    const filteringExpressions = grid.filteringExpressionsTree.find(columnName) as FilteringExpressionsTree;
    verifyEmitFilteringDone(grid, filteringExpressions, 1);

    const filteringCells = GridFunctions.getFilteringCells(fix);
    const stringCellChip = filteringCells[cellIndex].query(By.css('igx-chip'));

    // remove chip
    ControlsFunction.clickChipRemoveButton(stringCellChip.nativeElement);
    tick(30);
    fix.detectChanges();

    expect(grid.rowList.length).toEqual(8);

    const emptyFilter = new FilteringExpressionsTree(null, columnName);
    verifyEmitFilteringDone(grid, emptyFilter, 2);
};

const closeChipFromFiteringUIRow = (fix, grid, columnName, index) => {
    GridFunctions.clickFilterCellChip(fix, columnName);

    const filterUIRow = fix.debugElement.query(By.css(FILTER_UI_ROW));
    const close = filterUIRow.queryAll(By.css('button'))[1];

    GridFunctions.openFilterDDAndSelectCondition(fix, index);

    close.triggerEventHandler('click', null);
    tick();
    fix.detectChanges();

    const filteringExpressions = grid.filteringExpressionsTree.find(columnName) as FilteringExpressionsTree;
    verifyEmitFilteringDone(grid, filteringExpressions, 1);
};

const verifyEmitFilteringDone = (grid, filteringExpressions, calledTimes) => {
    const args = { owner: grid, cancel: false, filteringExpressions };
    expect(grid.filtering.emit).toHaveBeenCalledWith(args);
    expect(grid.filtering.emit).toHaveBeenCalledTimes(calledTimes);
    expect(grid.filteringDone.emit).toHaveBeenCalledWith(filteringExpressions);
    expect(grid.filteringDone.emit).toHaveBeenCalledTimes(calledTimes);
};

const filterGrid = (fix, grid, columnName, filterVal, condition) => {
    grid.filter(columnName, filterVal, condition);
    tick(100);
    fix.detectChanges();
};