import { ComponentFixture, TestBed, fakeAsync, tick, } from '@angular/core/testing' import { GlobalSearchComponent } from './global-search.component' import { Subject, of } from 'rxjs' import { SearchService } from 'src/app/services/rest/search.service' import { Router } from '@angular/router' import { NgbDropdownModule, NgbModal, NgbModalModule, } from '@ng-bootstrap/ng-bootstrap' import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component' import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { HttpClient } from '@angular/common/http' import { HttpClientTestingModule } from '@angular/common/http/testing' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FILTER_HAS_ANY_TAG, FILTER_HAS_CORRESPONDENT_ANY, FILTER_HAS_DOCUMENT_TYPE_ANY, FILTER_HAS_STORAGE_PATH_ANY, FILTER_HAS_TAGS_ANY, } from 'src/app/data/filter-rule-type' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { DocumentService } from 'src/app/services/rest/document.service' import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component' import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component' import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component' const searchResults = { total: 11, documents: [ { id: 1, title: 'Test', created_at: new Date(), updated_at: new Date(), document_type: { id: 1, name: 'Test' }, storage_path: { id: 1, path: 'Test' }, tags: [], correspondents: [], custom_fields: [], }, ], correspondents: [ { id: 1, name: 'TestCorrespondent', }, ], document_types: [ { id: 1, name: 'TestDocumentType', }, ], storage_paths: [ { id: 1, path: 'TestStoragePath', }, ], tags: [ { id: 1, name: 'TestTag', }, ], users: [ { id: 1, username: 'TestUser', }, ], groups: [ { id: 1, name: 'TestGroup', }, ], mail_accounts: [ { id: 1, name: 'TestMailAccount', }, ], mail_rules: [ { id: 1, name: 'TestMailRule', }, ], custom_fields: [ { id: 1, name: 'TestCustomField', }, ], workflows: [ { id: 1, name: 'TestWorkflow', }, ], } describe('GlobalSearchComponent', () => { let component: GlobalSearchComponent let fixture: ComponentFixture let searchService: SearchService let router: Router let modalService: NgbModal let documentService: DocumentService let documentListViewService: DocumentListViewService beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [GlobalSearchComponent], imports: [ HttpClientTestingModule, NgbModalModule, NgbDropdownModule, FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule.pick(allIcons), ], }).compileComponents() searchService = TestBed.inject(SearchService) router = TestBed.inject(Router) modalService = TestBed.inject(NgbModal) documentService = TestBed.inject(DocumentService) documentListViewService = TestBed.inject(DocumentListViewService) fixture = TestBed.createComponent(GlobalSearchComponent) component = fixture.componentInstance fixture.detectChanges() }) it('should handle keyboard events', () => { const focusSpy = jest.spyOn(component.searchInput.nativeElement, 'focus') component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'k', ctrlKey: true }) ) expect(focusSpy).toHaveBeenCalled() // coverage component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'k', metaKey: true }) ) component.searchResults = searchResults as any component.resultsDropdown.open() fixture.detectChanges() component['currentItemIndex'] = 0 const firstItemFocusSpy = jest.spyOn( component.resultItems.get(1).nativeElement, 'focus' ) component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'ArrowDown' }) ) expect(component['currentItemIndex']).toBe(1) expect(firstItemFocusSpy).toHaveBeenCalled() const zeroItemSpy = jest.spyOn( component.resultItems.get(0).nativeElement, 'focus' ) component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'ArrowUp' }) ) expect(component['currentItemIndex']).toBe(0) expect(zeroItemSpy).toHaveBeenCalled() const inputFocusSpy = jest.spyOn( component.searchInput.nativeElement, 'focus' ) component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'ArrowUp' }) ) expect(component['currentItemIndex']).toBe(-1) expect(inputFocusSpy).toHaveBeenCalled() const actionSpy = jest.spyOn(component, 'primaryAction') component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'ArrowDown' }) ) component.handleKeyboardEvent( new KeyboardEvent('keydown', { key: 'Enter' }) ) expect(actionSpy).toHaveBeenCalled() }) it('should search', fakeAsync(() => { const query = 'test' const searchSpy = jest.spyOn(searchService, 'globalSearch') searchSpy.mockReturnValue(of({} as any)) const dropdownOpenSpy = jest.spyOn(component.resultsDropdown, 'open') component.queryDebounce.next(query) tick(401) expect(searchSpy).toHaveBeenCalledWith(query) expect(dropdownOpenSpy).toHaveBeenCalled() })) it('should perform primary action', () => { const object = { id: 1 } const routerSpy = jest.spyOn(router, 'navigate') const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') const modalSpy = jest.spyOn(modalService, 'open') component.primaryAction('document', object) expect(routerSpy).toHaveBeenCalledWith(['/documents', object.id]) component.primaryAction('correspondent', object) expect(qfSpy).toHaveBeenCalledWith([ { rule_type: FILTER_HAS_CORRESPONDENT_ANY, value: object.id.toString() }, ]) component.primaryAction('documentType', object) expect(qfSpy).toHaveBeenCalledWith([ { rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY, value: object.id.toString() }, ]) component.primaryAction('storagePath', object) expect(qfSpy).toHaveBeenCalledWith([ { rule_type: FILTER_HAS_STORAGE_PATH_ANY, value: object.id.toString() }, ]) component.primaryAction('tag', object) expect(qfSpy).toHaveBeenCalledWith([ { rule_type: FILTER_HAS_ANY_TAG, value: object.id.toString() }, ]) component.primaryAction('user', object) expect(modalSpy).toHaveBeenCalledWith(UserEditDialogComponent, { size: 'lg', }) component.primaryAction('group', object) expect(modalSpy).toHaveBeenCalledWith(GroupEditDialogComponent, { size: 'lg', }) component.primaryAction('mailAccount', object) expect(modalSpy).toHaveBeenCalledWith(MailAccountEditDialogComponent, { size: 'xl', }) component.primaryAction('mailRule', object) expect(modalSpy).toHaveBeenCalledWith(MailRuleEditDialogComponent, { size: 'xl', }) component.primaryAction('customField', object) expect(modalSpy).toHaveBeenCalledWith(CustomFieldEditDialogComponent, { size: 'md', }) component.primaryAction('workflow', object) expect(modalSpy).toHaveBeenCalledWith(WorkflowEditDialogComponent, { size: 'xl', }) }) it('should perform secondary action', () => { const doc = searchResults.documents[0] const openSpy = jest.spyOn(window, 'open') component.secondaryAction('document', doc) expect(openSpy).toHaveBeenCalledWith(documentService.getDownloadUrl(doc.id)) const correspondent = searchResults.correspondents[0] const modalSpy = jest.spyOn(modalService, 'open') component.secondaryAction('correspondent', correspondent) expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { size: 'md', }) component.secondaryAction('documentType', searchResults.document_types[0]) expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { size: 'md', }) component.secondaryAction('storagePath', searchResults.storage_paths[0]) expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { size: 'md', }) component.secondaryAction('tag', searchResults.tags[0]) expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, { size: 'md', }) }) it('should reset', () => { const debounce = jest.spyOn(component.queryDebounce, 'next') const closeSpy = jest.spyOn(component.resultsDropdown, 'close') component['reset'](true) expect(debounce).toHaveBeenCalledWith(null) expect(component.searchResults).toBeNull() expect(component['currentItemIndex']).toBe(-1) expect(closeSpy).toHaveBeenCalled() }) it('should set current item', () => { component.searchResults = searchResults as any fixture.detectChanges() const focusSpy = jest.spyOn( component.resultItems.get(0).nativeElement, 'focus' ) component['currentItemIndex'] = 0 component['setCurrentItem']() expect(focusSpy).toHaveBeenCalled() }) it('should handle search input keydown', () => { component.searchResults = searchResults as any component.resultsDropdown.open() fixture.detectChanges() component.searchInputKeyDown( new KeyboardEvent('keydown', { key: 'ArrowDown' }) ) expect(component['currentItemIndex']).toBe(0) component.searchResults = { total: 1 } as any const primaryActionSpy = jest.spyOn(component, 'primaryAction') component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' })) expect(primaryActionSpy).toHaveBeenCalled() const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset') component.searchInputKeyDown( new KeyboardEvent('keydown', { key: 'Escape' }) ) expect(resetSpy).toHaveBeenCalled() }) it('should reset on dropdown close', () => { const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset') component.onDropdownOpenChange(false) expect(resetSpy).toHaveBeenCalled() }) })