// unit test ShellComponent with Spectator import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { ApplicationProcess, ApplicationService } from '@core/application'; import { AuthService } from '@core/auth'; import { Config } from '@core/config'; import { BreadcrumbService } from '@core/breadcrumb'; import { DomainAvailabilityService } from '@domain/availability'; import { DomainDashboardService } from '@domain/isa'; import { NotificationsHub } from '@hub/notifications'; import { ModalNotificationsComponent } from '@modal/notifications'; import { Spectator, createComponentFactory, SpyObject, createSpyObject } from '@ngneat/spectator'; import { DashboardComponent } from '@page/dashboard'; import { ShellFooterComponent } from '@shell/footer'; import { ShellHeaderComponent } from '@shell/header'; import { ShellProcessComponent, ShellProcessTabComponent } from '@shell/process'; import { UiIconComponent } from '@ui/icon'; import { UiModalService } from '@ui/modal'; import { EnvelopeDTO, MessageBoardItemDTO } from 'apps/hub/notifications/src/lib/defs'; import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; import { first } from 'rxjs/operators'; import { ShellComponent } from './shell.component'; import { WrongDestinationModalService } from 'apps/page/package-inspection/src/lib/components/wrong-destination-modal/wrong-destination-modal.service'; // DummyComponent Class @Component({ selector: 'dummy-component', template: '
', }) class DummyComponent { constructor() {} } describe('ShellComponent', () => { let spectator: Spectator; let applicationServiceMock: SpyObject; let modalServiceMock: SpyObject; let notificationsHubMock: SpyObject; let router: Router; let breadcrumbServiceMock: SpyObject; let authServiceMock: SpyObject; const createComponent = createComponentFactory({ component: ShellComponent, imports: [ RouterTestingModule.withRoutes([ { path: 'kunde', component: DummyComponent }, { path: 'kunde/dashboard', component: DashboardComponent }, ]), ], declarations: [ MockComponent(ShellHeaderComponent), MockComponent(ShellFooterComponent), MockComponent(ShellProcessComponent), MockComponent(ShellProcessTabComponent), MockComponent(UiIconComponent), ], mocks: [BreadcrumbService, DomainAvailabilityService, AuthService, DomainDashboardService, Config, WrongDestinationModalService], }); beforeEach(() => { applicationServiceMock = createSpyObject(ApplicationService); applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of([])); applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 })); applicationServiceMock.getActivatedProcessId$.and.returnValue(of(undefined)); applicationServiceMock.getLastActivatedProcessWithSectionAndType$.and.returnValue(of({})); applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({})); notificationsHubMock = createSpyObject(NotificationsHub); notificationsHubMock.notifications$ = of({}); modalServiceMock = createSpyObject(UiModalService); authServiceMock = createSpyObject(AuthService); spectator = createComponent({ providers: [ { provide: ApplicationService, useValue: applicationServiceMock }, { provide: NotificationsHub, useValue: notificationsHubMock }, { provide: UiModalService, useValue: modalServiceMock }, { provide: AuthService, useValue: authServiceMock }, ], }); breadcrumbServiceMock = spectator.inject(BreadcrumbService); router = spectator.inject(Router); }); it('should create', () => { expect(spectator.component).toBeTruthy(); }); describe('shell-header', () => { it('should call setSection() on sectionChange event with the section argument', () => { spyOn(spectator.component, 'setSection'); spectator.triggerEventHandler('shell-header', 'sectionChange', 'branch'); expect(spectator.component.setSection).toHaveBeenCalledWith('branch'); }); it('should render the header buttons', () => { // Test verhält sich anders, wenn die größe des Browserfensters kleiner ist als 640px, da // die Buttons dann unsichtbar werden und ins Drei-Punkt Menü verschoben werden. if (document.body.clientWidth > 639) { expect(spectator.query('shell-header .notifications-btn')).toBeVisible(); expect(spectator.query('shell-header .dashboard-btn')).toBeVisible(); expect(spectator.query('shell-header .logout-btn')).toBeVisible(); } else { expect(spectator.query('shell-header .notifications-btn')).not.toBeVisible(); expect(spectator.query('shell-header .dashboard-btn')).not.toBeVisible(); expect(spectator.query('shell-header .logout-btn')).not.toBeVisible(); } }); it('should have a anchor tag which navigates to /kunde/dashboard', () => { const anchor = spectator.query('shell-header a'); expect(anchor).toHaveAttribute('href', '/kunde/dashboard'); }); }); describe('shell-process', () => { it('should call addProcess() on addProcess event', () => { spyOn(spectator.component, 'addProcess'); spectator.triggerEventHandler('shell-process', 'addProcess', undefined); expect(spectator.component.addProcess).toHaveBeenCalled(); }); describe('shell-process-tab', () => { it('should render for each process', () => { const processes = [{}, {}, {}]; applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of(processes)); spectator.detectComponentChanges(); expect(spectator.queryAll('shell-process-tab')).toHaveLength(processes.length); }); it('should call activateProcess() on activateProcess event', () => { const processes = [{ id: 1 }]; applicationServiceMock.getProcesses$.and.returnValue(of(processes)); spectator.detectComponentChanges(); spyOn(spectator.component, 'activateProcess'); spectator.triggerEventHandler('shell-process-tab', 'activateProcess', processes[0].id); expect(spectator.component.activateProcess).toHaveBeenCalledWith(1); }); it('should call closeProcess() on closeProcess event', () => { const processes = [{ id: 1 }]; applicationServiceMock.getProcesses$.and.returnValue(of(processes)); spectator.detectComponentChanges(); spyOn(spectator.component, 'closeProcess'); spectator.triggerEventHandler('shell-process-tab', 'closeProcess', processes[0].id); expect(spectator.component.closeProcess).toHaveBeenCalledWith(1); }); }); }); describe('shell-footer', () => { it('should render when section is set', () => { applicationServiceMock.getSection$.and.returnValue(of('customer')); spectator.detectComponentChanges(); expect(spectator.query('shell-footer')).toBeVisible(); }); it('should not render when section is undefined', () => { applicationServiceMock.getSection$.and.returnValue(of(undefined)); spectator.detectComponentChanges(); expect(spectator.query('shell-footer')).not.toBeVisible(); }); it('should display the menu items for section customer', () => { applicationServiceMock.getSection$.and.returnValue(of('customer')); spectator.component.customerBasePath$ = of('/kunde/1'); spectator.detectComponentChanges(); const anchors = spectator.queryAll('shell-footer a'); expect(anchors[0]).toHaveText('Artikelsuche'); expect(anchors[0]).toHaveAttribute('href', '/kunde/1/product'); expect(anchors[1]).toHaveText('Kundensuche'); expect(anchors[1]).toHaveAttribute('href', '/kunde/1/customer'); expect(anchors[2]).toHaveText('Warenausgabe'); expect(anchors[2]).toHaveAttribute('href', '/kunde/1/goods/out'); }); it('should display the menu items for section branch', () => { applicationServiceMock.getSection$.and.returnValue(of('branch')); spectator.detectComponentChanges(); const anchors = spectator.queryAll('shell-footer a'); expect(anchors[0]).toHaveText('Tätigkeitskalender'); expect(anchors[0]).toHaveAttribute('href', '/filiale/task-calendar'); expect(anchors[1]).toHaveText('Abholfach'); expect(anchors[1]).toHaveAttribute('href', '/filiale/goods/in'); expect(anchors[2]).toHaveText('Remission'); expect(anchors[2]).toHaveAttribute('href', '/filiale/remission'); }); }); describe('activatedProcessId$', () => { it('should call _appService.getActivatedProcessId$() and return its value', async () => { applicationServiceMock.getActivatedProcessId$.and.returnValue(of(1)); const processId = await spectator.component.activatedProcessId$.pipe(first()).toPromise(); expect(processId).toBe(1); expect(applicationServiceMock.getActivatedProcessId$).toHaveBeenCalled(); }); }); describe('section$', () => { it('should call _appService.getSection$() and return its value', async () => { applicationServiceMock.getSection$.and.returnValue(of('branch')); const section = await spectator.component.section$.pipe(first()).toPromise(); expect(section).toBe('branch'); expect(applicationServiceMock.getSection$).toHaveBeenCalled(); }); }); describe('processes$', () => { it('should call _appService.processes$() and return its value', async () => { applicationServiceMock.getProcesses$.and.returnValue(of([{}, {}])); const processes = await spectator.component.processes$.pipe(first()).toPromise(); expect(processes).toHaveLength(2); expect(applicationServiceMock.getProcesses$).toHaveBeenCalledWith('customer'); }); }); describe('remissionProcess$', () => { it('should call _appService.getProcessById$() with Remission Id and return its value', async () => { applicationServiceMock.getProcessById$.and.returnValue(of({ id: 4000 })); await spectator.component.remissionProcess$.pipe(first()).toPromise(); expect(applicationServiceMock.getProcessById$).toHaveBeenCalled(); }); }); describe('remissionUrl$', () => { it('should return the correct url if process.data.active is available', async () => { const process = { id: 4000, data: { active: 9999, }, }; applicationServiceMock.getProcessById$.and.returnValue(of(process)); const url = await spectator.component.remissionUrl$.pipe(first()).toPromise(); expect(url).toBe('/filiale/remission/9999/list'); }); it('should return the correct url if process.data.active is not available', async () => { const process = { id: 4000, data: {}, }; applicationServiceMock.getProcessById$.and.returnValue(of(process)); const url = await spectator.component.remissionUrl$.pipe(first()).toPromise(); expect(url).toBe('/filiale/remission'); }); }); describe('remissionQueryParams$', () => { it('should return the correct queryParams if process.data.active and process.data.queryParams are available', async () => { const process = { id: 4000, data: { active: 9999, queryParams: { filter: 'test' }, }, }; applicationServiceMock.getProcessById$.and.returnValue(of(process)); const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise(); expect(queryParams).toEqual(process.data.queryParams); }); it('should return the correct queryParams if process.data.active and process.data.queryParams are not available', async () => { const process = { id: 4000, data: {}, }; applicationServiceMock.getProcessById$.and.returnValue(of(process)); const queryParams = await spectator.component.remissionQueryParams$.pipe(first()).toPromise(); expect(queryParams).toEqual({}); }); }); describe('setSection()', () => { it('should call _appService.setSection() with the argument section', async () => { await spectator.component.setSection('customer'); expect(applicationServiceMock.setSection).toHaveBeenCalledWith('customer'); }); it('should call activateProcess if getLastActivatedProcessWithSection returns a value', async () => { applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({ id: 1 })); spyOn(spectator.component, 'activateProcess'); await spectator.component.setSection('customer'); expect(spectator.component.activateProcess).toHaveBeenCalledWith(1); }); }); describe('logout()', () => { it('should call _authService.logout()', () => { spectator.component.logout(); expect(authServiceMock.logout).toHaveBeenCalled(); }); }); describe('addProcess()', () => { it('should call navigate to /kunde/{timestamp}/product', () => { spyOn(router, 'navigate'); spyOn(Date, 'now').and.returnValue(123); spectator.component.addProcess(); expect(router.navigate).toHaveBeenCalledWith(['/kunde', 123, 'product']); }); }); describe('closeProcess()', () => { it('should call _appService.removeProcess() with the processId argument', () => { const processes = [{}, {}, {}]; applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of(processes)); spectator.component.closeProcess(1); expect(applicationServiceMock.removeProcess).toHaveBeenCalledWith(1); }); it('should navigate to kunde/dashboard if no process is available', async () => { spyOn(router, 'navigate'); applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of([])); spectator.detectComponentChanges(); await spectator.component.closeProcess(1); expect(router.navigate).toHaveBeenCalledWith(['/kunde', 'dashboard']); }); it('should not navigate to kunde/dashboard if processes are available', async () => { spyOn(router, 'navigate'); const processes = [ { id: 1, name: 'test', section: 'customer' }, { id: 2, name: 'test', section: 'customer' }, ]; applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue(of({})); applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of(processes)); await spectator.component.closeProcess(1); expect(router.navigate).not.toHaveBeenCalledWith(['/kunde', 'dashboard']); }); it('should activate the next process when it was not the last process', async () => { spyOn(spectator.component, 'activateProcess'); applicationServiceMock.getLastActivatedProcessWithSection$.and.returnValue( of({ id: 2, name: 'test', section: 'customer', activated: 2, }) ); const processes = [ { id: 1, name: 'test', section: 'customer', activated: 1 }, { id: 2, name: 'test', section: 'customer', activated: 2 }, ]; applicationServiceMock.getSection$.and.returnValue(of('customer')); applicationServiceMock.getProcesses$.and.returnValue(of(processes)); await spectator.component.closeProcess(1); expect(spectator.component.activateProcess).toHaveBeenCalledWith(2); }); }); describe('activateProcess()', () => { it('should get the last activated breadcrumb by key and if it is defined it navigates to its path with queryParams', async () => { const crumb = { path: '/kunde/product', params: { id: 1 } }; spyOn(router, 'navigate'); breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(crumb)); await spectator.component.activateProcess(1); expect(router.navigate).toHaveBeenCalledWith([crumb.path], { queryParams: crumb.params }); }); it('should navigate to /kunde if no breadcrumb for this process exists', async () => { breadcrumbServiceMock.getLastActivatedBreadcrumbByKey$.and.returnValue(of(undefined)); spyOn(router, 'navigate'); await spectator.component.activateProcess(1); expect(router.navigate).toHaveBeenCalledWith(['/kunde', 1, 'product']); }); }); describe('processAction()', () => { it('should navigate to cart when process type is cart', () => { spyOn(router, 'navigate'); const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'cart' }; spectator.component.processAction(process); expect(router.navigate).toHaveBeenCalledWith(['/kunde', process.id, 'cart']); }); it('should not navigate to when process type is not cart', () => { spyOn(router, 'navigate'); const process: ApplicationProcess = { id: 1, name: 'Vorgang', section: 'customer', type: 'goods-out' }; spectator.component.processAction(process); expect(router.navigate).not.toHaveBeenCalled(); }); }); describe('openNotifications()', () => { it('should call modalService.open() with the ModalNotificationComponent', async () => { const notifications: EnvelopeDTO = { data: [{}, {}, {}], }; spectator.component.notifications$ = of(notifications); await spectator.component.openNotifications(); expect(modalServiceMock.open).toHaveBeenCalledWith({ content: ModalNotificationsComponent, data: notifications, config: { showScrollbarY: false, }, }); }); }); });