import {ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick} from '@angular/core/testing'; import { SorLookupInputComponent } from './sor-lookup-input.component'; import {DataCardService} from '../api/data-card.service'; import {of} from 'rxjs'; import {SorItem, SorService} from '../services/sor.service'; import {By} from '@angular/platform-browser'; import {SimpleChange, SimpleChanges} from '@angular/core'; describe('SorLookupInputComponent', () => { let component: SorLookupInputComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ SorLookupInputComponent ], providers: [ { provide: DataCardService, useValue: {isDataCardClosed: of(false)}}, { provide: SorService, useValue: {lookUp: (lookUpVal: string) => of( [ {name: 'Babyfabrikken', sorId: '123', institutionOwnerSorId: 'abc'}, {name: 'Ude i skoven', sorId: '456', institutionOwnerSorId: 'def'}, {name: 'Dr. Frankensteins Hospital', sorId: '789', institutionOwnerSorId: 'ghi'}, ] as SorItem[])}}, ] }) .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(SorLookupInputComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('detects mouse hover events', (done) => { fixture.whenStable().then(() => { const element = fixture.elementRef.nativeElement; element.dispatchEvent(new Event('mouseenter')); fixture.detectChanges(); expect(component.hover).toBeTrue(); element.dispatchEvent(new Event('mouseleave')); fixture.detectChanges(); expect(component.hover).toBeFalse(); done(); }); }); it('registers as touched on blur', (done) => { let touched = false; component.registerOnTouched(() => touched = true); fixture.whenStable().then(() => { const element = fixture.elementRef.nativeElement; element.dispatchEvent(new Event('blur')); fixture.detectChanges(); expect(touched).toBeTrue(); done(); }); }); it('updates inner value when ngModel is updated on the outside', () => { component.writeValue('tis but a test'); expect(component.innerModel).toEqual('tis but a test'); }); describe('debouncing', () => { beforeEach(() => { fixture = TestBed.createComponent(SorLookupInputComponent); component = fixture.componentInstance; // Notice how we DON'T call fixture.detectChanges() here - that's because we need to set the debounce // property before we initialize the component fully }); it('debounces input when asked to', fakeAsync(() => { component.allowFreeText = true; component.debounceInMs = 200; const spy = spyOn(component, 'writeValue').and.callThrough(); fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; emulateKeystroke(input, 'h'); tick(1); emulateKeystroke(input, 'e'); tick(1); emulateKeystroke(input, 'l'); tick(1); emulateKeystroke(input, 'l'); tick(1); emulateKeystroke(input, 'o'); tick(200); expect(spy).toHaveBeenCalledTimes(1); expect(component.value).toEqual('hello'); }); })); it('does not debounce if not asked to', fakeAsync(() => { component.allowFreeText = true; component.debounceInMs = undefined; const spy = spyOn(component, 'writeValue').and.callThrough(); fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; emulateKeystroke(input, 'h'); tick(1); emulateKeystroke(input, 'e'); tick(1); emulateKeystroke(input, 'l'); tick(1); emulateKeystroke(input, 'l'); tick(1); emulateKeystroke(input, 'o'); tick(200); expect(spy).toHaveBeenCalledTimes(5); expect(component.value).toEqual('hello'); }); })); }); it('makes ngModel undefined when set to not relevant', () => { component.writeValue('test'); fixture.detectChanges(); component.notRelevant = true; component.ngOnChanges({notRelevant: {currentValue: true, previousValue: undefined} as SimpleChange} as SimpleChanges); expect(component.value).toBe(undefined); }); it('re-fills the value when set to not relevant and back again', () => { component.writeValue('test'); fixture.detectChanges(); component.notRelevant = true; component.ngOnChanges({notRelevant: {currentValue: true, previousValue: undefined} as SimpleChange} as SimpleChanges); expect(component.value).toBe(undefined); component.notRelevant = false; component.ngOnChanges({notRelevant: {currentValue: false, previousValue: true} as SimpleChange} as SimpleChanges); expect(component.value).toBe('test'); }); it('does not register a change on the first value set', () => { // This is to avoid issues where values initialized from an async call will mark the form as dirty const mock = { onChange: (whatever: any) => undefined }; const onChange = spyOn(mock, 'onChange'); component.registerOnChange(onChange); component.writeValue('whatever'); expect(onChange).not.toHaveBeenCalled(); component.makeDirtyable(); component.writeValue('even more whatever'); expect(onChange).toHaveBeenCalledWith('even more whatever'); }); it('becomes dirtyable when clicked', () => { fixture.debugElement.query(By.css('mat-select')).triggerEventHandler('focus', {}); fixture.detectChanges(); expect((component as any).dirtyAble).toBeTrue(); }); it('returns an array of strings when doing lookup with type=name', () => { component.type = 'name'; let calledTimes = 0; component.sorAutocompleteOptions$('hej').subscribe((lookupArray) => { calledTimes++; expect(lookupArray[0]).toBe('Babyfabrikken'); }); expect(calledTimes).toBe(1); }); it('returns an array of HealthCareOrganizations when doing lookup with type=healthCareOrganization', () => { component.type = 'healthCareOrganization'; let calledTimes = 0; component.sorAutocompleteOptions$('hej').subscribe((lookupArray) => { calledTimes++; expect(lookupArray[0]).toEqual({name: 'Babyfabrikken', sorId: '123'}); expect(lookupArray[0]).not.toEqual({name: 'Babyfabrikken', sorId: '123', institutionOwnerSorId: 'abc'}); }); expect(calledTimes).toBe(1); }); it('returns an array of SorItems when doing lookup with type=sorItem', () => { component.type = 'sorItem'; let calledTimes = 0; component.sorAutocompleteOptions$('hej').subscribe((lookupArray) => { calledTimes++; expect(lookupArray[0]).toEqual({name: 'Babyfabrikken', sorId: '123', institutionOwnerSorId: 'abc'}); expect(lookupArray[0]).not.toEqual({name: 'Babyfabrikken', sorId: '123'}); }); expect(calledTimes).toBe(1); }); it('sets lookupValue to name of selected item, when type !== name', () => { component.type = 'healthCareOrganization'; component.writeValue({name: 'Babyfabrikken', sorId: '123'}); fixture.detectChanges(); expect(component.lookupValue).toBe('Babyfabrikken'); }); it('does not writeValue on input when type !== name', fakeAsync(() => { component.allowFreeText = true; component.type = 'healthCareOrganization'; component.debounceInMs = undefined; const spy = spyOn(component, 'writeValue').and.callThrough(); fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; emulateKeystroke(input, 'h'); tick(); emulateKeystroke(input, 'e'); tick(); emulateKeystroke(input, 'l'); tick(); emulateKeystroke(input, 'l'); tick(); emulateKeystroke(input, 'o'); tick(); expect(spy).toHaveBeenCalledTimes(0); expect(component.value).toEqual(undefined); discardPeriodicTasks(); }); })); it('does not writeValue on input when allowFreeText is false', fakeAsync(() => { component.allowFreeText = false; component.type = 'name'; component.debounceInMs = undefined; const spy = spyOn(component, 'writeValue').and.callThrough(); fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; emulateKeystroke(input, 'h'); tick(); emulateKeystroke(input, 'e'); tick(); emulateKeystroke(input, 'l'); tick(); emulateKeystroke(input, 'l'); tick(); emulateKeystroke(input, 'o'); tick(); expect(spy).toHaveBeenCalledTimes(0); expect(component.value).toEqual(undefined); discardPeriodicTasks(); }); })); it('focuses input when opening', () => { const spy = spyOn(component, 'focusAutocompleteInput'); component.handleSelectOpen(); expect(spy).toHaveBeenCalledTimes(1); }); it('focuses input when opening', () => { const spy = spyOn(component, 'focusAutocompleteInput'); component.handleSelectOpen(); expect(spy).toHaveBeenCalledTimes(1); }); it('sets value to undefined if closing while lookupvalue is the empty string', () => { component.writeValue('Babyfabrikken'); expect(component.value).toBe('Babyfabrikken'); component.lookupValue = ''; component.handleSelectClose(); expect(component.value).toBe(undefined); }); it('sets lookupValue to value.name if type !== name', () => { component.lookupValue = ''; expect(component.lookupValue).toBe(''); component.type = 'healthCareOrganization'; fixture.detectChanges(); component.setLookupValue({name: 'Babyfabrikken', sorId: '123'}); expect(component.lookupValue).toBe('Babyfabrikken'); }); it('sets lookupValue to value if type === name', () => { component.lookupValue = ''; expect(component.lookupValue).toBe(''); component.type = 'name'; fixture.detectChanges(); component.setLookupValue('Babyfabrikken'); expect(component.lookupValue).toBe('Babyfabrikken'); }); it('sets lookupValue to this.value when select closes and lookupValue !== empty string', () => { component.lookupValue = 'Babyfabrikken'; expect(component.lookupValue).toBe('Babyfabrikken'); component.type = 'name'; component.innerModel = 'Ude i skoven'; fixture.detectChanges(); component.handleSelectClose(); expect(component.lookupValue).toBe('Ude i skoven'); }); }); function emulateKeystroke(input: HTMLInputElement, char: string): void { input.value += char; input.dispatchEvent(new Event('keyup')); }