/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { OverlayContainer } from '@angular/cdk/overlay'; import { ChangeDetectionStrategy, Component, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RadioButtonModule } from '../radiobutton/radiobutton.module'; import { InputDropdownComponent } from './input-dropdown.component'; import { InputDropdownModule } from './input-dropdown.module'; describe('InputDropdownComponent', () => { let component: InputDropdownComponent; let fixture: ComponentFixture>; beforeEach(() => { TestBed.configureTestingModule({ imports: [InputDropdownModule, NoopAnimationsModule], }) .overrideComponent(InputDropdownComponent, { set: { changeDetection: ChangeDetectionStrategy.Default }, }) .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(InputDropdownComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should reset filter correctly', () => { component.filter = 'Tralala'; spyOn(component.filterChange, 'emit'); spyOn(component.filterInputElement.nativeElement, 'focus'); component.resetFilter(); fixture.detectChanges(); expect(component.filter).toEqual(''); expect(component.filterChange.emit).toHaveBeenCalledWith(''); expect(component.filterInputElement.nativeElement.focus).toHaveBeenCalledWith(); }); it('should write value', () => { const selectedSpy = spyOn(component.selectedChange, 'emit'); const newValue = 'Bla'; component.writeValue(newValue); expect(component.selected).toEqual(newValue); expect(selectedSpy).not.toHaveBeenCalled(); }); it('should send the selected value to Angular Forms when an item is selected', () => { const onChangeSpy = spyOn(component, 'onChange'); const newValue = 'Blubb'; // ideally we should have a test suits using ngModel or reactive forms, but for now this // will emulate an item being selected component.ngOnChanges({ selected: new SimpleChange(null, newValue, false), }); expect(onChangeSpy).toHaveBeenCalledWith(newValue); expect(onChangeSpy).toHaveBeenCalledTimes(1); }); it('should clear the filter when an item is selected', () => { const filterValue = 'habe'; component.filter = filterValue; expect(component.filter).toEqual(filterValue); // ideally we should have a test suits using ngModel or reactive forms, but for now this // will emulate an item being selected component.ngOnChanges({ selected: new SimpleChange(null, 'haberdasher', false), }); expect(component.filter).toEqual(''); }); it('should register onTouched', () => { const onTouchedSpy = spyOn(component, 'onTouched'); // ideally we should have a test suits using ngModel or reactive forms, but for now this // will emulate an item being selected component.ngOnChanges({ selected: new SimpleChange(null, 'One', false), }); expect(onTouchedSpy).toHaveBeenCalledTimes(1); }); }); @Component({ selector: 'app-dropdown-test', template: ` Selection: {{ selected ? selected : '(none)' }}
@for (option of options; track option) { {{ option }} }
`, imports: [InputDropdownModule, RadioButtonModule, FormsModule], }) export class InputDropdownTestComponent { disabled: boolean = false; dropdownOpen: boolean = false; allowNull: boolean = false; options: string[] = ['One', 'Two', 'Three']; selected: string = null; ariaLabelledby: string; // eslint-disable-next-line @typescript-eslint/no-empty-function onSelectedChange(event: any): void {} // eslint-disable-next-line @typescript-eslint/no-empty-function onOpenChange(isOpen: boolean): void {} } describe('InputDropdownComponent', () => { let component: InputDropdownTestComponent; let fixture: ComponentFixture; let nativeElement: HTMLElement; let overlayContainer: OverlayContainer; let overlayContainerElement: HTMLElement; let openChangeSpy: jasmine.Spy; beforeEach(() => { TestBed.configureTestingModule({ imports: [ InputDropdownModule, NoopAnimationsModule, RadioButtonModule, FormsModule, InputDropdownTestComponent, ], }).compileComponents(); // access the overlay container inject([OverlayContainer], (oc: OverlayContainer) => { overlayContainer = oc; overlayContainerElement = oc.getContainerElement(); })(); fixture = TestBed.createComponent(InputDropdownTestComponent); component = fixture.componentInstance; nativeElement = fixture.nativeElement; openChangeSpy = spyOn(component, 'onOpenChange'); fixture.detectChanges(); }); afterEach(() => { overlayContainer.ngOnDestroy(); }); it('should open dropdown when dropdownOpen is programmatically set to true', async () => { component.dropdownOpen = true; fixture.detectChanges(); await fixture.whenStable(); const dropdown = document.querySelector('.filter-container'); expect(dropdown).toBeTruthy(); expect(openChangeSpy).not.toHaveBeenCalled(); }); it('should close dropdown when dropdownOpen is programmatically set to false', async () => { component.dropdownOpen = true; fixture.detectChanges(); await fixture.whenStable(); component.dropdownOpen = false; fixture.detectChanges(); await fixture.whenStable(); const filter = document.querySelector('.filter-container'); expect(filter).toBeFalsy(); expect(openChangeSpy).not.toHaveBeenCalled(); }); it('should not focus the input field when dropdownOpen is programmatically set to true', async () => { component.dropdownOpen = true; fixture.detectChanges(); await fixture.whenStable(); const input = document.querySelector('input.form-control'); expect(document.activeElement).not.toBe(input); }); it('should focus the input field when dropdownOpen is opened with a click', async () => { const trigger = document.querySelector('button.form-control') as HTMLButtonElement; trigger.click(); fixture.detectChanges(); await fixture.whenStable(); const input = document.querySelector('input.form-control'); expect(document.activeElement).toBe(input); }); it('dropdownOpenChange should emit when the dropdown is toggled non-programmatically', async () => { const trigger = nativeElement.querySelector('button.form-control') as HTMLButtonElement; await trigger.click(); fixture.detectChanges(); await fixture.whenStable(); expect(openChangeSpy).toHaveBeenCalledTimes(1); expect(openChangeSpy).toHaveBeenCalledWith(true); await trigger.click(); fixture.detectChanges(); await fixture.whenStable(); expect(openChangeSpy).toHaveBeenCalledTimes(2); expect(openChangeSpy).toHaveBeenCalledWith(false); }); it('should not open the dropdown when disabled is true', async () => { const trigger = nativeElement.querySelector('button.form-control') as HTMLButtonElement; component.disabled = true; trigger.click(); fixture.detectChanges(); await fixture.whenStable(); const filterContainer = nativeElement.querySelector('.filter-container'); expect(filterContainer).toBeNull(); }); it('should not retain focus on the button when disabled is true.', async () => { const trigger = nativeElement.querySelector('button.form-control') as HTMLButtonElement; component.disabled = true; fixture.detectChanges(); await fixture.whenStable(); // open menu trigger.click(); fixture.detectChanges(); await fixture.whenStable(); const button = document.querySelector('button.form-control'); expect(document.activeElement).not.toBe(button); }); it('dropdownOpenChange should not emit when the button has been disabled', async () => { const trigger = nativeElement.querySelector('.ux-select-icon') as HTMLButtonElement; component.disabled = true; fixture.detectChanges(); trigger.click(); fixture.detectChanges(); await fixture.whenStable(); expect(openChangeSpy).not.toHaveBeenCalled(); }); it('should apply aria-labelledby to the input element', async () => { component.ariaLabelledby = 'test-id'; component.dropdownOpen = true; fixture.detectChanges(); await fixture.whenStable(); const input = document.querySelector('input.form-control'); expect(input.getAttribute('aria-labelledby')).toContain('test-id'); }); describe('with allowNull = true', () => { beforeEach(() => { component.allowNull = true; component.dropdownOpen = true; component.selected = 'One'; fixture.detectChanges(); }); it('should set the selected display value to "(none)" when the clear button is clicked', async () => { const clearButton = nativeElement.querySelector( '.ux-select-icon.ux-select-clear-icon' ) as HTMLButtonElement; clearButton.click(); fixture.detectChanges(); await fixture.whenStable(); const title = nativeElement.querySelector('.selection'); expect(title.innerText).toBe('Selection: (none)'); }); it('should emit selectedChange when the value has been cleared', async () => { spyOn(component, 'onSelectedChange'); const clearButton = nativeElement.querySelector( '.ux-select-icon.ux-select-clear-icon' ) as HTMLButtonElement; clearButton.click(); fixture.detectChanges(); await fixture.whenStable(); expect(component.onSelectedChange).toHaveBeenCalledWith(undefined); }); it('should not clear the value when disabled is true', async () => { component.disabled = true; fixture.detectChanges(); const clearButton = nativeElement.querySelector( '.ux-select-icon.ux-select-clear-icon' ) as HTMLButtonElement; clearButton.click(); fixture.detectChanges(); await fixture.whenStable(); const title = nativeElement.querySelector('.selection'); expect(title.innerText).toBe('Selection: One'); }); }); });