igniteui-angular-sovn
Version:
Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps
1,094 lines (920 loc) • 138 kB
text/typescript
import { Component, ViewChild, DebugElement, OnInit, ElementRef } from '@angular/core';
import { NgFor, NgIf, NgStyle } from '@angular/common';
import { TestBed, tick, fakeAsync, waitForAsync, discardPeriodicTasks } from '@angular/core/testing';
import { FormsModule, UntypedFormGroup, UntypedFormBuilder, UntypedFormControl, Validators, ReactiveFormsModule, NgForm, NgControl } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { IgxDropDownItemComponent, ISelectionEventArgs } from '../drop-down/public_api';
import { IgxHintDirective, IgxLabelDirective, IgxPrefixDirective, IgxSuffixDirective } from '../input-group/public_api';
import { IgxSelectComponent, IgxSelectFooterDirective, IgxSelectHeaderDirective } from './select.component';
import { IgxSelectItemComponent } from './select-item.component';
import { configureTestSuite } from '../test-utils/configure-suite';
import { HorizontalAlignment, VerticalAlignment, ConnectedPositioningStrategy, AbsoluteScrollStrategy } from '../services/public_api';
import { addScrollDivToElement } from '../services/overlay/overlay.spec';
import { UIInteractions } from '../test-utils/ui-interactions.spec';
import { IgxButtonDirective } from '../directives/button/button.directive';
import { IgxIconComponent } from '../icon/icon.component';
import { IgxInputState } from './../directives/input/input.directive';
import { IgxSelectGroupComponent } from './select-group.component';
import { IgxDropDownItemBaseDirective } from '../drop-down/drop-down-item.base';
const CSS_CLASS_INPUT_GROUP = 'igx-input-group';
const CSS_CLASS_INPUT = 'igx-input-group__input';
const CSS_CLASS_TOGGLE_BUTTON = 'igx-icon';
const CSS_CLASS_DROPDOWN_LIST_SCROLL = 'igx-drop-down__list-scroll';
const CSS_CLASS_DROPDOWN_LIST = 'igx-drop-down__list';
const CSS_CLASS_DROPDOWN_SELECT_HEADER = 'igx-drop-down__select-header';
const CSS_CLASS_DROPDOWN_SELECT_FOOTER = 'igx-drop-down__select-footer';
const CSS_CLASS_DROPDOWN_LIST_ITEM = 'igx-drop-down__item';
const CSS_CLASS_SELECTED_ITEM = 'igx-drop-down__item--selected';
const CSS_CLASS_DISABLED_ITEM = 'igx-drop-down__item--disabled';
const CSS_CLASS_FOCUSED_ITEM = 'igx-drop-down__item--focused';
const CSS_CLASS_INPUT_GROUP_BOX = 'igx-input-group--box';
const CSS_CLASS_INPUT_GROUP_REQUIRED = 'igx-input-group--required';
const CSS_CLASS_INPUT_GROUP_INVALID = 'igx-input-group--invalid';
const CSS_CLASS_INPUT_GROUP_LABEL = 'igx-input-group__label';
const CSS_CLASS_INPUT_GROUP_BORDER = 'igx-input-group--border';
const CSS_CLASS_INPUT_GROUP_COMFORTABLE = 'igx-input-group--comfortable';
const CSS_CLASS_INPUT_GROUP_COSY = 'igx-input-group--cosy';
const CSS_CLASS_INPUT_GROUP_COMPACT = 'igx-input-group--compact';
const arrowDownKeyEvent = new KeyboardEvent('keydown', { key: 'ArrowDown' });
const arrowUpKeyEvent = new KeyboardEvent('keydown', { key: 'ArrowUp' });
const altArrowDownKeyEvent = new KeyboardEvent('keydown', { altKey: true, key: 'ArrowDown' });
const altArrowUpKeyEvent = new KeyboardEvent('keydown', { altKey: true, key: 'ArrowUp' });
const spaceKeyEvent = new KeyboardEvent('keydown', { key: 'Space' });
const escapeKeyEvent = new KeyboardEvent('keydown', { key: 'Escape' });
const enterKeyEvent = new KeyboardEvent('keydown', { key: 'Enter' });
const endKeyEvent = new KeyboardEvent('keydown', { key: 'End' });
const homeKeyEvent = new KeyboardEvent('keydown', { key: 'Home' });
const tabKeyEvent = new KeyboardEvent('keydown', { key: 'Tab' });
const shiftTabKeysEvent = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true });
describe('igxSelect', () => {
let fixture;
let select: IgxSelectComponent;
let inputElement: DebugElement;
let selectList: DebugElement;
let selectListWrapper: DebugElement;
const verifyFocusedItem = focusedItemIndex => {
const focusedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_FOCUSED_ITEM));
expect(focusedItems.length).toEqual(1);
expect(selectList.children[focusedItemIndex].nativeElement.classList.contains(CSS_CLASS_FOCUSED_ITEM)).toBeTruthy();
expect(select.focusedItem).toBe(select.items[focusedItemIndex]);
expect(select.items[focusedItemIndex].focused).toBeTruthy();
};
const verifySelectedItem = itemIndex => {
expect(select.input.value).toEqual(select.items[itemIndex].value);
expect(select.value).toEqual(select.items[itemIndex].value);
const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM));
expect(selectedItems.length).toEqual(1);
expect(selectList.children[itemIndex].nativeElement.classList.contains(CSS_CLASS_SELECTED_ITEM)).toBeTruthy();
expect(select.selectedItem).toBe(select.items[itemIndex] as IgxSelectItemComponent);
expect(select.items[itemIndex].selected).toBeTruthy();
};
const verifyOpenCloseEvents = (openEventCounter = 0, closeEventCounter = 0, toggleCallCounter = 0) => {
expect(select.opening.emit).toHaveBeenCalledTimes(openEventCounter);
expect(select.opened.emit).toHaveBeenCalledTimes(openEventCounter);
expect(select.open).toHaveBeenCalledTimes(openEventCounter);
expect(select.closing.emit).toHaveBeenCalledTimes(closeEventCounter);
expect(select.closed.emit).toHaveBeenCalledTimes(closeEventCounter);
expect(select.close).toHaveBeenCalledTimes(closeEventCounter);
expect(select.toggle).toHaveBeenCalledTimes(toggleCallCounter);
};
configureTestSuite();
beforeAll(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
NoopAnimationsModule,
IgxSelectSimpleComponent,
IgxSelectGroupsComponent,
IgxSelectMiddleComponent,
IgxSelectTopComponent,
IgxSelectBottomComponent,
IgxSelectAffixComponent,
IgxSelectReactiveFormComponent,
IgxSelectTemplateFormComponent,
IgxSelectHeaderFooterComponent,
IgxSelectCDRComponent
]
}).compileComponents();
}));
beforeEach(() => {
UIInteractions.clearOverlay();
});
afterAll(() => {
UIInteractions.clearOverlay();
});
describe('General tests: ', () => {
beforeEach(() => {
fixture = TestBed.createComponent(IgxSelectSimpleComponent);
fixture.detectChanges();
select = fixture.componentInstance.select;
inputElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT));
selectList = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL));
selectListWrapper = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST));
});
it('should initialize the select component properly', () => {
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
expect(fixture.componentInstance).toBeDefined();
expect(select).toBeDefined();
expect(inputGroup).toBeTruthy();
expect(select.placeholder).toBeDefined();
expect(select.value).toBeNull();
expect(select.disabled).toBeFalsy();
expect(select.overlaySettings).toBeUndefined();
expect(select.collapsed).toBeDefined();
expect(select.collapsed).toBeTruthy();
select.open();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
});
it('should properly accept input properties', () => {
expect(select.width).toEqual('300px');
expect(select.height).toEqual('200px');
expect(select.maxHeight).toEqual('256px');
expect(select.disabled).toBeFalsy();
expect(select.placeholder).toEqual('Choose a city');
expect(select.value).toBeNull();
// Default type will be set - currently 'line'
expect(select.type).toEqual('line');
expect(select.displayDensity).toEqual('comfortable');
expect(select.overlaySettings).toBeUndefined();
expect(select.items).toBeDefined();
// Reset input values
select.width = '500px';
expect(select.width).toEqual('500px');
select.height = '450px';
expect(select.height).toEqual('450px');
select.maxHeight = '300px';
expect(select.maxHeight).toEqual('300px');
select.placeholder = 'Your home town';
expect(select.placeholder).toEqual('Your home town');
select.value = 'Hamburg';
expect(select.value).toEqual('Hamburg');
select.type = 'box';
expect(select.type).toEqual('box');
select.displayDensity = 'compact';
expect(select.displayDensity).toEqual('compact');
select.items[3].disabled = true;
expect(select.items[3].disabled).toBeTruthy();
select.items[10].selected = true;
expect(select.items[10].selected).toBeTruthy();
select.items[11].value = 'Milano';
expect(select.items[11].value).toEqual('Milano');
const positionSettings = {
target: select.inputGroup.element.nativeElement,
horizontalDirection: HorizontalAlignment.Right,
verticalDirection: VerticalAlignment.Bottom,
horizontalStartPoint: HorizontalAlignment.Left,
verticalStartPoint: VerticalAlignment.Bottom
};
const customOverlaySettings = {
modal: true,
closeOnOutsideClick: false,
positionStrategy: new ConnectedPositioningStrategy(
positionSettings
),
scrollStrategy: new AbsoluteScrollStrategy()
};
select.overlaySettings = customOverlaySettings;
expect(select.overlaySettings).toBe(customOverlaySettings);
expect(select.collapsed).toBeTruthy();
select.toggle();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
select.disabled = true;
expect(select.disabled).toBeTruthy();
});
it('should open dropdown on input click', () => {
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
expect(select.collapsed).toBeTruthy();
inputGroup.nativeElement.click();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
});
it('should close dropdown on item click', fakeAsync(() => {
const selectedItemEl = selectList.children[2];
expect(select.collapsed).toBeTruthy();
select.toggle();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
selectedItemEl.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
}));
it('should close dropdown on clicking selected item', fakeAsync(() => {
spyOn(select.selectionChanging, 'emit');
select.items[1].selected = true;
select.open();
fixture.detectChanges();
const selectedItemEl = selectList.children[1];
expect(select.collapsed).toBeFalsy();
selectedItemEl.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
select.open();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
selectedItemEl.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
expect(select.selectionChanging.emit).toHaveBeenCalledTimes(1);
}));
it('should toggle dropdown on toggle button click', fakeAsync(() => {
const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
expect(select.collapsed).toBeTruthy();
toggleBtn.nativeElement.click();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
toggleBtn.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
}));
it('should toggle dropdown using API methods', fakeAsync(() => {
select.items[0].selected = true;
expect(select.collapsed).toBeTruthy();
select.open();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
select.close();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
select.toggle();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
select.toggle();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
}));
it('should not display dropdown list when no select items', fakeAsync(() => {
fixture.componentInstance.items = [];
fixture.detectChanges();
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
inputGroup.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeTruthy();
expect(selectListWrapper.nativeElement.classList.contains('igx-toggle--hidden')).toBeTruthy();
}));
it('should properly emit opening/closing events on input click', fakeAsync(() => {
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
expect(select).toBeTruthy();
spyOn(select.opening, 'emit');
spyOn(select.opened, 'emit');
spyOn(select.closing, 'emit');
spyOn(select.closed, 'emit');
spyOn(select, 'toggle').and.callThrough();
spyOn(select, 'open').and.callThrough();
spyOn(select, 'close').and.callThrough();
inputGroup.nativeElement.click();
tick();
fixture.detectChanges();
verifyOpenCloseEvents(1, 0, 1);
inputGroup.nativeElement.click();
tick();
fixture.detectChanges();
verifyOpenCloseEvents(1, 1, 2);
select.disabled = true;
fixture.detectChanges();
inputGroup.nativeElement.click();
tick();
fixture.detectChanges();
// No additional calls, because select is disabled
expect(select.closing.emit).toHaveBeenCalledTimes(1);
expect(select.closed.emit).toHaveBeenCalledTimes(1);
expect(select.opening.emit).toHaveBeenCalledTimes(1);
expect(select.opened.emit).toHaveBeenCalledTimes(1);
}));
it('should properly emit closing events on item click', fakeAsync(() => {
const selectedItemEl = selectList.children[2];
select.toggle();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
spyOn(select.closing, 'emit');
spyOn(select.closed, 'emit');
selectedItemEl.nativeElement.click();
tick();
fixture.detectChanges();
expect(select.closing.emit).toHaveBeenCalledTimes(1);
expect(select.closed.emit).toHaveBeenCalledTimes(1);
}));
it('should properly emit opening/closing events on toggle button click', fakeAsync(() => {
const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
expect(select).toBeTruthy();
spyOn(select.opening, 'emit');
spyOn(select.opened, 'emit');
spyOn(select.closing, 'emit');
spyOn(select.closed, 'emit');
spyOn(select, 'toggle').and.callThrough();
spyOn(select, 'open').and.callThrough();
spyOn(select, 'close').and.callThrough();
toggleBtn.nativeElement.click();
tick();
fixture.detectChanges();
verifyOpenCloseEvents(1, 0, 1);
toggleBtn.nativeElement.click();
tick();
fixture.detectChanges();
verifyOpenCloseEvents(1, 1, 2);
}));
it('should emit closing events on input blur when closeOnOutsideClick: true (default value)', fakeAsync(() => {
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
spyOn(select.closing, 'emit');
spyOn(select.closed, 'emit');
expect(select).toBeDefined();
select.toggle();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
dummyInput.focus();
dummyInput.click();
tick();
fixture.detectChanges();
expect(dummyInput).toEqual(document.activeElement);
expect(select.collapsed).toBeTruthy();
expect(select.closing.emit).toHaveBeenCalledTimes(1);
expect(select.closed.emit).toHaveBeenCalledTimes(1);
}));
it('should NOT emit closing events on input blur when closeOnOutsideClick: false', fakeAsync(() => {
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
spyOn(select.closing, 'emit');
spyOn(select.closed, 'emit');
const customOverlaySettings = {
closeOnOutsideClick: false
};
select.overlaySettings = customOverlaySettings;
expect(select.overlaySettings).toBe(customOverlaySettings);
expect(select.collapsed).toBeTruthy();
fixture.detectChanges();
expect(select).toBeDefined();
select.toggle();
tick();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
dummyInput.focus();
dummyInput.click();
tick();
fixture.detectChanges();
expect(dummyInput).toEqual(document.activeElement);
expect(select.collapsed).toBeFalsy();
expect(select.closing.emit).toHaveBeenCalledTimes(0);
expect(select.closed.emit).toHaveBeenCalledTimes(0);
}));
it('should render aria attributes properly', fakeAsync(() => {
const dropdownListElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL));
const dropdownWrapper = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST));
const toggleBtn = fixture.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
const labelID = fixture.componentInstance.label1.nativeElement.getAttribute('id');
expect(inputElement.nativeElement.getAttribute('role')).toEqual('combobox');
expect(inputElement.nativeElement.getAttribute('aria-haspopup')).toEqual('listbox');
expect(inputElement.nativeElement.getAttribute('aria-labelledby')).toEqual(labelID);
expect(dropdownListElement.nativeElement.getAttribute('aria-labelledby')).toEqual(labelID);
expect(inputElement.nativeElement.getAttribute('aria-owns')).toEqual(select.listId);
expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('false');
expect(toggleBtn.nativeElement.getAttribute('aria-hidden')).toEqual('true');
expect(dropdownListElement.nativeElement.getAttribute('role')).toEqual('listbox');
expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('true');
select.toggle();
tick();
fixture.detectChanges();
expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('true');
expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('false');
select.toggle();
tick();
fixture.detectChanges();
expect(inputElement.nativeElement.getAttribute('aria-expanded')).toEqual('false');
expect(dropdownWrapper.nativeElement.getAttribute('aria-hidden')).toEqual('true');
}));
it('should render aria attributes on dropdown items properly', () => {
const selectItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_DROPDOWN_LIST_ITEM));
selectItems.forEach(item => {
expect(item.nativeElement.getAttribute('role')).toEqual('option');
expect(item.nativeElement.getAttribute('aria-selected')).toEqual('false');
expect(item.nativeElement.getAttribute('aria-disabled')).toEqual('false');
});
const selectedItem = select.items[2];
const disabledItem = select.items[8];
selectedItem.selected = true;
disabledItem.disabled = true;
fixture.detectChanges();
expect(selectItems[selectedItem.index].nativeElement.getAttribute('aria-selected')).toEqual('true');
expect(selectItems[disabledItem.index].nativeElement.getAttribute('aria-disabled')).toEqual('true');
});
it('should render input type properly', fakeAsync(() => {
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
// Default type will be set - currently 'line'
expect(select.type).toEqual('line');
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BOX)).toBeFalsy();
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BORDER)).toBeFalsy();
select.type = 'box';
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BOX)).toBeTruthy();
select.type = 'border';
fixture.detectChanges();
tick();
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_BORDER)).toBeTruthy();
}));
it('should render display density properly', () => {
const inputGroup = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
// Default display density is 'comfortable'
expect(select.displayDensity).toEqual('comfortable');
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COMFORTABLE)).toBeTruthy();
select.displayDensity = 'cosy';
fixture.detectChanges();
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COSY)).toBeTruthy();
select.displayDensity = 'compact';
fixture.detectChanges();
expect(inputGroup.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_COMPACT)).toBeTruthy();
});
it('should close dropdown on blur when closeOnOutsideClick: true (default value)', fakeAsync(() => {
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
expect(select.collapsed).toBeTruthy();
select.toggle();
fixture.detectChanges();
expect(select.collapsed).toBeFalsy();
dummyInput.focus();
dummyInput.click();
tick();
fixture.detectChanges();
expect(dummyInput).toEqual(document.activeElement);
expect(select.collapsed).toBeTruthy();
}));
it('should NOT close dropdown on blur when closeOnOutsideClick: false', fakeAsync(() => {
const dummyInput = fixture.componentInstance.dummyInput.nativeElement;
const customOverlaySettings = {
closeOnOutsideClick: false
};
select.overlaySettings = customOverlaySettings;
expect(select.overlaySettings).toBe(customOverlaySettings);
expect(select.collapsed).toBeTruthy();
fixture.detectChanges();
select.toggle();
expect(select.collapsed).toBeFalsy();
dummyInput.focus();
dummyInput.click();
tick();
fixture.detectChanges();
expect(dummyInput).toEqual(document.activeElement);
expect(select.collapsed).toBeFalsy();
}));
});
describe('Form tests: ', () => {
it('Should properly initialize when used as a reactive form control - with validators', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
const inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
fix.detectChanges();
const selectComp = fix.componentInstance.select;
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
expect(selectFormReference).toBeDefined();
expect(selectComp).toBeDefined();
expect(selectComp.selectedItem).toBeUndefined();
expect(selectComp.value).toEqual('');
expect(inputGroupIsRequiredClass).toBeDefined();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.toggle();
expect(selectComp.collapsed).toEqual(false);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INVALID);
selectComp.selectItem(selectComp.items[4]);
expect(selectComp.value).toEqual('Option 5');
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.value = 'Option 1';
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
}));
it('Should properly initialize when used as a reactive form control - without initial validators', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
fix.detectChanges();
// 1) check if label's --required class and its asterisk are applied
const dom = fix.debugElement;
const selectComp = fix.componentInstance.select;
const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm;
let inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
let inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
// interaction test - expect actual asterisk
// The only way to get a pseudo elements like :before OR :after is to use getComputedStyle(element [, pseudoElt]),
// as these are not in the actual DOM
let asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
expect(asterisk).toBe('"*"');
expect(inputGroupIsRequiredClass).toBeDefined();
expect(inputGroupIsRequiredClass).not.toBeNull();
// 2) check that input group's --invalid class is NOT applied
expect(inputGroupInvalidClass).toBeNull();
// interaction test - markAsTouched + open&close so the --invalid and --required classes are applied
fix.debugElement.componentInstance.markAsTouched();
const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
inputGroup.nativeElement.click();
const toggleBtn = fix.debugElement.query(By.css('.' + CSS_CLASS_TOGGLE_BUTTON));
toggleBtn.nativeElement.click();
tick();
fix.detectChanges();
expect(selectComp.collapsed).toEqual(true);
inputGroupInvalidClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_INVALID));
expect(inputGroupInvalidClass).not.toBeNull();
expect(inputGroupInvalidClass).not.toBeUndefined();
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
expect(inputGroupIsRequiredClass).not.toBeNull();
expect(inputGroupIsRequiredClass).not.toBeUndefined();
// 3) Check if the input group's --invalid and --required classes are removed when validator is dynamically cleared
fix.componentInstance.removeValidators(formGroup);
fix.detectChanges();
tick();
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
const selectFormReference = fix.componentInstance.reactiveForm.controls.optionsSelect;
// interaction test - expect no asterisk
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
expect(selectFormReference).toBeDefined();
expect(selectComp).toBeDefined();
expect(selectComp.selectedItem).toBeUndefined();
expect(selectComp.value).toEqual('');
expect(inputGroupIsRequiredClass).toBeNull();
expect(asterisk).toBe('none');
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.selectItem(selectComp.items[4]);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
document.documentElement.dispatchEvent(new Event('click'));
expect(selectComp.collapsed).toEqual(true);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
// Re-add all Validators
fix.componentInstance.addValidators(formGroup);
fix.detectChanges();
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
expect(inputGroupIsRequiredClass).toBeDefined();
expect(inputGroupIsRequiredClass).not.toBeNull();
expect(inputGroupIsRequiredClass).not.toBeUndefined();
// interaction test - expect actual asterisk
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
expect(asterisk).toBe('"*"');
// 4) Should NOT remove asterisk, when remove validators on igxSelect with required HTML attribute set(edge case)
// set required HTML attribute
inputGroup.parent.nativeElement.setAttribute('required', '');
// Re-add all Validators
fix.componentInstance.addValidators(formGroup);
fix.detectChanges();
// update and clear validators
fix.componentInstance.removeValidators(formGroup);
fix.detectChanges();
tick();
// expect asterisk
asterisk = window.getComputedStyle(dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_LABEL)).nativeElement, ':after').content;
expect(asterisk).toBe('"*"');
inputGroupIsRequiredClass = dom.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
expect(inputGroupIsRequiredClass).toBeDefined();
expect(inputGroupIsRequiredClass).not.toBeNull();
expect(inputGroupIsRequiredClass).not.toBeUndefined();
}));
it('should update validity state when programmatically setting errors on reactive form controls', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
fix.detectChanges();
const selectComp = fix.componentInstance.select;
const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm;
// the form control has validators
formGroup.markAllAsTouched();
formGroup.get('optionsSelect').setErrors({ error: true });
fix.detectChanges();
expect(selectComp.input.valid).toBe(IgxInputState.INVALID);
expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_INVALID)).toBe(true);
expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(true);
// remove the validators and set errors
(fix.componentInstance as IgxSelectReactiveFormComponent).removeValidators(formGroup);
formGroup.markAsUntouched();
fix.detectChanges();
formGroup.markAllAsTouched();
formGroup.get('optionsSelect').setErrors({ error: true });
fix.detectChanges();
// no validator, but there is a set error
expect(selectComp.input.valid).toBe(IgxInputState.INVALID);
expect((selectComp as any).inputGroup.element.nativeElement).toHaveClass(CSS_CLASS_INPUT_GROUP_INVALID);
expect((selectComp as any).inputGroup.element.nativeElement.classList.contains(CSS_CLASS_INPUT_GROUP_REQUIRED)).toBe(false);
}));
it('Should properly initialize when used as a form control - with initial validators', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
fix.detectChanges();
const selectComp = fix.componentInstance.select;
const selectFormReference = fix.componentInstance.ngForm.form;
expect(selectFormReference).toBeDefined();
expect(selectComp).toBeDefined();
tick();
fix.detectChanges();
expect(selectComp.selectedItem).toBeUndefined();
expect(selectComp.value).toBeNull();
expect(inputGroupIsRequiredClass).toBeDefined();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.toggle();
expect(selectComp.collapsed).toEqual(false);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INVALID);
selectComp.selectItem(selectComp.items[4]);
expect(selectComp.value).toEqual('Option 5');
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.value = 'Option 1';
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
fix.componentInstance.isRequired = false;
fix.detectChanges();
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
expect(inputGroupIsRequiredClass).toBeNull();
}));
it('Should properly initialize when used as a form control - without initial validators', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
let inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
fix.detectChanges();
const selectComp = fix.componentInstance.select;
const selectFormReference = fix.componentInstance.ngForm.form;
selectFormReference.clearValidators();
expect(selectFormReference).toBeDefined();
expect(selectComp).toBeDefined();
expect(selectComp.selectedItem).toBeUndefined();
expect(selectComp.value).toBeUndefined();
expect(inputGroupIsRequiredClass).toBeNull();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.selectItem(selectComp.items[4]);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
document.documentElement.dispatchEvent(new Event('click'));
expect(selectComp.collapsed).toEqual(true);
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
fix.componentInstance.isRequired = true;
fix.detectChanges();
inputGroupIsRequiredClass = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP_REQUIRED));
expect(inputGroupIsRequiredClass).toBeDefined();
}));
it('Should have correctly bound focus and blur handlers', () => {
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
fix.detectChanges();
select = fix.componentInstance.select;
const input = fix.debugElement.query(By.css(`.${CSS_CLASS_INPUT}`));
spyOn(select, 'onFocus');
spyOn(select, 'onBlur');
input.triggerEventHandler('focus', {});
expect(select.onFocus).toHaveBeenCalled();
expect(select.onFocus).toHaveBeenCalledWith();
input.triggerEventHandler('blur', {});
expect(select.onBlur).toHaveBeenCalled();
expect(select.onFocus).toHaveBeenCalledWith();
});
// Bug #6025 Select does not disable in reactive form
it('Should disable when form is disabled', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectReactiveFormComponent);
fix.detectChanges();
const formGroup: UntypedFormGroup = fix.componentInstance.reactiveForm;
const selectComp = fix.componentInstance.select;
const inputGroup = fix.debugElement.query(By.css('.' + CSS_CLASS_INPUT_GROUP));
inputGroup.nativeElement.click();
tick();
fix.detectChanges();
expect(selectComp.collapsed).toBeFalsy();
selectComp.close();
fix.detectChanges();
formGroup.disable();
tick();
fix.detectChanges();
inputGroup.nativeElement.click();
tick();
fix.detectChanges();
expect(selectComp.collapsed).toBeTruthy();
}));
it('should set validity to initial when the form is reset', fakeAsync(() => {
const fix = TestBed.createComponent(IgxSelectTemplateFormComponent);
fix.detectChanges();
tick();
const selectComp = fix.componentInstance.select;
selectComp.onBlur();
expect(selectComp.input.valid).toEqual(IgxInputState.INVALID);
fix.componentInstance.ngForm.resetForm();
tick();
expect(selectComp.input.valid).toEqual(IgxInputState.INITIAL);
}));
});
describe('Selection tests: ', () => {
describe('Using simple select component', () => {
beforeEach(() => {
fixture = TestBed.createComponent(IgxSelectSimpleComponent);
select = fixture.componentInstance.select;
fixture.detectChanges();
inputElement = fixture.debugElement.query(By.css('.' + CSS_CLASS_INPUT));
selectList = fixture.debugElement.query(By.css('.' + CSS_CLASS_DROPDOWN_LIST_SCROLL));
});
it('should select item with mouse click', fakeAsync(() => {
let selectedItemIndex = 5;
select.toggle();
tick();
fixture.detectChanges();
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 15;
select.toggle();
tick();
fixture.detectChanges();
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
}));
it('should select item with API selectItem() method', fakeAsync(() => {
let selectedItemIndex = 15;
select.selectItem(select.items[selectedItemIndex]);
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 1;
select.selectItem(select.items[selectedItemIndex]);
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
}));
it('should select item on setting value property', fakeAsync(() => {
let selectedItemIndex = 7;
select.value = select.items[selectedItemIndex].value.toString();
fixture.detectChanges();
tick();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 12;
select.value = select.items[selectedItemIndex].value.toString();
fixture.detectChanges();
tick();
verifySelectedItem(selectedItemIndex);
}));
it('should select item on setting item\'s selected property', () => {
let selectedItemIndex = 9;
select.items[selectedItemIndex].selected = true;
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 14;
select.items[selectedItemIndex].selected = true;
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
});
it('should select item with ENTER/SPACE keys', fakeAsync(() => {
let selectedItemIndex = 2;
select.toggle();
tick();
fixture.detectChanges();
inputElement.triggerEventHandler('keydown', arrowDownKeyEvent);
inputElement.triggerEventHandler('keydown', arrowDownKeyEvent);
inputElement.triggerEventHandler('keydown', spaceKeyEvent);
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 4;
select.toggle();
tick();
fixture.detectChanges();
inputElement.triggerEventHandler('keydown', arrowDownKeyEvent);
inputElement.triggerEventHandler('keydown', arrowDownKeyEvent);
inputElement.triggerEventHandler('keydown', enterKeyEvent);
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
}));
it('should allow single selection only', fakeAsync(() => {
let selectedItemIndex = 5;
select.toggle();
tick();
fixture.detectChanges();
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 15;
select.selectItem(select.items[selectedItemIndex]);
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
selectedItemIndex = 8;
select.value = select.items[selectedItemIndex].value.toString();
fixture.detectChanges();
tick();
verifySelectedItem(selectedItemIndex);
}));
it('should clear selection when value property does not match any item', fakeAsync(() => {
const selectedItemIndex = 5;
select.value = select.items[selectedItemIndex].value.toString();
fixture.detectChanges();
tick();
verifySelectedItem(selectedItemIndex);
select.value = 'Ghost city';
tick();
fixture.detectChanges();
const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM));
expect(selectedItems.length).toEqual(0);
expect(select.selectedItem).toBeUndefined();
expect(select.input.value).toEqual('');
}));
it('should focus first item in dropdown if there is not selected item', fakeAsync(() => {
const focusedItemIndex = 0;
const selectedItemIndex = 8;
select.toggle();
tick();
fixture.detectChanges();
verifyFocusedItem(focusedItemIndex);
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
verifySelectedItem(selectedItemIndex);
expect(select.items[focusedItemIndex].focused).toBeFalsy();
// Unselect selected item
select.value = '';
fixture.detectChanges();
select.toggle();
tick();
fixture.detectChanges();
verifyFocusedItem(focusedItemIndex);
}));
it('should populate the input box with the selected item value', fakeAsync(() => {
let selectedItemIndex = 5;
let selectedItemValue = select.items[selectedItemIndex].value;
const checkInputValue = () => {
expect(select.selectedItem.value).toEqual(selectedItemValue);
expect(select.value).toEqual(selectedItemValue);
expect(inputElement.nativeElement.value.toString().trim()).toEqual(selectedItemValue);
};
// There is not a selected item initially
const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM));
expect(selectedItems.length).toEqual(0);
expect(select.value).toBeNull();
expect(select.input.value).toEqual('');
expect(inputElement.nativeElement.value).toEqual('');
// Select item - mouse click
select.toggle();
tick();
fixture.detectChanges();
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
checkInputValue();
// Select item - selectItem method
selectedItemIndex = 0;
selectedItemValue = select.items[selectedItemIndex].value;
select.selectItem(select.items[selectedItemIndex]);
tick();
fixture.detectChanges();
select.toggle();
tick();
fixture.detectChanges();
checkInputValue();
// Select item - item selected property
selectedItemIndex = 12;
selectedItemValue = select.items[selectedItemIndex].value;
select.items[selectedItemIndex].selected = true;
fixture.detectChanges();
tick();
fixture.detectChanges();
checkInputValue();
// Select item - value property
selectedItemIndex = 8;
selectedItemValue = select.items[selectedItemIndex].value;
select.value = select.items[selectedItemIndex].value.toString();
fixture.detectChanges();
tick();
fixture.detectChanges();
checkInputValue();
}));
it('should populate the input with the selected item text', fakeAsync(() => {
let selectedItemIndex = 0;
const checkInputValue = () => {
expect(select.selectedItem.text).toEqual(select.input.value);
expect(inputElement.nativeElement.value.toString().trim()).toEqual(select.selectedItem.text);
};
// There is not a selected item initially
const selectedItems = fixture.debugElement.queryAll(By.css('.' + CSS_CLASS_SELECTED_ITEM));
expect(selectedItems.length).toEqual(0);
expect(select.value).toBeNull();
expect(select.input.value).toEqual('');
expect(inputElement.nativeElement.value).toEqual('');
// Select item - mouse click
select.toggle();
tick();
fixture.detectChanges();
selectList.children[selectedItemIndex].nativeElement.click();
tick();
fixture.detectChanges();
checkInputValue();
// Select item - selectItem method
selectedItemIndex = 1;
select.selectItem(select.items[selectedItemIndex]);
tick();
fixture.detectChanges();
select.toggle();
tick();