igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
2,378 lines • 108 kB
text/typescript
import { AsyncPipe } from '@angular/common';
import { AfterViewInit, ChangeDetectorRef, Component, DebugElement, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { FormsModule, NgForm, ReactiveFormsModule, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { IgxComboDropDownComponent } from '../combo/combo-dropdown.component';
import { IgxComboState } from '../combo/combo.common';
import { RemoteDataService } from '../combo/combo.component.spec';
import { IComboSelectionChangingEventArgs, IgxComboFooterDirective, IgxComboHeaderDirective, IgxComboItemDirective, IgxComboToggleIconDirective } from '../combo/public_api';
import { DisplayDensity } from '../core/density';
import { IgxSelectionAPIService } from '../core/selection';
import { IBaseCancelableBrowserEventArgs, PlatformUtil } from '../core/utils';
import { IgxIconComponent } from '../icon/icon.component';
import { IgxIconService } from '../icon/icon.service';
import { IgxInputState, IgxLabelDirective } from '../input-group/public_api';
import { AbsoluteScrollStrategy, AutoPositionStrategy, ConnectedPositioningStrategy } from '../services/public_api';
import { configureTestSuite } from '../test-utils/configure-suite';
import { UIInteractions, wait } from '../test-utils/ui-interactions.spec';
import { IgxSimpleComboComponent, ISimpleComboSelectionChangingEventArgs } from './public_api';
const CSS_CLASS_COMBO = 'igx-combo';
const SIMPLE_COMBO_ELEMENT = 'igx-simple-combo';
const CSS_CLASS_COMBO_DROPDOWN = 'igx-combo__drop-down';
const CSS_CLASS_DROPDOWN = 'igx-drop-down';
const CSS_CLASS_DROPDOWNLIST = 'igx-drop-down__list';
const CSS_CLASS_DROPDOWNLIST_SCROLL = 'igx-drop-down__list-scroll';
const CSS_CLASS_CONTENT = 'igx-combo__content';
const CSS_CLASS_CONTAINER = 'igx-display-container';
const CSS_CLASS_DROPDOWNLISTITEM = 'igx-drop-down__item';
const CSS_CLASS_TOGGLEBUTTON = 'igx-combo__toggle-button';
const CSS_CLASS_CLEARBUTTON = 'igx-combo__clear-button';
const CSS_CLASS_ADDBUTTON = 'igx-combo__add-item';
const CSS_CLASS_FOCUSED = 'igx-drop-down__item--focused';
const CSS_CLASS_INPUTGROUP = 'igx-input-group';
const CSS_CLASS_COMBO_INPUTGROUP = 'igx-input-group__input';
const CSS_CLASS_INPUTGROUP_REQUIRED = 'igx-input-group--required';
const CSS_CLASS_HEADER = 'header-class';
const CSS_CLASS_FOOTER = 'footer-class';
const CSS_CLASS_ITEM = 'igx-drop-down__item';
const CSS_CLASS_ITEM_COSY = 'igx-drop-down__item--cosy';
const CSS_CLASS_ITEM_COMPACT = 'igx-drop-down__item--compact';
const CSS_CLASS_HEADER_ITEM = 'igx-drop-down__header';
const CSS_CLASS_HEADER_COSY = 'igx-drop-down__header--cosy';
const CSS_CLASS_HEADER_COMPACT = 'igx-drop-down__header--compact';
const CSS_CLASS_INPUT_COSY = 'igx-input-group--cosy';
const CSS_CLASS_INPUT_COMPACT = 'igx-input-group--compact';
const CSS_CLASS_INPUT_COMFORTABLE = 'igx-input-group--comfortable';
const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required';
const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid';
const defaultDropdownItemHeight = 40;
const defaultDropdownItemMaxHeight = 400;
describe('IgxSimpleCombo', () => {
let fixture: ComponentFixture<any>;
let combo: IgxSimpleComboComponent;
let input: DebugElement;
configureTestSuite();
describe('Unit tests: ', () => {
const data = ['Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Item6', 'Item7'];
const complexData = [
{ country: 'UK', city: 'London' },
{ country: 'France', city: 'Paris' },
{ country: 'Germany', city: 'Berlin' },
{ country: 'Bulgaria', city: 'Sofia' },
{ country: 'Austria', city: 'Vienna' },
{ country: 'Spain', city: 'Madrid' },
{ country: 'Italy', city: 'Rome' }
];
const elementRef = { nativeElement: null };
const mockSelection: {
[key: string]: jasmine.Spy;
} = jasmine.createSpyObj('IgxSelectionAPIService', ['get', 'set', 'add_items', 'select_items']);
const mockCdr = jasmine.createSpyObj('ChangeDetectorRef', ['markForCheck', 'detectChanges']);
const mockComboService = jasmine.createSpyObj('IgxComboAPIService', ['register']);
const mockNgControl = jasmine.createSpyObj('NgControl', ['registerOnChangeCb', 'registerOnTouchedCb']);
const mockInjector = jasmine.createSpyObj('Injector', {
get: mockNgControl
});
mockSelection.get.and.returnValue(new Set([]));
const mockIconService = new IgxIconService(null, null, null, null);
const platformUtil = new PlatformUtil('browser');
it('should properly call dropdown methods on toggle', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.dropdown = dropdown;
dropdown.collapsed = true;
combo.open();
dropdown.collapsed = false;
expect(combo.dropdown.open).toHaveBeenCalledTimes(1);
expect(combo.collapsed).toBe(false);
combo.close();
dropdown.collapsed = true;
expect(combo.dropdown.close).toHaveBeenCalledTimes(1);
expect(combo.collapsed).toBe(true);
combo.toggle();
dropdown.collapsed = false;
expect(combo.dropdown.toggle).toHaveBeenCalledTimes(1);
expect(combo.collapsed).toBe(false);
});
it('should call dropdown toggle with correct overlaySettings', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['toggle']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.dropdown = dropdown;
const defaultSettings = (combo as any)._overlaySettings;
combo.toggle();
expect(combo.dropdown.toggle).toHaveBeenCalledWith(defaultSettings || {});
const newSettings = {
positionStrategy: new ConnectedPositioningStrategy(),
scrollStrategy: new AbsoluteScrollStrategy()
};
combo.overlaySettings = newSettings;
const expectedSettings = Object.assign({}, defaultSettings, newSettings);
combo.toggle();
expect(combo.dropdown.toggle).toHaveBeenCalledWith(expectedSettings);
});
it('should properly get/set displayKey', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.valueKey = 'field';
expect(combo.displayKey).toEqual(combo.valueKey);
combo.displayKey = 'region';
expect(combo.displayKey).toEqual('region');
expect(combo.displayKey === combo.valueKey).toBeFalsy();
});
it('should select items through select method', () => {
const selectionService = new IgxSelectionAPIService();
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']);
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.comboInput = comboInput;
combo.data = complexData;
combo.valueKey = 'country'; // with valueKey
combo.dropdown = dropdown;
spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length);
const selectedItems = [combo.data[0].country];
combo.select('UK');
expect(combo.selection).toEqual(selectedItems);
combo.select('Germany');
selectedItems.push(combo.data[2].country);
selectedItems.shift();
expect(combo.selection).toEqual(selectedItems);
selectedItems.shift();
combo.valueKey = null; // without valueKey
selectedItems.push(combo.data[5]);
combo.select(combo.data[5]);
expect(combo.selection).toEqual(selectedItems);
selectedItems.shift();
selectedItems.push(combo.data[1]);
combo.select(combo.data[1]);
expect(combo.selection).toEqual(selectedItems);
});
it('should emit owner on `opening` and `closing`', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
spyOn(combo.opening, 'emit').and.callThrough();
spyOn(combo.closing, 'emit').and.callThrough();
const mockObj = {};
const mockEvent = new Event('mock');
const inputEvent: IBaseCancelableBrowserEventArgs = {
cancel: false,
owner: mockObj,
event: mockEvent
};
combo.comboInput = {
nativeElement: {
focus: () => { }
}
} as any;
(combo as any).textSelection = { selected: false, trigger: () => { } };
combo.handleOpening(inputEvent);
const expectedCall: IBaseCancelableBrowserEventArgs = { owner: combo, event: inputEvent.event, cancel: inputEvent.cancel };
expect(combo.opening.emit).toHaveBeenCalledWith(expectedCall);
combo.handleClosing(inputEvent);
expect(combo.closing.emit).toHaveBeenCalledWith(expectedCall);
let sub = combo.opening.subscribe((e: IBaseCancelableBrowserEventArgs) => {
e.cancel = true;
});
combo.handleOpening(inputEvent);
expect(inputEvent.cancel).toEqual(true);
sub.unsubscribe();
inputEvent.cancel = false;
sub = combo.closing.subscribe((e: IBaseCancelableBrowserEventArgs) => {
e.cancel = true;
});
combo.handleClosing(inputEvent);
expect(inputEvent.cancel).toEqual(true);
sub.unsubscribe();
});
it('should fire selectionChanging event on item selection', () => {
const selectionService = new IgxSelectionAPIService();
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = data;
combo.dropdown = dropdown;
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
comboInput.value = 'test';
combo.comboInput = comboInput;
spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length);
spyOn(combo.selectionChanging, 'emit');
let oldSelection = undefined;
let newSelection = [combo.data[1]];
combo.select(combo.data[1]);
expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(1);
expect(combo.selectionChanging.emit).toHaveBeenCalledWith({
oldSelection,
newSelection: newSelection[0],
owner: combo,
displayText: newSelection[0].trim(),
cancel: false
});
oldSelection = [...newSelection];
newSelection = [combo.data[0]];
combo.select(combo.data[0]);
expect(combo.selectionChanging.emit).toHaveBeenCalledTimes(2);
expect(combo.selectionChanging.emit).toHaveBeenCalledWith({
oldSelection: oldSelection[0],
newSelection: newSelection[0],
owner: combo,
displayText: newSelection[0].trim(),
cancel: false
});
});
it('should properly emit added and removed values in change event on single value selection', () => {
const selectionService = new IgxSelectionAPIService();
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = complexData;
combo.valueKey = 'country';
combo.dropdown = dropdown;
spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length);
const selectionSpy = spyOn(combo.selectionChanging, 'emit');
const expectedResults: ISimpleComboSelectionChangingEventArgs = {
newSelection: combo.data[0][combo.valueKey],
oldSelection: undefined,
owner: combo,
displayText: `${combo.data[0][combo.displayKey]}`,
cancel: false
};
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
comboInput.value = 'test';
combo.comboInput = comboInput;
combo.select(combo.data[0][combo.valueKey]);
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
Object.assign(expectedResults, {
newSelection: undefined,
oldSelection: combo.data[0][combo.valueKey],
displayText: ''
});
combo.deselect();
expect(selectionSpy).toHaveBeenCalledWith(expectedResults);
});
it('should properly handle selection manipulation through selectionChanging emit', () => {
const selectionService = new IgxSelectionAPIService();
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = data;
combo.dropdown = dropdown;
spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length);
spyOn(combo.selectionChanging, 'emit').and.callFake((event: IComboSelectionChangingEventArgs) => event.newSelection = []);
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
combo.comboInput = comboInput;
// No items are initially selected
expect(combo.selection).toEqual([]);
// Select the first item
combo.select(combo.data[0]);
// selectionChanging fires and overrides the selection to be [];
expect(combo.selection).toEqual([]);
});
it('should not throw error when setting data to null', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
let errorMessage = '';
try {
combo.data = null;
} catch (ex) {
errorMessage = ex.message;
}
expect(errorMessage).toBe('');
expect(combo.data).not.toBeUndefined();
expect(combo.data).not.toBeNull();
expect(combo.data.length).toBe(0);
});
it('should not throw error when setting data to undefined', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
let errorMessage = '';
try {
combo.data = undefined;
} catch (ex) {
errorMessage = ex.message;
}
expect(errorMessage).toBe('');
expect(combo.data).not.toBeUndefined();
expect(combo.data).not.toBeNull();
expect(combo.data.length).toBe(0);
});
it('should properly handleInputChange', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'navigateFirst']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = data;
combo.dropdown = dropdown;
const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough();
spyOn(combo.searchInputUpdate, 'emit');
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
comboInput.value = 'test';
combo.comboInput = comboInput;
combo.handleInputChange();
expect(matchSpy).toHaveBeenCalledTimes(1);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(0);
const args = {
searchText: 'Fake',
owner: combo,
cancel: false
};
combo.handleInputChange('Fake');
expect(matchSpy).toHaveBeenCalledTimes(2);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(1);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledWith(args);
args.searchText = '';
combo.handleInputChange('');
expect(matchSpy).toHaveBeenCalledTimes(3);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(2);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledWith(args);
combo.handleInputChange();
expect(matchSpy).toHaveBeenCalledTimes(4);
expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(2);
});
it('should be able to cancel searchInputUpdate', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = data;
combo.searchInputUpdate.subscribe((e) => {
e.cancel = true;
});
const matchSpy = spyOn<any>(combo, 'checkMatch').and.callThrough();
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'collapsed', 'open', 'navigateFirst']);
combo.dropdown = dropdown;
spyOn(combo.searchInputUpdate, 'emit').and.callThrough();
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value', 'focused']);
comboInput.value = 'test';
combo.comboInput = comboInput;
combo.handleInputChange('Item1');
expect(combo.searchInputUpdate.emit).toHaveBeenCalledTimes(1);
expect(matchSpy).toHaveBeenCalledTimes(1);
});
it('should not open on click if combo is disabled', () => {
combo = new IgxSimpleComboComponent(elementRef, mockCdr, mockSelection as any, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['open', 'close', 'toggle']);
const spyObj = jasmine.createSpyObj('event', ['stopPropagation', 'preventDefault']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value']);
comboInput.value = 'test';
combo.comboInput = comboInput;
combo.ngOnInit();
combo.dropdown = dropdown;
dropdown.collapsed = true;
combo.disabled = true;
combo.onClick(spyObj);
expect(combo.dropdown.collapsed).toBeTruthy();
});
it('should not clear value when combo is disabled', () => {
const selectionService = new IgxSelectionAPIService();
combo = new IgxSimpleComboComponent(elementRef, mockCdr, selectionService, mockComboService,
mockIconService, platformUtil, null, null, mockInjector);
const dropdown = jasmine.createSpyObj('IgxComboDropDownComponent', ['selectItem', 'focusedItem']);
const spyObj = jasmine.createSpyObj('event', ['stopPropagation']);
spyOn(mockIconService, 'addSvgIconFromText').and.returnValue(null);
combo.ngOnInit();
combo.data = data;
combo.dropdown = dropdown;
combo.disabled = true;
const comboInput = jasmine.createSpyObj('IgxInputDirective', ['value', 'focus']);
comboInput.value = 'test';
combo.comboInput = comboInput;
spyOnProperty(combo, 'totalItemCount').and.returnValue(combo.data.length);
const item = combo.data.slice(0, 1);
combo.select(item);
combo.handleClear(spyObj);
expect(combo.value).toEqual(item[0]);
});
});
describe('Initialization and rendering tests: ', () => {
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboSampleComponent,
IgxSimpleComboEmptyComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IgxSimpleComboSampleComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`));
});
it('should initialize the combo component properly', () => {
const toggleButton = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLEBUTTON));
expect(fixture.componentInstance).toBeDefined();
expect(combo).toBeDefined();
expect(combo.collapsed).toBeDefined();
expect(combo.collapsed).toBeTruthy();
expect(input).toBeDefined();
expect(toggleButton).toBeDefined();
expect(combo.placeholder).toBeDefined();
});
it('should initialize input properties properly', () => {
expect(combo.data).toBeDefined();
expect(combo.valueKey).toEqual('field');
expect(combo.displayKey).toEqual('field');
expect(combo.groupKey).toEqual('region');
expect(combo.width).toEqual('400px');
expect(combo.itemsMaxHeight).toEqual(320);
expect(combo.itemHeight).toEqual(32);
expect(combo.placeholder).toEqual('Location');
expect(combo.allowCustomValues).toEqual(false);
expect(combo.cssClass).toEqual(CSS_CLASS_COMBO);
expect(combo.type).toEqual('box');
});
it('should apply all appropriate classes on combo initialization', () => {
const comboWrapper = fixture.nativeElement.querySelector(SIMPLE_COMBO_ELEMENT);
expect(comboWrapper).not.toBeNull();
expect(comboWrapper.classList.contains(CSS_CLASS_COMBO)).toBeTruthy();
expect(comboWrapper.childElementCount).toEqual(2);
const dropDownElement = comboWrapper.children[1];
expect(dropDownElement.classList.contains(CSS_CLASS_COMBO_DROPDOWN)).toBeTruthy();
expect(dropDownElement.classList.contains(CSS_CLASS_DROPDOWN)).toBeTruthy();
expect(dropDownElement.childElementCount).toEqual(1);
const dropDownList = dropDownElement.children[0];
const dropDownScrollList = dropDownElement.children[0].children[0];
expect(dropDownList.classList.contains(CSS_CLASS_DROPDOWNLIST)).toBeTruthy();
expect(dropDownList.classList.contains('igx-toggle--hidden')).toBeTruthy();
expect(dropDownScrollList.childElementCount).toEqual(0);
});
it('should render aria attributes properly', fakeAsync(() => {
expect(input.nativeElement.getAttribute('role')).toEqual('combobox');
expect(input.nativeElement.getAttribute('aria-haspopup')).toEqual('listbox');
expect(input.nativeElement.getAttribute('aria-readonly')).toMatch('false');
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
expect(input.nativeElement.getAttribute('aria-controls')).toEqual(combo.dropdown.listId);
expect(input.nativeElement.getAttribute('aria-labelledby')).toEqual(combo.placeholder);
const dropdown = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_DROPDOWN}`));
expect(dropdown.nativeElement.getAttribute('ng-reflect-labelled-by')).toEqual(combo.placeholder);
combo.open();
tick();
fixture.detectChanges();
const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id);
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', list);
tick();
fixture.detectChanges();
expect(list.nativeElement.getAttribute('aria-activedescendant')).toEqual(combo.dropdown.focusedItem.id);
}));
it('should render aria-expanded attribute properly', fakeAsync(() => {
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
combo.open();
tick();
fixture.detectChanges();
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('true');
combo.close();
tick();
fixture.detectChanges();
expect(input.nativeElement.getAttribute('aria-expanded')).toMatch('false');
}));
it('should render placeholder values for inputs properly', () => {
combo.toggle();
fixture.detectChanges();
expect(combo.collapsed).toBeFalsy();
expect(combo.placeholder).toEqual('Location');
expect(combo.comboInput.nativeElement.placeholder).toEqual('Location');
combo.placeholder = 'States';
fixture.detectChanges();
expect(combo.placeholder).toEqual('States');
expect(combo.comboInput.nativeElement.placeholder).toEqual('States');
});
it('should render dropdown list and item height properly', fakeAsync(() => {
// NOTE: Minimum itemHeight is 2 rem, per Material Design Guidelines (for mobile only)
let itemHeight = defaultDropdownItemHeight;
let itemMaxHeight = defaultDropdownItemMaxHeight;
combo.displayDensity = DisplayDensity.comfortable;
fixture.detectChanges();
combo.toggle();
tick();
fixture.detectChanges();
const dropdownItems = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
const verifyDropdownItemHeight = () => {
expect(combo.itemHeight).toEqual(itemHeight);
expect(dropdownItems[0].nativeElement.clientHeight).toEqual(itemHeight);
expect(combo.itemsMaxHeight).toEqual(itemMaxHeight);
expect(dropdownList.nativeElement.clientHeight).toEqual(itemMaxHeight);
};
verifyDropdownItemHeight();
itemHeight = 48;
itemMaxHeight = 480;
combo.itemHeight = itemHeight;
tick();
fixture.detectChanges();
verifyDropdownItemHeight();
itemMaxHeight = 438;
combo.itemsMaxHeight = 438;
tick();
fixture.detectChanges();
verifyDropdownItemHeight();
itemMaxHeight = 1171;
combo.itemsMaxHeight = 1171;
tick();
fixture.detectChanges();
verifyDropdownItemHeight();
itemHeight = 83;
combo.itemHeight = 83;
tick();
fixture.detectChanges();
verifyDropdownItemHeight();
}));
it('should render focused items properly', () => {
const dropdown = combo.dropdown;
combo.toggle();
fixture.detectChanges();
dropdown.navigateItem(2); // Component is virtualized, so this will focus the ACTUAL 3rd item
fixture.detectChanges();
const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement;
const dropdownItems = dropdownList.querySelectorAll(`.${CSS_CLASS_DROPDOWNLISTITEM}`);
const focusedItem_1 = dropdownItems[1];
expect(focusedItem_1.classList.contains(CSS_CLASS_FOCUSED)).toBeTruthy();
// Change focus
dropdown.navigateItem(6);
fixture.detectChanges();
const focusedItem_2 = dropdownItems[5];
expect(focusedItem_2.classList.contains(CSS_CLASS_FOCUSED)).toBeTruthy();
expect(focusedItem_1.classList.contains(CSS_CLASS_FOCUSED)).toBeFalsy();
});
it('should properly initialize templates', () => {
expect(combo).toBeDefined();
expect(combo.footerTemplate).toBeDefined();
expect(combo.headerTemplate).toBeDefined();
expect(combo.itemTemplate).toBeDefined();
expect(combo.addItemTemplate).toBeUndefined();
expect(combo.headerItemTemplate).toBeUndefined();
});
it('should properly render header and footer templates', () => {
let headerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`));
let footerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`));
expect(headerElement).toBeNull();
expect(footerElement).toBeNull();
combo.toggle();
fixture.detectChanges();
expect(combo.headerTemplate).toBeDefined();
expect(combo.footerTemplate).toBeDefined();
const dropdownList: HTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement;
headerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`));
footerElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`));
expect(headerElement).not.toBeNull();
const headerHTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_HEADER}`)).nativeElement;
expect(headerHTMLElement.parentNode).toEqual(dropdownList);
expect(headerHTMLElement.textContent).toEqual('This is a header');
expect(footerElement).not.toBeNull();
const footerHTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_FOOTER}`)).nativeElement;
expect(footerHTMLElement.parentNode).toEqual(dropdownList);
expect(footerHTMLElement.textContent).toEqual('This is a footer');
});
it('should initialize the component with empty data and bindings', () => {
fixture = TestBed.createComponent(IgxSimpleComboEmptyComponent);
expect(() => {
fixture.detectChanges();
}).not.toThrow();
expect(fixture.componentInstance.combo).toBeDefined();
});
});
describe('Binding tests: ', () => {
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboSampleComponent,
IgxComboInContainerTestComponent,
IgxComboRemoteDataComponent,
ComboModelBindingComponent
]
}).compileComponents();
}));
it('should bind combo data to array of primitive data', () => {
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
fixture.detectChanges();
const data = [...fixture.componentInstance.citiesData];
combo = fixture.componentInstance.combo;
const comboData = combo.data;
expect(comboData).toEqual(data);
});
it('should remove undefined from array of primitive data', () => {
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
combo.data = ['New York', 'Sofia', undefined, 'Istanbul','Paris'];
expect(combo.data).toEqual(['New York', 'Sofia', 'Istanbul','Paris']);
});
it('should bind combo data to array of objects', () => {
fixture = TestBed.createComponent(IgxSimpleComboSampleComponent);
fixture.detectChanges();
const data = [...fixture.componentInstance.items];
combo = fixture.componentInstance.combo;
const comboData = combo.data;
expect(comboData).toEqual(data);
});
it('should render empty template when combo data source is not set', () => {
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
combo.data = [];
fixture.detectChanges();
combo.toggle();
fixture.detectChanges();
const dropdownList = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLIST_SCROLL}`)).nativeElement;
const dropdownItemsContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`)).nativeElement;
const dropDownContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement;
const listItems = dropDownContainer.querySelectorAll(`.${CSS_CLASS_DROPDOWNLISTITEM}`);
expect(listItems.length).toEqual(0);
// Expect no items to be rendered in the virtual container
expect(dropdownItemsContainer.children[0].childElementCount).toEqual(0);
// Expect the list child (NOT COMBO ITEM) to be a container with "The list is empty";
const dropdownItem = dropdownList.lastElementChild as HTMLElement;
expect(dropdownItem.firstElementChild.textContent).toEqual('The list is empty');
});
it('should bind combo data properly when changing data source runtime', () => {
const newData = ['Item 1', 'Item 2'];
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
fixture.detectChanges();
const data = [...fixture.componentInstance.citiesData];
combo = fixture.componentInstance.combo;
expect(combo.data).toEqual(data);
combo.data = newData;
fixture.detectChanges();
expect(combo.data).toEqual(newData);
});
it('should properly bind to object value w/ valueKey', fakeAsync(() => {
fixture = TestBed.createComponent(ComboModelBindingComponent);
fixture.detectChanges();
tick();
const component = fixture.componentInstance;
combo = fixture.componentInstance.combo;
combo.valueKey = 'id';
component.selectedItem = 1;
fixture.detectChanges();
tick();
expect(combo.selection).toEqual([combo.data[1][combo.valueKey]]);
combo.select(combo.data[4][combo.valueKey]);
fixture.detectChanges();
expect(component.selectedItem).toEqual(4);
}));
it('should properly bind to object value w/o valueKey', fakeAsync(() => {
fixture = TestBed.createComponent(ComboModelBindingComponent);
fixture.detectChanges();
tick();
const component = fixture.componentInstance;
combo = fixture.componentInstance.combo;
component.selectedItem = component.items[0];
fixture.detectChanges();
tick();
expect(combo.selection).toEqual([combo.data[0]]);
combo.select(combo.data[4]);
fixture.detectChanges();
expect(component.selectedItem).toEqual(combo.data[4]);
}));
it('should clear selection w/o valueKey', fakeAsync(() => {
fixture = TestBed.createComponent(ComboModelBindingComponent);
fixture.detectChanges();
const component = fixture.componentInstance;
combo = fixture.componentInstance.combo;
component.items = ['One', 'Two', 'Three', 'Four', 'Five'];
combo.select('Three');
fixture.detectChanges();
expect(combo.selection).toEqual(['Three']);
combo.handleClear(new MouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toEqual('');
}));
it('should properly bind to values w/o valueKey', fakeAsync(() => {
fixture = TestBed.createComponent(ComboModelBindingComponent);
fixture.detectChanges();
const component = fixture.componentInstance;
combo = fixture.componentInstance.combo;
component.items = ['One', 'Two', 'Three', 'Four', 'Five'];
component.selectedItem = 'One';
fixture.detectChanges();
tick();
expect(combo.selection).toEqual([component.selectedItem]);
combo.select('Three');
fixture.detectChanges();
expect(fixture.componentInstance.selectedItem).toEqual('Three');
}));
it('should bind combo data to remote data and clear selection properly', (async () => {
fixture = TestBed.createComponent(IgxComboRemoteDataComponent);
fixture.detectChanges();
combo = fixture.componentInstance.instance;
expect(combo).toBeDefined();
expect(combo.valueKey).toBeDefined();
let selectedItem = combo.data[1];
const spyObj = jasmine.createSpyObj('event', ['stopPropagation']);
combo.toggle();
combo.select(combo.data[1][combo.valueKey]);
expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`);
expect(combo.selection).toEqual([selectedItem[combo.valueKey]]);
// Clear items while they are in view
combo.handleClear(spyObj);
expect(combo.selection).toEqual([]);
expect(combo.value).toBe('');
selectedItem = combo.data[2];
combo.select(combo.data[2][combo.valueKey]);
expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`);
// Scroll selected items out of view
combo.virtualScrollContainer.scrollTo(40);
await wait();
fixture.detectChanges();
combo.handleClear(spyObj);
expect(combo.selection).toEqual([]);
expect(combo.value).toBe('');
combo.select(combo.data[7][combo.valueKey]);
expect(combo.value).toBe(combo.data[7][combo.displayKey]);
}));
});
describe('Keyboard navigation and interactions', () => {
let dropdown: IgxComboDropDownComponent;
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboSampleComponent,
IgxComboInContainerTestComponent,
IgxSimpleComboIconTemplatesComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IgxSimpleComboSampleComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`));
dropdown = combo.dropdown;
});
it('should toggle dropdown list with arrow down/up keys', fakeAsync(() => {
spyOn(combo, 'open').and.callThrough();
spyOn(combo, 'close').and.callThrough();
combo.onArrowDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowDown'));
tick();
fixture.detectChanges();
expect(combo.open).toHaveBeenCalledTimes(1);
combo.onArrowDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowDown', true));
tick();
fixture.detectChanges();
expect(combo.collapsed).toEqual(false);
expect(combo.open).toHaveBeenCalledTimes(1);
combo.handleKeyDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowUp'));
tick();
fixture.detectChanges();
expect(combo.close).toHaveBeenCalledTimes(1);
combo.handleKeyDown(UIInteractions.getKeyboardEvent('keydown', 'ArrowUp', true));
fixture.detectChanges();
tick();
expect(combo.close).toHaveBeenCalledTimes(2);
}));
it('should not close the dropdown on ArrowUp key if the active item is the first one in the list', fakeAsync(() => {
combo.open();
tick();
fixture.detectChanges();
const list = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', list);
tick();
fixture.detectChanges();
expect(combo.collapsed).toEqual(false);
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', list);
tick();
fixture.detectChanges();
expect(combo.collapsed).toEqual(false);
}));
it('should select an item from the dropdown list with the Space key without closing it', () => {
combo.open();
fixture.detectChanges();
const dropdownContent = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
expect(dropdownContent).not.toBeFalsy();
combo.handleKeyUp(UIInteractions.getKeyboardEvent('keyup', 'ArrowDown'));
fixture.detectChanges();
expect(dropdown.focusedItem).toBeTruthy();
expect(dropdown.focusedItem.index).toEqual(1);
spyOn(combo.closed, 'emit').and.callThrough();
UIInteractions.triggerEventHandlerKeyDown('Space', dropdownContent);
fixture.detectChanges();
expect(combo.closed.emit).not.toHaveBeenCalled();
expect(combo.selection.length).toEqual(1);
});
it('should clear the selection on tab/blur if the search text does not match any value', () => {
// allowCustomValues does not matter
combo.select(combo.data[2][combo.valueKey]);
fixture.detectChanges();
expect(combo.selection.length).toBe(1);
expect(input.nativeElement.value).toEqual('Massachusetts');
UIInteractions.simulateTyping('L', input, 13, 14);
const event = {
target: input.nativeElement,
key: 'L',
stopPropagation: () => { },
stopImmediatePropagation: () => { },
preventDefault: () => { }
};
combo.onArrowDown(new KeyboardEvent('keydown', { ...event }));
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(input.nativeElement.value.length).toEqual(0);
expect(combo.selection.length).toEqual(0);
});
it('should not clear selection on tab/blur after filtering and selecting a value', () => {
UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();
UIInteractions.triggerKeyDownEvtUponElem('Enter', input.nativeElement);
expect(combo.selection.length).toEqual(1);
expect(combo.value).toEqual('Wisconsin');
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.selection.length).toEqual(1);
expect(combo.value).toEqual('Wisconsin');
});
it('should display the AddItem button when allowCustomValues is true and there is a partial match', fakeAsync(() => {
fixture.componentInstance.allowCustomValues = true;
fixture.detectChanges();
combo.open();
fixture.detectChanges();
UIInteractions.setInputElementValue(input.nativeElement, 'Massachuset');
fixture.detectChanges();
expect(combo.isAddButtonVisible()).toBeTruthy();
let addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`));
expect(addItemButton).not.toBeNull();
// after adding the item, the addItem button should not be displayed (there is a full match)
addItemButton.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.collapsed).toBeFalsy();
expect(combo.data.findIndex(i => i.field === 'Massachuset')).not.toBe(-1);
addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`));
expect(combo.isAddButtonVisible()).toBeFalsy();
expect(addItemButton).toBeNull();
}));
it('should move the focus to the AddItem button with ArrowDown when allowCustomValues is true', fakeAsync(() => {
fixture.componentInstance.allowCustomValues = true;
fixture.detectChanges();
UIInteractions.setInputElementValue(input.nativeElement, 'MassachusettsL');
fixture.detectChanges();
combo.open();
fixture.detectChanges();
const addItemButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_ADDBUTTON}`));
expect(addItemButton).toBeDefined();
input.nativeElement.focus();
fixture.detectChanges();
combo.onArrowDown(new Event('keydown'));
fixture.detectChanges();
expect(document.activeElement).toEqual(addItemButton.nativeElement);
}));
it('should close when an item is clicked on', () => {
spyOn(combo, 'close').and.callThrough();
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
expect(item1).toBeDefined();
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.close).toHaveBeenCalledTimes(1);
});
it('should retain selection after blurring', () => {
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
expect(item1).toBeDefined();
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.selection.length).toBe(1);
});
it('should scroll to top when opened and there is no selection', () => {
combo.deselect();
fixture.detectChanges();
spyOn(combo, 'onClick').and.callThrough();
spyOn((combo as any).virtDir, 'scrollTo').and.callThrough();
const toggleButton = fixture.debugElement.query(By.directive(IgxIconComponent));
expect(toggleButton).toBeDefined();
toggleButton.nativeElement.click();
fixture.detectChanges();
expect(combo.collapsed).toBeFalsy();
expect(combo.onClick).toHaveBeenCalledTimes(1);
expect((combo as any).virtDir.scrollTo).toHaveBeenCalledWith(0);
});
it('should close the dropdown with Alt + ArrowUp', fakeAsync(() => {
combo.open();
fixture.detectChanges();
spyOn(combo, 'close').and.callThrough();
UIInteractions.triggerEventHandlerKeyDown('ArrowDown', input);
tick();
fixture.detectChanges();
expect(document.activeElement).toHaveClass('igx-combo__content');
UIInteractions.triggerEventHandlerKeyDown('ArrowUp', input, true, true);
fixture.detectChanges();
expect(combo.close).toHaveBeenCalledTimes(1);
}));
it('should select the first filtered item with Enter', () => {
UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();
UIInteractions.triggerKeyDownEvtUponElem('Enter', input.nativeElement);
expect(input.nativeElement.value).toEqual('Wisconsin');
});
it('should navigate to next filtered item on ArrowDown', () => {
combo.allowCustomValues = true;
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.simulateTyping('con', input);
expect(combo.comboInput.value).toEqual('con');
fixture.detectChanges();
// filtered data -> Wisconsin, Connecticut
UIInteractions.triggerKeyDownEvtUponElem('ArrowDown', input.nativeElement);
UIInteractions.triggerKeyDownEvtUponElem('Enter', input.nativeElement);
expect(input.nativeElement.value).toEqual('Connecticut');
});
it('should clear selection when all text in input is removed by Backspace and Delete', () => {
combo.select('Wisconsin');
fixture.detectChanges();
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Backspace', input);
fixture.detectChanges();
expect(combo.selection.length).toEqual(0);
input.triggerEventHandler('blur', {});
fixture.detectChanges();
combo.select('Wisconsin');
fixture.detectChanges();
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Backspace', input);
fixture.detectChanges();
expect(combo.selection.length).toEqual(0);
});
it('should display all list items when clearing the input by Space', () => {
combo.select('Wisconsin');
fixture.detectChanges();
expect(combo.selection.length).toEqual(1);
UIInteractions.simulateTyping(' ', input, 0, 9);
fixture.detectChanges();
expect(combo.selection.length).toEqual(0);
expect(combo.filteredData.length).toEqual(combo.data.length);
});
it('should close the dropdown (if opened) when tabbing outside of the input', () => {
combo.open();
fixture.detectChanges();
spyOn(combo, 'close').and.callThrough();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.close).toHaveBeenCalledTimes(1);
});
it('should clear the input on blur with a partial match', () => {
spyOn(combo as any, 'clearSelection').and.callThrough();
spyOn(combo.dropdown.closing, 'emit').and.callThrough();
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.simulateTyping('new', input);
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect((combo as any).clearSelection).toHaveBeenCalledOnceWith(true);
expect(combo.dropdown.closing.emit).toHaveBeenCalledTimes(1);
expect(combo.value).toBeFalsy();
});
it('should not clear the selection and input on blur with a match', () => {
fixture = TestBed.createComponent(IgxComboInContainerTestComponent);
combo = fixture.componentInstance.combo;
fixture.detectChanges();
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`));
combo.data = ['Apples', 'Apple'];
combo.select(combo.data[1]);
fixture.detectChanges();
expect(combo.selection.length).toBe(1);
expect(combo.selection[0]).toEqual('Apple');
combo.open();
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.value).toEqual('Apple');
expect(combo.selection.length).toEqual(1);
});
it('should clear input on blur when dropdown is collapsed with no match', () => {
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.simulateTyping('new', input);
const toggleButton = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLEBUTTON));
toggleButton.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.value).toEqual('');
expect(combo.selection.length).toEqual(0);
});
it('should open the combo when input is focused', () => {
spyOn(combo, 'open').and.callThrough();
spyOn(combo, 'close').and.callThrough();
input.triggerEventHandler('focus', {});
fixture.detectChanges();
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.open).toHaveBeenCalledTimes(1);
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.open).toHaveBeenCalledTimes(1);
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.close).toHaveBeenCalledTimes(1);
input.triggerEventHandler('focus', {});
fixture.detectChanges();
input.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.open).toHaveBeenCalledTimes(1);
});
it('should empty any invalid item values', () => {
combo.valueKey = 'key';
combo.displayKey = 'value';
combo.data = [
{ key: 1, value: null },
{ key: 2, value: 'val2' },
{ key: 3, value: '' },
{ key: 4, value: undefined },
];
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
expect(item1).toBeDefined();
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
combo.open();
fixture.detectChanges();
const item2 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[1];
expect(item2).toBeDefined();
item2.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('val2');
combo.open();
fixture.detectChanges();
const item3 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
expect(item3).toBeDefined();
item3.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
combo.open();
fixture.detectChanges();
const item5 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
expect(item5).toBeDefined();
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
});
it('should select falsy values except "undefined"', () => {
combo.valueKey = 'value';
combo.displayKey = 'field';
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: '', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[0];
expect(item1).toBeDefined();
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('0');
expect(combo.selection).toEqual([ 0 ]);
combo.open();
fixture.detectChanges();
const item2 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[1];
expect(item2).toBeDefined();
item2.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('false');
expect(combo.selection).toEqual([ false ]);
combo.open();
fixture.detectChanges();
const item3 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
expect(item3).toBeDefined();
item3.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([ '' ]);
combo.open();
fixture.detectChanges();
const item4 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
expect(item4).toBeDefined();
item4.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('null');
expect(combo.selection).toEqual([ null ]);
combo.open();
fixture.detectChanges();
const item5 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[4];
expect(item5).toBeDefined();
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('NaN');
expect(combo.selection).toEqual([ NaN ]);
// should not select "undefined"
// combo.value & combo.selection equal the values from the previous selection
combo.open();
fixture.detectChanges();
const item6 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[5];
expect(item6).toBeDefined();
item6.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('NaN');
expect(combo.selection).toEqual([ NaN ]);
});
it('should select falsy values except "undefined" with "writeValue" method', () => {
combo.valueKey = 'value';
combo.displayKey = 'field';
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: 'empty', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
combo.writeValue(0);
expect(combo.selection).toEqual([0]);
expect(combo.value).toBe('0');
combo.writeValue(false);
expect(combo.selection).toEqual([false]);
expect(combo.value).toBe('false');
combo.writeValue('');
expect(combo.selection).toEqual(['']);
expect(combo.value).toBe('empty');
combo.writeValue(null);
expect(combo.selection).toEqual([null]);
expect(combo.value).toBe('null');
combo.writeValue(NaN);
expect(combo.selection).toEqual([NaN]);
expect(combo.value).toBe('NaN');
// should not select undefined
combo.writeValue(undefined);
expect(combo.selection).toEqual([]);
expect(combo.value).toBe('');
});
it('should toggle dropdown list on clicking a templated toggle icon', fakeAsync(() => {
fixture = TestBed.createComponent(IgxSimpleComboIconTemplatesComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
const toggleIcon = fixture.debugElement.query(By.css(`.${CSS_CLASS_TOGGLEBUTTON}`));
expect(toggleIcon).toBeDefined();
expect(toggleIcon.nativeElement.textContent).toBe('search');
expect(combo.collapsed).toBeTruthy();
toggleIcon.nativeElement.click();
tick();
fixture.detectChanges();
expect(combo.collapsed).toBeFalsy();
toggleIcon.nativeElement.click();
tick();
fixture.detectChanges();
expect(combo.collapsed).toBeTruthy();
}));
it('should clear the selection when typing in the input', () => {
combo.select('Wisconsin');
fixture.detectChanges();
expect(combo.selection.length).toEqual(1);
let clearButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_CLEARBUTTON}`));
expect(clearButton).not.toBeNull();
input.triggerEventHandler('focus', {});
fixture.detectChanges();
UIInteractions.simulateTyping('L', input, 9, 10);
fixture.detectChanges();
expect(combo.selection.length).toEqual(0);
//should hide the clear button immediately when clearing the selection by typing
clearButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_CLEARBUTTON}`));
expect(clearButton).toBeNull();
});
it('should open the combo to the top when there is no space to open to the bottom', fakeAsync(() => {
fixture = TestBed.createComponent(IgxBottomPositionSimpleComboComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
const newSettings = {
positionStrategy: new AutoPositionStrategy(),
scrollStrategy: new AbsoluteScrollStrategy()
};
combo.overlaySettings = newSettings;
fixture.detectChanges();
combo.open();
fixture.detectChanges();
expect(combo.collapsed).toEqual(false);
expect(combo.overlaySettings.positionStrategy.settings.verticalDirection).toBe(-1);
combo.select('Connecticut');
fixture.detectChanges();
expect(combo.selection.length).toBe(1);
expect(combo.selection[0]).toEqual('Connecticut');
fixture.detectChanges();
combo.dropdown.close();
tick();
fixture.detectChanges();
expect(combo.collapsed).toEqual(true);
combo.open();
fixture.detectChanges();
expect(combo.collapsed).toEqual(false);
expect(combo.overlaySettings.positionStrategy.settings.verticalDirection).toBe(-1);
combo.dropdown.close();
tick();
fixture.detectChanges();
expect(combo.collapsed).toEqual(true);
}));
it('should not open when clearing the selection from the clear icon when the combo is collapsed', fakeAsync(() => {
fixture = TestBed.createComponent(IgxSimpleComboSampleComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
combo.select('Connecticut');
fixture.detectChanges();
combo.handleClear(new MouseEvent('click'));
fixture.detectChanges();
expect(combo.collapsed).toEqual(true);
}));
it('should select values that have spaces as prefixes/suffixes', fakeAsync(() => {
fixture.detectChanges();
dropdown.toggle();
fixture.detectChanges();
UIInteractions.simulateTyping('Ohio ', input);
fixture.detectChanges();
UIInteractions.triggerKeyDownEvtUponElem('Enter', input.nativeElement);
fixture.detectChanges();
combo.toggle();
tick();
fixture.detectChanges();
combo.onBlur();
tick();
fixture.detectChanges();
expect(combo.value).toBe('Ohio ');
}));
});
describe('Display density', () => {
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboSampleComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IgxSimpleComboSampleComponent);
fixture.detectChanges();
combo = fixture.componentInstance.combo;
});
it('Should be able to set Display Density as input', () => {
expect(combo.displayDensity).toEqual(DisplayDensity.cosy);
fixture.componentInstance.density = DisplayDensity.compact;
fixture.detectChanges();
expect(combo.displayDensity).toEqual(DisplayDensity.compact);
fixture.componentInstance.density = DisplayDensity.comfortable;
fixture.detectChanges();
expect(combo.displayDensity).toEqual(DisplayDensity.comfortable);
});
it('should apply correct styles to items and input when Display Density is set', () => {
combo.toggle();
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length);
expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COSY).length);
expect(document.getElementsByClassName(CSS_CLASS_INPUT_COSY).length).toBe(1);
fixture.componentInstance.density = DisplayDensity.compact;
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length);
expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_COMPACT).length);
expect(document.getElementsByClassName(CSS_CLASS_INPUT_COMPACT).length).toBe(1);
fixture.componentInstance.density = DisplayDensity.comfortable;
fixture.detectChanges();
expect(combo.dropdown.items.length).toEqual(document.getElementsByClassName(CSS_CLASS_ITEM).length);
expect(combo.dropdown.headers.length).toEqual(document.getElementsByClassName(CSS_CLASS_HEADER_ITEM).length);
expect(document.getElementsByClassName(CSS_CLASS_INPUT_COMFORTABLE).length).toBe(1);
expect(document.getElementsByClassName(CSS_CLASS_ITEM_COMPACT).length).toEqual(0);
expect(document.getElementsByClassName(CSS_CLASS_ITEM_COSY).length).toEqual(0);
});
it('should scale items container depending on displayDensity (itemHeight * 10)', () => {
combo.toggle();
fixture.detectChanges();
expect(combo.itemsMaxHeight).toEqual(320);
fixture.componentInstance.density = DisplayDensity.compact;
fixture.detectChanges();
expect(combo.itemsMaxHeight).toEqual(280);
fixture.componentInstance.density = DisplayDensity.comfortable;
fixture.detectChanges();
expect(combo.itemsMaxHeight).toEqual(400);
});
});
describe('Form control tests: ', () => {
describe('Template form tests: ', () => {
let inputGroupRequired: DebugElement;
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboInTemplatedFormComponent
]
}).compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(IgxSimpleComboInTemplatedFormComponent);
fixture.detectChanges();
combo = fixture.componentInstance.testCombo;
input = fixture.debugElement.query(By.css(`${CSS_CLASS_INPUTGROUP} input`));
inputGroupRequired = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUTGROUP_REQUIRED}`));
}));
it('should properly initialize when used in a template form control', () => {
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
expect(inputGroupRequired).toBeDefined();
combo.onBlur();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
input.triggerEventHandler('focus', {});
combo.select('Wisconsin');
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.VALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.VALID);
const clearButton = fixture.debugElement.query(By.css(`.${CSS_CLASS_CLEARBUTTON}`));
clearButton.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
it('should set validity to initial when the form is reset', fakeAsync(() => {
combo.onBlur();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
fixture.componentInstance.form.resetForm();
tick();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
}));
it('should not select null, undefined and empty string in a template form with required', () => {
// array of objects
combo.valueKey = 'value';
combo.displayKey = 'field';
combo.groupKey = undefined;
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: '', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
// empty string
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// null
combo.open();
fixture.detectChanges();
const item2 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
item2.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// undefined
combo.open();
fixture.detectChanges();
const item3 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[5];
item3.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// primitive data - undefined is not displayed in the dropdown
combo.valueKey = undefined;
combo.displayKey = undefined;
combo.groupKey = undefined;
combo.data = [ 0, false, '', null, NaN, undefined];
fixture.componentInstance.form.resetForm();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
// empty string
combo.open();
fixture.detectChanges();
const item4 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
item4.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// null
combo.open();
fixture.detectChanges();
const item5 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
it('should not select null, undefined and empty string with "writeValue" method in a template form with required', () => {
// array of objects
combo.valueKey = 'value';
combo.displayKey = 'field';
combo.groupKey = undefined;
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: '', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
combo.onBlur();
fixture.detectChanges();
combo.writeValue(null);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue('');
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue(undefined);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// primitive data - undefined is not displayed in the dropdown
combo.valueKey = undefined;
combo.displayKey = undefined;
combo.groupKey = undefined;
combo.data = [ 0, false, '', null, NaN, undefined];
fixture.componentInstance.form.resetForm();
fixture.detectChanges();
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
combo.onBlur();
fixture.detectChanges();
combo.writeValue(null);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue('');
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue(undefined);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
});
describe('Reactive form tests: ', () => {
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxSimpleComboInReactiveFormComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IgxSimpleComboInReactiveFormComponent);
fixture.detectChanges();
combo = fixture.componentInstance.reactiveCombo;
});
it('should not select null, undefined and empty string in a reactive form with required', () => {
// array of objects
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: '', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
// empty string
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// null
combo.open();
fixture.detectChanges();
const item2 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
item2.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// undefined
combo.open();
fixture.detectChanges();
const item3 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[5];
item3.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// primitive data - undefined is not displayed in the dropdown
combo.valueKey = undefined;
combo.displayKey = undefined;
combo.data = [ 0, false, '', null, NaN, undefined];
fixture.componentInstance.reactiveForm.resetForm();
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
// empty string
combo.open();
fixture.detectChanges();
const item4 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[2];
item4.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// null
combo.open();
fixture.detectChanges();
const item5 = fixture.debugElement.queryAll(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`))[3];
item5.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
it('should not select null, undefined and empty string with "writeValue" method in a reactive form with required', () => {
// array of objects
combo.data = [
{ field: '0', value: 0 },
{ field: 'false', value: false },
{ field: '', value: '' },
{ field: 'null', value: null },
{ field: 'NaN', value: NaN },
{ field: 'undefined', value: undefined },
];
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
combo.onBlur();
fixture.detectChanges();
combo.writeValue(null);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue('');
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue(undefined);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
// primitive data - undefined is not displayed in the dropdown
combo.valueKey = undefined;
combo.displayKey = undefined;
combo.data = [ 0, false, '', null, NaN, undefined];
fixture.componentInstance.reactiveForm.resetForm();
fixture.detectChanges();
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INITIAL);
expect(combo.comboInput.valid).toEqual(IgxInputState.INITIAL);
combo.onBlur();
fixture.detectChanges();
combo.writeValue(null);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue('');
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
combo.writeValue(undefined);
expect(combo.value).toBe('');
expect(combo.selection).toEqual([]);
expect(combo.valid).toEqual(IgxComboState.INVALID);
expect(combo.comboInput.valid).toEqual(IgxInputState.INVALID);
});
it('Should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => {
const form = (fixture.componentInstance as IgxSimpleComboInReactiveFormComponent).comboForm;
// the form control has validators
form.markAllAsTouched();
form.get('comboValue').setErrors({ error: true });
fixture.detectChanges();
expect((combo as any).comboInput.valid).toBe(IgxInputState.INVALID);
expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true);
expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(true);
// remove the validators and set errors
form.get('comboValue').clearValidators();
form.markAsUntouched();
fixture.detectChanges();
form.markAllAsTouched();
form.get('comboValue').setErrors({ error: true });
fixture.detectChanges();
// no validator, but there is a set error
expect((combo as any).comboInput.valid).toBe(IgxInputState.INVALID);
expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true);
expect((combo as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false);
}));
});
});
describe('Selection tests: ', () => {
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
ReactiveFormsModule,
FormsModule,
IgxComboRemoteDataComponent,
IgxSimpleComboBindingDataAfterInitComponent
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IgxComboRemoteDataComponent);
fixture.detectChanges();
combo = fixture.componentInstance.instance;
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`));
});
it('should prevent registration of remote entries when selectionChanging is cancelled', () => {
spyOn(combo.selectionChanging, 'emit').and.callFake((event: IComboSelectionChangingEventArgs) => event.cancel = true);
combo.open();
fixture.detectChanges();
const item1 = fixture.debugElement.query(By.css(`.${CSS_CLASS_DROPDOWNLISTITEM}`));
expect(item1).toBeDefined();
item1.triggerEventHandler('click', UIInteractions.getMouseEvent('click'));
fixture.detectChanges();
expect(combo.selection.length).toEqual(0);
expect((combo as any)._remoteSelection[0]).toBeUndefined();
});
it('should add predefined selection to the input when data is bound after initialization', fakeAsync(() => {
fixture = TestBed.createComponent(IgxSimpleComboBindingDataAfterInitComponent);
fixture.detectChanges();
combo = fixture.componentInstance.instance;
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_COMBO_INPUTGROUP}`));
tick(16);
fixture.detectChanges();
const expectedOutput = 'One';
expect(input.nativeElement.value).toEqual(expectedOutput);
}));
it('should not clear selection when bound to remote data and item is out of view', (async () => {
expect(combo.valueKey).toBeDefined();
expect(combo.selection.length).toEqual(0);
const selectedItem = combo.data[1];
combo.toggle();
combo.select(combo.data[1][combo.valueKey]);
// Scroll selected item out of view
combo.virtualScrollContainer.scrollTo(40);
await wait();
fixture.detectChanges();
input.nativeElement.focus();
fixture.detectChanges();
UIInteractions.triggerEventHandlerKeyDown('Tab', input);
fixture.detectChanges();
expect(combo.selection.length).toEqual(1);
expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`);
expect(combo.selection).toEqual([selectedItem[combo.valueKey]]);
}));
it('should set combo.value to empty string when bound to remote data and selected item\'s data is not present', (async () => {
expect(combo.valueKey).toBeDefined();
expect(combo.valueKey).toEqual('id');
expect(combo.selection.length).toEqual(0);
// current combo data - id: 0 - 9
// select item that is not present in the data source yet
combo.select(15);
expect(combo.selection.length).toEqual(1);
expect(combo.value).toEqual('');
combo.toggle();
// scroll to selected item
combo.virtualScrollContainer.scrollTo(15);
await wait(30);
fixture.detectChanges();
const selectedItem = combo.data[combo.data.length - 1];
expect(combo.value).toEqual(`${selectedItem[combo.displayKey]}`);
}));
});
});
@Component({
template: `
<igx-simple-combo #combo [placeholder]="'Location'" [data]='items' [displayDensity]="density"
[valueKey]="'field'" [groupKey]="'region'" [width]="'400px'" (selectionChanging)="selectionChanging($event)"
[allowCustomValues]="allowCustomValues">
<ng-template igxComboItem let-display let-key="valueKey">
<div class="state-card--simple">
<span class="small-red-circle"></span>
<div class="display-value--main">State: {{display[key]}}</div>
<div class="display-value--sub">Region: {{display.region}}</div>
</div>
</ng-template>
<ng-template igxComboHeader>
<div class="header-class">This is a header</div>
</ng-template>
<ng-template igxComboFooter>
<div class="footer-class">This is a footer</div>
</ng-template>
</igx-simple-combo>
`,
standalone: true,
imports: [IgxSimpleComboComponent, IgxComboItemDirective, IgxComboHeaderDirective, IgxComboFooterDirective]
})
class IgxSimpleComboSampleComponent {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public density: DisplayDensity = DisplayDensity.cosy;
public allowCustomValues = false;
public items = [];
public initData = [];
constructor() {
const division = {
'New England 01': ['Connecticut', 'Maine', 'Massachusetts'],
'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'],
'Mid-Atlantic': ['New Jersey', 'New York', 'Pennsylvania'],
'East North Central 02': ['Michigan', 'Ohio ', 'Wisconsin'],
'East North Central 01': ['Illinois', 'Indiana'],
'West North Central 01': ['Missouri', 'Nebraska', 'North Dakota', 'South Dakota'],
'West North Central 02': ['Iowa', 'Kansas', 'Minnesota'],
'South Atlantic 01': ['Delaware', 'Florida', 'Georgia', 'Maryland'],
'South Atlantic 02': ['North Carolina', 'South Carolina', 'Virginia'],
'South Atlantic 03': ['District of Columbia', 'West Virginia'],
'East South Central 01': ['Alabama', 'Kentucky'],
'East South Central 02': ['Mississippi', 'Tennessee'],
'West South Central': ['Arkansas', 'Louisiana', 'Oklahome', 'Texas'],
Mountain: ['Arizona', 'Colorado', 'Idaho', 'Montana', 'Nevada', 'New Mexico', 'Utah', 'Wyoming'],
'Pacific 01': ['Alaska', 'California'],
'Pacific 02': ['Hawaii', 'Oregon', 'Washington']
};
const keys = Object.keys(division);
for (const key of keys) {
division[key].map((e) => {
this.items.push({
field: e,
region: key.substring(0, key.length - 3)
});
});
}
this.initData = this.items;
}
public selectionChanging() {
}
}
@Component({
template: `<igx-simple-combo #combo [data]="data" displayKey="test" [(ngModel)]="name"></igx-simple-combo>`,
standalone: true,
imports: [IgxSimpleComboComponent, FormsModule]
})
export class IgxSimpleComboEmptyComponent {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public data: any[] = [];
public name!: string;
}
@Component({
template: `<igx-simple-combo #combo [data]="data" displayKey="name" valueKey="id" [(ngModel)]="name">
<ng-template igxComboToggleIcon><igx-icon>search</igx-icon></ng-template>
</igx-simple-combo>`,
standalone: true,
imports: [IgxSimpleComboComponent, IgxIconComponent, IgxComboToggleIconDirective, FormsModule]
})
export class IgxSimpleComboIconTemplatesComponent {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public data: any[] = [
{ name: 'Sofia', id: '1' },
{ name: 'London', id: '2' },
];
public name!: string;
}
@Component({
template: `<igx-simple-combo [(ngModel)]="selectedItem" [data]="items"></igx-simple-combo>`,
standalone: true,
imports: [IgxSimpleComboComponent, FormsModule]
})
export class ComboModelBindingComponent implements OnInit {
@ViewChild(IgxSimpleComboComponent, { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public items: any[];
public selectedItem: any;
public ngOnInit() {
this.items = [{ text: 'One', id: 0 }, { text: 'Two', id: 1 }, { text: 'Three', id: 2 },
{ text: 'Four', id: 3 }, { text: 'Five', id: 4 }];
}
}
@Component({
template: `
<div class="comboContainer" [style.width]="'500px'">
<igx-simple-combo #combo placeholder="Location(s)"
[data]="citiesData"
[allowCustomValues]="true">
>
</igx-simple-combo>
</div>
`,
standalone: true,
imports: [IgxSimpleComboComponent]
})
class IgxComboInContainerTestComponent {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public citiesData: string[] = [
'New York',
'Sofia',
'Istanbul',
'Paris',
'Hamburg',
'Berlin',
'London',
'Oslo',
'Los Angeles',
'Rome',
'Madrid',
'Ottawa',
'Prague',
'Padua',
'Palermo',
'Palma de Mallorca'];
}
@Component({
providers: [RemoteDataService],
template: `
<label id="mockID">Combo Label</label>
<igx-simple-combo #combo [placeholder]="'Products'" [data]="data | async" (dataPreLoad)="dataLoading($event)" [itemsMaxHeight]='400'
[itemHeight]='40' [valueKey]="'id'" [displayKey]="'product'" [width]="'400px'"
[ariaLabelledBy]="'mockID'">
</igx-simple-combo>
`,
standalone: true,
imports: [IgxSimpleComboComponent, AsyncPipe]
})
export class IgxComboRemoteDataComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public instance: IgxSimpleComboComponent;
public data;
constructor(private remoteDataService: RemoteDataService, public cdr: ChangeDetectorRef) { }
public ngOnInit(): void {
this.data = this.remoteDataService.records;
}
public ngAfterViewInit() {
this.remoteDataService.getData(this.instance.virtualizationState, (count) => {
this.instance.totalItemCount = count;
this.cdr.detectChanges();
});
}
public dataLoading(evt) {
this.remoteDataService.getData(evt, () => {
this.cdr.detectChanges();
});
}
public ngOnDestroy() {
this.cdr.detach();
}
}
@Component({
template: `
<form #form="ngForm">
<igx-simple-combo #testCombo class="input-container" [placeholder]="'Locations'"
name="anyName" required [(ngModel)]="values" [data]="items"
[displayKey]="'field'" [valueKey]="'field'"
[groupKey]="'field' ? 'region' : ''" [width]="'100%'">
<label igxLabel>Combo Label</label>
</igx-simple-combo>
</form>
`,
standalone: true,
imports: [IgxSimpleComboComponent, IgxLabelDirective, FormsModule]
})
class IgxSimpleComboInTemplatedFormComponent {
@ViewChild('testCombo', { read: IgxSimpleComboComponent, static: true })
public testCombo: IgxSimpleComboComponent;
@ViewChild('form')
public form: NgForm;
public items: any[] = [];
public values: Array<any>;
constructor() {
const division = {
'New England 01': ['Connecticut', 'Maine', 'Massachusetts'],
'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'],
'Mid-Atlantic': ['New Jersey', 'New York', 'Pennsylvania'],
'East North Central 02': ['Michigan', 'Ohio', 'Wisconsin'],
'East North Central 01': ['Illinois', 'Indiana'],
'West North Central 01': ['Missouri', 'Nebraska', 'North Dakota', 'South Dakota'],
'West North Central 02': ['Iowa', 'Kansas', 'Minnesota'],
'South Atlantic 01': ['Delaware', 'Florida', 'Georgia', 'Maryland'],
'South Atlantic 02': ['North Carolina', 'South Carolina', 'Virginia'],
'South Atlantic 03': ['District of Columbia', 'West Virginia'],
'East South Central 01': ['Alabama', 'Kentucky'],
'East South Central 02': ['Mississippi', 'Tennessee'],
'West South Central': ['Arkansas', 'Louisiana', 'Oklahome', 'Texas'],
Mountain: ['Arizona', 'Colorado', 'Idaho', 'Montana', 'Nevada', 'New Mexico', 'Utah', 'Wyoming'],
'Pacific 01': ['Alaska', 'California'],
'Pacific 02': ['Hawaii', 'Oregon', 'Washington']
};
const keys = Object.keys(division);
for (const key of keys) {
division[key].map((e) => {
this.items.push({
field: e,
region: key.substring(0, key.length - 3)
});
});
}
}
}
@Component({
template: `
<form [formGroup]="comboForm" #reactiveForm="ngForm">
<igx-simple-combo #reactiveCombo formControlName="comboValue" name="falsyValueCombo"
displayKey="field" valueKey="value" [data]="comboData">
</igx-simple-combo>
</form>
`,
standalone: true,
imports: [IgxSimpleComboComponent, ReactiveFormsModule]
})
export class IgxSimpleComboInReactiveFormComponent {
@ViewChild('reactiveCombo', { read: IgxSimpleComboComponent, static: true })
public reactiveCombo: IgxSimpleComboComponent;
@ViewChild('reactiveForm')
public reactiveForm: NgForm;
public comboForm: UntypedFormGroup;
public comboData: any;
constructor(fb: UntypedFormBuilder) {
this.comboForm = fb.group({
comboValue: new UntypedFormControl('', Validators.required),
});
this.comboData = [
{ field: 'One', value: 1 },
{ field: 'Two', value: 2 },
{ field: 'Three', value: 3 },
{ field: 'Four', value: 4 },
];
}
}
@Component({
template: `
<igx-simple-combo [(ngModel)]="selectedItem" [data]="items" [valueKey]="'id'" [displayKey]="'text'"></igx-simple-combo>
`,
standalone: true,
imports: [IgxSimpleComboComponent, FormsModule]
})
export class IgxSimpleComboBindingDataAfterInitComponent implements AfterViewInit {
public items: any[];
public selectedItem = 1;
constructor(private cdr: ChangeDetectorRef) { }
public ngAfterViewInit() {
requestAnimationFrame(() => {
this.items = [{ text: 'One', id: 1 }, { text: 'Two', id: 2 }, { text: 'Three', id: 3 },
{ text: 'Four', id: 4 }, { text: 'Five', id: 5 }];
this.cdr.detectChanges();
});
}
}
@Component({
template: `
<div style="display: flex; flex-direction: column; height: 100%; justify-content: flex-end;">
<igx-simple-combo #combo [data]="items" [displayKey]="'field'" [valueKey]="'field'" [width]="'100%'"
style="margin-bottom: 60px;">
</igx-simple-combo>
</div>
`,
standalone: true,
imports: [IgxSimpleComboComponent]
})
export class IgxBottomPositionSimpleComboComponent {
@ViewChild('combo', { read: IgxSimpleComboComponent, static: true })
public combo: IgxSimpleComboComponent;
public items: any[] = [];
constructor() {
const division = {
'New England 01': ['Connecticut', 'Maine', 'Massachusetts'],
'New England 02': ['New Hampshire', 'Rhode Island', 'Vermont'],
'Mid-Atlantic': ['New Jersey', 'New York', 'Pennsylvania'],
'East North Central 02': ['Michigan', 'Ohio', 'Wisconsin'],
'East North Central 01': ['Illinois', 'Indiana'],
'West North Central 01': ['Missouri', 'Nebraska', 'North Dakota', 'South Dakota'],
'West North Central 02': ['Iowa', 'Kansas', 'Minnesota'],
'South Atlantic 01': ['Delaware', 'Florida', 'Georgia', 'Maryland'],
'South Atlantic 02': ['North Carolina', 'South Carolina', 'Virginia'],
'South Atlantic 03': ['District of Columbia', 'West Virginia'],
'East South Central 01': ['Alabama', 'Kentucky'],
'East South Central 02': ['Mississippi', 'Tennessee'],
'West South Central': ['Arkansas', 'Louisiana', 'Oklahome', 'Texas'],
Mountain: ['Arizona', 'Colorado', 'Idaho', 'Montana', 'Nevada', 'New Mexico', 'Utah', 'Wyoming'],
'Pacific 01': ['Alaska', 'California'],
'Pacific 02': ['Hawaii', 'Oregon', 'Washington']
};
const keys = Object.keys(division);
for (const key of keys) {
division[key].map((e) => {
this.items.push({
field: e,
region: key.substring(0, key.length - 3)
});
});
}
}
}