Unit Tests for ShellSideMenuComponent

This commit is contained in:
Lorenz Hilpert
2023-04-21 18:49:37 +02:00
parent e3b018c5f7
commit 470a451168
3 changed files with 275 additions and 17 deletions

View File

@@ -3,7 +3,7 @@
<button
type="button"
(click)="createProcess()"
class="hidden w-full py-[0.625rem] start-process-btn desktop:grid grid-flow-row items-center justify-center"
class="create-process-btn hidden w-full py-[0.625rem] start-process-btn desktop:grid grid-flow-row items-center justify-center"
>
<div class="bg-brand text-white w-[2.375rem] h-[2.375rem] rounded-full grid items-center justify-center mx-auto mb-1">
<ui-svg-icon icon="add"></ui-svg-icon>
@@ -12,7 +12,10 @@
</button>
</div>
<ng-container [ngSwitch]="section$ | async">
<nav class="grid grid-flow-row gap-6 side-menu-nav desktop:mt-6 max-h-[calc(100vh-30.625rem)]" *ngSwitchCase="'customer'">
<nav
class="customer-section grid grid-flow-row gap-6 side-menu-nav desktop:mt-6 max-h-[calc(100vh-30.625rem)]"
*ngSwitchCase="'customer'"
>
<a class="side-menu-nav-item" (click)="closeSideMenu()" [routerLink]="[customerBasePath$ | async, 'product']">
<div class="side-menu-nav-item-icon">
<ui-svg-icon icon="import-contacts"></ui-svg-icon>
@@ -43,7 +46,10 @@
<span class="side-menu-nav-item-name">Kundenbestellung</span>
</a>
</nav>
<nav class="grid grid-flow-row gap-6 side-menu-nav desktop:mt-6 max-h-[calc(100vh-30.625rem)] overflow-y-auto" *ngSwitchCase="'branch'">
<nav
class="branch-section grid grid-flow-row gap-6 side-menu-nav desktop:mt-6 max-h-[calc(100vh-30.625rem)] overflow-y-auto"
*ngSwitchCase="'branch'"
>
<a class="side-menu-nav-item" (click)="closeSideMenu()" [routerLink]="['/filiale', 'assortment']">
<div class="side-menu-nav-item-icon">
<ui-svg-icon icon="shape-outline"></ui-svg-icon>
@@ -83,7 +89,7 @@
</ng-container>
<div class="grow"></div>
<hr class="divider" />
<button type="button" class="side-menu-processes" (click)="closeAllProcesses()">
<button type="button" class="side-menu-processes close-all-processes-btn" (click)="closeAllProcesses()">
<div class="side-menu-processes-open mx-auto mb-1">
<span class="isa-label customer:bg-accent-1 branch:bg-accent-2 text-accent-1-content">
{{ processesCount$ | async }}
@@ -97,13 +103,13 @@
</div>
<div class="bg-surface panel rounded-tr-[5px]">
<nav class="grid grid-flow-row gap-2 py-2 side-bottom-menu">
<a class="side-bottom-menu-item" (click)="closeSideMenu()" [routerLink]="['/kunde', 'dashboard']">
<a class="dashboard-btn side-bottom-menu-item" (click)="closeSideMenu()" [routerLink]="['/kunde', 'dashboard']">
<div class="side-bottom-menu-item-icon">
<ui-svg-icon icon="receipt-long"></ui-svg-icon>
</div>
<span class="side-menu-nav-item-name">Dashboard</span>
</a>
<button class="side-bottom-menu-item" (click)="logout()">
<button class="logout-btn side-bottom-menu-item" (click)="logout()">
<div class="side-bottom-menu-item-icon">
<ui-svg-icon icon="logout"></ui-svg-icon>
</div>

View File

@@ -0,0 +1,237 @@
import { Spectator, createComponentFactory } from '@ngneat/spectator';
import { ShellSideMenuComponent } from './side-menu.component';
import { ShellService } from '../shell.service';
import { AuthService } from '@core/auth';
import { StockService } from '@swagger/wws';
import { ApplicationProcess, ApplicationService } from '@core/application';
import { WrongDestinationModalService } from '@shared/modals/wrong-destination-modal';
import { of } from 'rxjs';
import { IfRoleDirective } from 'apps/core/auth/src/lib/if-role.directive';
import { MockComponents, MockDirectives } from 'ng-mocks';
import { RouterTestingModule } from '@angular/router/testing';
import { UISvgIconComponent, UiIconComponent } from '@ui/icon';
import { Router } from '@angular/router';
fdescribe('ShellSideMenuComponent', () => {
let spectator: Spectator<ShellSideMenuComponent>;
const createComponent = createComponentFactory({
component: ShellSideMenuComponent,
imports: [RouterTestingModule],
mocks: [ShellService, AuthService, WrongDestinationModalService],
declarations: [MockDirectives(IfRoleDirective), MockComponents(UISvgIconComponent, UiIconComponent, UISvgIconComponent)],
providers: [
{
provide: ApplicationService,
useValue: jasmine.createSpyObj<ApplicationService>(
'ApplicationService',
{
getSection$: of('customer'),
getProcesses$: of([]),
getProcessById$: of(undefined),
createProcess: Promise.resolve(),
removeProcess: undefined,
},
{ activatedProcessId$: of(undefined) }
),
},
{
provide: StockService,
useValue: jasmine.createSpyObj(StockService, {
StockCurrentBranch: of({ result: { key: 'test' } }),
}),
},
],
});
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
describe('closeSideMenu()', () => {
it('should call shellService.closeSideMenu', () => {
const shellService = spectator.inject(ShellService);
spectator.component.closeSideMenu();
expect(shellService.closeSideMenu).toHaveBeenCalled();
});
});
describe('logout()', () => {
it('should call authService.logout', () => {
const authService = spectator.inject(AuthService);
spectator.component.logout();
expect(authService.logout).toHaveBeenCalled();
});
});
describe('createProcess()', () => {
it('should call this.createCartProcess and this.navigateToCatalog with the created process', async () => {
const process: ApplicationProcess = { id: 1, name: '', section: 'customer' };
const spy = spyOn(spectator.component, 'createCartProcess').and.returnValue(Promise.resolve(process));
const spy2 = spyOn(spectator.component, 'navigateToCatalog');
await spectator.component.createProcess();
expect(spy).toHaveBeenCalled();
expect(spy2).toHaveBeenCalledWith(process);
});
});
describe('createCartProcess()', () => {
it('should create a new process and return it', async () => {
const applicationService = spectator.inject(ApplicationService);
spyOn(spectator.component, 'getNextProcessName').and.returnValue(Promise.resolve('Vorgang 3'));
spyOn(spectator.component, 'getNextProcessId').and.returnValue(123);
const expected: ApplicationProcess = {
id: 123,
name: 'Vorgang 3',
section: 'customer',
type: 'cart',
closeable: true,
};
const result = await spectator.component.createCartProcess();
expect(applicationService.createProcess).toHaveBeenCalledWith(expected);
expect(result).toEqual(expected);
});
});
describe('getNextProcessName()', () => {
it('should return the next process name by getting all "customer" processes and filter by type "cart" and name starting with "Vorgang " returns the next higher number', async () => {
const applicationService = spectator.inject(ApplicationService);
(applicationService.getProcesses$ as jasmine.Spy).and.returnValue(
of([
{ id: 1, name: 'Vorgang 1', section: 'customer', type: 'cart' },
{ id: 2, name: 'Vorgang 2', section: 'customer', type: 'cart' },
{ id: 3, name: 'Vorgang 3', section: 'customer', type: 'cart' },
{ id: 4, name: 'Unit Test', section: 'customer', type: 'cart' },
])
);
const result = await spectator.component.getNextProcessName();
expect(result).toEqual('Vorgang 4');
});
});
describe('getNextProcessId()', () => {
it('should return the next process id using Date.now()', () => {
const result = spectator.component.getNextProcessId();
expect(result).toEqual(Date.now());
});
});
describe('navigateToCatalog(process: ApplicationProcess)', () => {
it('should call Router.navigate with ["/kunde", <process.id>, "product", "search"]', () => {
const router = spectator.inject(Router);
const process: ApplicationProcess = { id: 1, name: '', section: 'customer' };
const spy = spyOn(router, 'navigate');
spectator.component.navigateToCatalog(process);
expect(spy).toHaveBeenCalledWith(['/kunde', 1, 'product', 'search']);
});
});
describe('navigateToDashboard()', () => {
it('should call Router.navigate with ["/kunde", "dashboard"]', () => {
const router = spectator.inject(Router);
const spy = spyOn(router, 'navigate');
spectator.component.navigateToDashboard();
expect(spy).toHaveBeenCalledWith(['/kunde', 'dashboard']);
});
});
describe('closeAllProcesses()', () => {
it('should get processes$ and iterate over them and call applicationService.removeProcess with the process id ', async () => {
const applicationService = spectator.inject(ApplicationService);
spectator.component.processes$ = of([
{ id: 1, name: 'Vorgang 1', section: 'customer', type: 'cart' },
{ id: 2, name: 'Vorgang 2', section: 'customer', type: 'cart' },
{ id: 3, name: 'Vorgang 3', section: 'customer', type: 'cart' },
{ id: 4, name: 'Unit Test', section: 'customer', type: 'cart' },
]);
spyOn(spectator.component, 'navigateToDashboard');
await spectator.component.closeAllProcesses();
expect(applicationService.removeProcess).toHaveBeenCalledWith(1);
expect(applicationService.removeProcess).toHaveBeenCalledWith(2);
expect(applicationService.removeProcess).toHaveBeenCalledWith(3);
expect(applicationService.removeProcess).toHaveBeenCalledWith(4);
expect(spectator.component.navigateToDashboard).toHaveBeenCalled();
});
});
describe('fetchAndOpenPackages()', () => {
it('shuld call WrongDestinationModalService.fetchAndOpen()', () => {
const wrongDestinationModalService = spectator.inject(WrongDestinationModalService);
spectator.component.fetchAndOpenPackages();
expect(wrongDestinationModalService.fetchAndOpen).toHaveBeenCalled();
});
});
describe('template', () => {
describe('.create-process-btn should call createProcess()', () => {
it('should call createProcess()', () => {
const spy = spyOn(spectator.component, 'createProcess');
spectator.click('.create-process-btn');
expect(spy).toHaveBeenCalled();
});
});
describe('nav.customer-section', () => {
it('should render a nav.customer-section when section$ returns "customer"', () => {
spectator.setInput({ section$: of('customer') });
expect(spectator.query('nav.customer-section')).toBeTruthy();
});
it('should not render a nav.customer-section when section$ returns "branch"', () => {
spectator.setInput({ section$: of('branch') });
expect(spectator.query('nav.customer-section')).toBeFalsy();
});
});
describe('nav.branch-section', () => {
it('should render a nav.branch-section when section$ returns "branch"', () => {
spectator.setInput({ section$: of('branch') });
expect(spectator.query('nav.branch-section')).toBeTruthy();
});
it('should not render a nav.branch-section when section$ returns "customer"', () => {
spectator.setInput({ section$: of('customer') });
expect(spectator.query('nav.branch-section')).toBeFalsy();
});
});
describe('.close-all-processes-btn', () => {
it('should call closeAllProcesses() when clicked', () => {
const spy = spyOn(spectator.component, 'closeAllProcesses');
spectator.click('.close-all-processes-btn');
expect(spy).toHaveBeenCalled();
});
it('should render the process count', () => {
spectator.component.processesCount$ = of<any>(3);
spectator.detectComponentChanges();
expect(spectator.query('.close-all-processes-btn .isa-label').textContent).toContain('3');
});
});
describe('.dashboard-btn', () => {
it('should call closeSideMenu() when clicked', () => {
const spy = spyOn(spectator.component, 'closeSideMenu');
spectator.click('.dashboard-btn');
expect(spy).toHaveBeenCalled();
});
});
describe('.logout-btn', () => {
it('should call logout() when clicked', () => {
const spy = spyOn(spectator.component, 'logout');
spectator.click('.logout-btn');
expect(spy).toHaveBeenCalled();
});
});
});
});

View File

@@ -19,12 +19,12 @@ export class ShellSideMenuComponent {
map((x) => x.result.key)
);
processes$ = this._app.getSection$().pipe(switchMap((section) => this._app.getProcesses$(section)));
section$ = this._app.getSection$();
processes$ = this.section$.pipe(switchMap((section) => this._app.getProcesses$(section)));
processesCount$ = this.processes$.pipe(map((processes) => processes?.length ?? 0));
section$ = this._app.getSection$();
activeProcess$ = this._app.activatedProcessId$.pipe(switchMap((processId) => this._app.getProcessById$(processId)));
customerBasePath$ = this.activeProcess$.pipe(
@@ -62,14 +62,12 @@ export class ShellSideMenuComponent {
}
async createCartProcess() {
const processes = await this._app.getProcesses$('customer').pipe(first()).toPromise();
const count = processes.filter((x) => x.type === 'cart' && x.name.startsWith('Vorgang ')).length;
const nextProcessName = await this.getNextProcessName();
const process: ApplicationProcess = {
id: Date.now(),
id: this.getNextProcessId(),
type: 'cart',
name: `Vorgang ${count + 1}`,
name: nextProcessName,
section: 'customer',
closeable: true,
};
@@ -79,6 +77,23 @@ export class ShellSideMenuComponent {
return process;
}
async getNextProcessName() {
let processes = await this._app.getProcesses$('customer').pipe(first()).toPromise();
processes = processes.filter((x) => x.type === 'cart' && x.name.startsWith('Vorgang '));
const maxProcessNumber = processes.reduce((max, process) => {
const number = parseInt(process.name.replace('Vorgang ', ''), 10);
return number > max ? number : max;
}, 0);
return `Vorgang ${maxProcessNumber + 1}`;
}
getNextProcessId() {
return Date.now();
}
navigateToCatalog(process: ApplicationProcess) {
this._router.navigate(['/kunde', process.id, 'product', 'search']);
}
@@ -88,9 +103,9 @@ export class ShellSideMenuComponent {
}
async closeAllProcesses() {
const section = await this._app.getSection$().pipe(take(1)).toPromise();
const processes = await this._app.getProcesses$(section).pipe(take(1)).toPromise();
processes.filter((f) => f.closeable).forEach((process) => this._app.removeProcess(process.id));
const processes = await this.processes$.pipe(take(1)).toPromise();
processes.forEach((process) => this._app.removeProcess(process.id));
this.navigateToDashboard();
}