diff --git a/goofy-client/libs/api-root-shared/src/lib/api-root.linkrel.ts b/goofy-client/libs/api-root-shared/src/lib/api-root.linkrel.ts index 8a4faba6927a932dbff0b372eb370066bb63c4c0..8000a325a35eb37f6e7a3df85aa1381ae2aae312 100644 --- a/goofy-client/libs/api-root-shared/src/lib/api-root.linkrel.ts +++ b/goofy-client/libs/api-root-shared/src/lib/api-root.linkrel.ts @@ -1,6 +1,8 @@ export enum ApiRootLinkRel { VORGAENGE = 'vorgaenge', + MY_VORGAENGE = 'myVorgaenge', SEARCH = 'search', + SEARCH_MY_VORGAENGE = 'searchMyVorgaenge', SEARCH_USER_PROFILES = 'search-user-profiles', DOWNLOAD_TOKEN = 'downloadToken' } \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation.module.ts b/goofy-client/libs/navigation/src/lib/navigation.module.ts index 87467d3c79bb485401dda82cf4295abcf6b21789..7e25ca7e5083dda422b73f0dfb6e0780fc046c82 100644 --- a/goofy-client/libs/navigation/src/lib/navigation.module.ts +++ b/goofy-client/libs/navigation/src/lib/navigation.module.ts @@ -8,6 +8,8 @@ import { BuildInfoComponent } from './build-info/build-info.component'; import { HeaderContainerComponent } from './header-container/header-container.component'; import { HeaderComponent } from './header-container/header/header.component'; import { SettingsComponent } from './header-container/header/settings/settings.component'; +import { AllVorgaengeNavigationItemComponent } from './navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component'; +import { MyVorgaengeNavigationItemComponent } from './navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component'; import { NavigationComponent } from './navigation/navigation.component'; @NgModule({ @@ -16,7 +18,9 @@ import { NavigationComponent } from './navigation/navigation.component'; HeaderComponent, NavigationComponent, SettingsComponent, - HeaderContainerComponent + HeaderContainerComponent, + AllVorgaengeNavigationItemComponent, + MyVorgaengeNavigationItemComponent ], imports: [ CommonModule, diff --git a/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.html b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.html new file mode 100644 index 0000000000000000000000000000000000000000..5527a14181cfc894a711bccfc6b09e1f837f35a5 --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.html @@ -0,0 +1,10 @@ +<a #home="routerLinkActive" routerLink="/" + routerLinkActive="active" + [routerLinkActiveOptions]="{ exact: true }" + [matTooltip]="navigationCollapse ? label: null" + matRipple + matRippleColor="rgba(13,71,161, 0.08)"> + <mat-icon>apps</mat-icon> + <h1 *ngIf="home.isActive; else homeInactive">{{label}}</h1> + <ng-template #homeInactive><span>{{label}}</span></ng-template> +</a> \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.scss b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.spec.ts b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a749332616862f7621018eaeae50d9bb0cdd051 --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AllVorgaengeNavigationItemComponent } from './all-vorgaenge-navigation-item.component'; + +describe('AllVorgaengeNavigationItemComponent', () => { + let component: AllVorgaengeNavigationItemComponent; + let fixture: ComponentFixture<AllVorgaengeNavigationItemComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + MatIcon, + AllVorgaengeNavigationItemComponent + ], + imports: [ + RouterTestingModule, + MatTooltipModule, + ] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AllVorgaengeNavigationItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.ts b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a3e503a4bc29a7f55026a88c37f84bc5c4cfbb5 --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'goofy-client-all-vorgaenge-navigation-item', + templateUrl: './all-vorgaenge-navigation-item.component.html', + styleUrls: ['./all-vorgaenge-navigation-item.component.scss'] +}) +export class AllVorgaengeNavigationItemComponent { + + @Input() navigationCollapse: boolean; + + readonly label: string = 'Alle Vorgänge'; + +} \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.html b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.html new file mode 100644 index 0000000000000000000000000000000000000000..8cd267eca080072f75890a8aa41db619c3844d1a --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.html @@ -0,0 +1,10 @@ +<a #home="routerLinkActive" routerLink="/meineVorgaenge" + routerLinkActive="active" + [routerLinkActiveOptions]="{ exact: true }" + [matTooltip]="navigationCollapse ? label: null" + matRipple + matRippleColor="rgba(13,71,161, 0.08)"> + <mat-icon>star</mat-icon> + <h1 *ngIf="home.isActive; else homeInactive">{{label}}</h1> + <ng-template #homeInactive><span>{{label}}</span></ng-template> +</a> \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.scss b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.spec.ts b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9989dfd640c45a6193ce3d2a696aaa490e38b260 --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.spec.ts @@ -0,0 +1,33 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatIcon } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; +import { MyVorgaengeNavigationItemComponent } from './my-vorgaenge-navigation-item.component'; + +describe('MyVorgaengeNavigationItemComponent', () => { + let component: MyVorgaengeNavigationItemComponent; + let fixture: ComponentFixture<MyVorgaengeNavigationItemComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + MatIcon, + MyVorgaengeNavigationItemComponent + ], + imports: [ + RouterTestingModule, + MatTooltipModule, + ] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MyVorgaengeNavigationItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.ts b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..12e8f04ffd58bdf4aac80926a659d9e9ca22bda3 --- /dev/null +++ b/goofy-client/libs/navigation/src/lib/navigation/my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'goofy-client-my-vorgaenge-navigation-item', + templateUrl: './my-vorgaenge-navigation-item.component.html', + styleUrls: ['./my-vorgaenge-navigation-item.component.scss'] +}) +export class MyVorgaengeNavigationItemComponent { + + @Input() navigationCollapse: boolean; + + readonly label: string = 'Meine Vorgänge'; +} \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.html b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.html index 62a54310ce74a4fe3a29b8df9ce55a075cf4e0c9..be217da32e2330dbc2ec77f51a7e8a737bdd5f89 100644 --- a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.html +++ b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.html @@ -1,16 +1,16 @@ -<nav [ngClass]="{ small: navigationCollapse$ | async }"> - <ul> - <li> - <a #home="routerLinkActive" routerLink="/" - routerLinkActive="active" - [routerLinkActiveOptions]="{ exact: true }" - [matTooltip]="(navigationCollapse$ | async)? 'Alle Vorgänge': null" - matRipple - matRippleColor="rgba(13,71,161, 0.08)"> - <mat-icon>apps</mat-icon> - <h1 *ngIf="home.isActive; else homeInactive">Alle Vorgänge</h1> - <ng-template #homeInactive><span>Alle Vorgänge</span></ng-template> - </a> - </li> - </ul> -</nav> +<ng-container *ngIf="apiRootStateResource$ | async as apiRootStateResource"> + <nav [ngClass]="{ small: navigationCollapse$ | async }"> + <ul> + <li> + <goofy-client-all-vorgaenge-navigation-item *ngIf="apiRootStateResource.resource | hasLink: apiRootLinkRel.VORGAENGE" data-test-id="all-vorgaenge-navigation-item" + [navigationCollapse]="navigationCollapse$ | async"> + </goofy-client-all-vorgaenge-navigation-item> + </li> + <li> + <goofy-client-my-vorgaenge-navigation-item *ngIf="apiRootStateResource.resource | hasLink: apiRootLinkRel.MY_VORGAENGE" data-test-id="my-vorgaenge-navigation-item" + [navigationCollapse]="navigationCollapse$ | async"> + </goofy-client-my-vorgaenge-navigation-item> + </li> + </ul> + </nav> +</ng-container> \ No newline at end of file diff --git a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.scss b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.scss index 9eddb9b846d0238566a4ed8cc48f205487bdb358..063bd9ed1b2e73bdb0bbf1128152a4eff0290b39 100644 --- a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.scss +++ b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.scss @@ -53,10 +53,6 @@ ul { outline: 0; white-space: nowrap; - &:focus-within { - //box-shadow: inset 0 0 0 2px mat-color($primaryPalette, darker); - } - h1, span { margin: 0 0 0 20px; diff --git a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.spec.ts b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.spec.ts index 105807b506f350d3d60c24b9695dbb8ef7055de2..48ba82063a5750b34d9012d7e8ace99196d4a429 100644 --- a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.spec.ts +++ b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.spec.ts @@ -1,11 +1,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatIcon } from '@angular/material/icon'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ApiRootLinkRel, ApiRootService } from '@goofy-client/api-root-shared'; import { AppService } from '@goofy-client/app-shared'; -import { mock } from '@goofy-client/test-utils'; -import { BehaviorSubject } from 'rxjs'; +import { createStateResource, HasLinkPipe } from '@goofy-client/tech-shared'; +import { getElementFromFixture, mock } from '@goofy-client/test-utils'; +import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { BehaviorSubject, of } from 'rxjs'; +import { AllVorgaengeNavigationItemComponent } from './all-vorgaenge-navigation-item/all-vorgaenge-navigation-item.component'; +import { MyVorgaengeNavigationItemComponent } from './my-vorgaenge-navigation-item/my-vorgaenge-navigation-item.component'; import { NavigationComponent } from './navigation.component'; -import { MatTooltipModule } from '@angular/material/tooltip'; describe('NavigationComponent', () => { let component: NavigationComponent; @@ -13,22 +17,28 @@ describe('NavigationComponent', () => { const navigationCollapseSubj: BehaviorSubject<boolean> = new BehaviorSubject(false); const appService = { ...mock(AppService), getNavigationCollapse: () => navigationCollapseSubj }; + const apiRootService = { ...mock(ApiRootService), getApiRoot: () => of(createStateResource(createApiRootResource())) }; + + const allVorgaenge: string = getDataTestIdOf('all-vorgaenge-navigation-item'); + const myVorgaenge: string = getDataTestIdOf('my-vorgaenge-navigation-item'); beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule, - MatTooltipModule - ], providers: [ { provide: AppService, useValue: appService + }, + { + provide: ApiRootService, + useValue: apiRootService } ], declarations: [ - MatIcon, - NavigationComponent + NavigationComponent, + HasLinkPipe, + MockComponent(MyVorgaengeNavigationItemComponent), + MockComponent(AllVorgaengeNavigationItemComponent) ] }).compileComponents(); }); @@ -42,4 +52,46 @@ describe('NavigationComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + describe('all vorgaenge item', () => { + + it('should show on existing link', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.VORGAENGE]))); + + fixture.detectChanges(); + const element = getElementFromFixture(fixture, allVorgaenge); + + expect(element).toBeInstanceOf(HTMLElement); + }) + + it('should hide on missing link', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + + fixture.detectChanges(); + const element = getElementFromFixture(fixture, allVorgaenge); + + expect(element).not.toBeInstanceOf(HTMLElement); + }) + }) + + describe('my vorgaenge item', () => { + + it('should show on existing link', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource([ApiRootLinkRel.MY_VORGAENGE]))); + + fixture.detectChanges(); + const element = getElementFromFixture(fixture, myVorgaenge); + + expect(element).toBeInstanceOf(HTMLElement); + }) + + it('should hide on missing link', () => { + component.apiRootStateResource$ = of(createStateResource(createApiRootResource())); + + fixture.detectChanges(); + const element = getElementFromFixture(fixture, myVorgaenge); + + expect(element).not.toBeInstanceOf(HTMLElement); + }) + }) }); diff --git a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.ts b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.ts index b571a2502e2b9495090ad140d1dd411500e2a30a..113f5daf97637fec52b677188eebafe4eeb782ad 100644 --- a/goofy-client/libs/navigation/src/lib/navigation/navigation.component.ts +++ b/goofy-client/libs/navigation/src/lib/navigation/navigation.component.ts @@ -1,7 +1,10 @@ import { Component } from '@angular/core'; +import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@goofy-client/api-root-shared'; import { AppService } from '@goofy-client/app-shared'; +import { StateResource } from '@goofy-client/tech-shared'; import { Observable } from 'rxjs'; +//TODO Componente an die Containerstruktur anpassen @Component({ selector: 'goofy-client-navigation', templateUrl: './navigation.component.html', @@ -10,8 +13,12 @@ import { Observable } from 'rxjs'; export class NavigationComponent { navigationCollapse$: Observable<boolean>; + apiRootStateResource$: Observable<StateResource<ApiRootResource>>; - constructor(private appService: AppService) { + readonly apiRootLinkRel = ApiRootLinkRel; + + constructor(private appService: AppService, private apiRootService: ApiRootService) { this.navigationCollapse$ = this.appService.getNavigationCollapse(); + this.apiRootStateResource$ = this.apiRootService.getApiRoot(); } } \ No newline at end of file diff --git a/goofy-client/libs/tech-shared/src/lib/service/navigation.service.spec.ts b/goofy-client/libs/tech-shared/src/lib/service/navigation.service.spec.ts index 48699c8ca01de108c2fcd44b6b1df945cc522dd7..fa769e183137f9746b45977da9cf6aee152511fc 100644 --- a/goofy-client/libs/tech-shared/src/lib/service/navigation.service.spec.ts +++ b/goofy-client/libs/tech-shared/src/lib/service/navigation.service.spec.ts @@ -1,20 +1,104 @@ import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { mock, useFromMock } from '@goofy-client/test-utils'; +import { ResourceUri } from '@ngxp/rest'; +import { of } from 'rxjs'; +import { EMPTY_STRING } from '../tech.util'; import { NavigationService } from './navigation.service'; describe('NavigationService', () => { let service: NavigationService; + const router = { + ...mock(Router), + url: EMPTY_STRING, + events: of({}) + }; + + const activatedRoute = { + ...mock(ActivatedRoute), + root: { + firstChild: { + params: of(<Params>{}) + } + }, + }; + beforeEach(() => { TestBed.configureTestingModule({ - imports: [ - RouterTestingModule + providers: [ + { + provide: Router, + useValue: router + }, + { + provide: ActivatedRoute, + useValue: activatedRoute + } ] }); - service = TestBed.inject(NavigationService); + service = new NavigationService(useFromMock(<any>router), useFromMock(<any>activatedRoute)); }); it('should be created', () => { expect(service).toBeTruthy(); }); + + describe('isMeineVorgaengeNavigation', () => { + + it('should return true', () => { + router.url = '/meineVorgaenge'; + + const isMeineVorgaenge: boolean = service.isMeineVorgaengeNavigation(); + + expect(isMeineVorgaenge).toBeTruthy(); + }) + + it('should return false', () => { + router.url = '/'; + + const isMeineVorgaenge: boolean = service.isMeineVorgaengeNavigation(); + + expect(isMeineVorgaenge).toBeFalsy(); + }) + }) + + describe('navigation', () => { + + describe('to vorganglist', () => { + + it('should navigate to vorganglist on navigation my vorgaenge', () => { + router.url = '/meineVorgaenge'; + + service.navigateToVorgangList(); + + expect(router.navigate).toHaveBeenCalledWith(['/meineVorgaenge/']); + }) + + it('should navigate to vorganglist on navigation all vorgaenge', () => { + router.url = '/'; + + service.navigateToVorgangList(); + + expect(router.navigate).toHaveBeenCalledWith(['/']); + }) + }) + + it('should navigate to vorgang with given uri', () => { + const linkRel: ResourceUri = 'dummyUrl'; + + service.navigateToVorgang(linkRel); + + expect(router.navigate).toHaveBeenCalledWith(['/', 'vorgang', linkRel]); + }) + + it('should navigate with search string', () => { + router.url = '/'; + const searchString: string = 'testSearchString'; + + service.search(searchString); + + expect(router.navigate).toHaveBeenCalledWith(['/search/', encodeURI(searchString)]); + }) + }) }); diff --git a/goofy-client/libs/tech-shared/src/lib/service/navigation.service.ts b/goofy-client/libs/tech-shared/src/lib/service/navigation.service.ts index 5e4b0369254fdd38ae3b991c4e2bf08d2bc36509..f25af895ad1a8fc38d93a39e3a9333642d52d21a 100644 --- a/goofy-client/libs/tech-shared/src/lib/service/navigation.service.ts +++ b/goofy-client/libs/tech-shared/src/lib/service/navigation.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Params, PRIMARY_OUTLET, Router } from '@angular/router'; +import { ResourceUri } from '@ngxp/rest'; import { isNil, isUndefined } from 'lodash-es'; import { BehaviorSubject, Subscription } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; @@ -13,7 +14,7 @@ export class NavigationService { private routeSubscription: Subscription; - constructor(private router: Router, private route: ActivatedRoute) { + constructor(private router: Router, private activatedRoute: ActivatedRoute) { this.init(); } @@ -24,13 +25,13 @@ export class NavigationService { } private prefillRouterData(): void { - this.getLastFirstChild(this.route.root).params.subscribe((params: Params) => this.routeParameter$.next(params)); + this.getLastFirstChild(this.activatedRoute.root).params.subscribe((params: Params) => this.routeParameter$.next(params)); } private subscribeToRouterEvents(): void { this.router.events.pipe( filter(event => event instanceof NavigationEnd), - map(() => this.getLastFirstChild(this.route)), + map(() => this.getLastFirstChild(this.activatedRoute)), filter(route => route.outlet === PRIMARY_OUTLET)) .subscribe(route => this.updateRouteParameter(route)); } @@ -99,4 +100,25 @@ export class NavigationService { public navigateRelativeTo(relativePath: string, relativeTo: ActivatedRoute): void { this.router.navigate([relativePath], { relativeTo }); } + + public search(searchString: string): void { + this.router.navigate([this.getRootPath() + 'search/', encodeURI(searchString)]);//TODO: encoding/decoding abstimmen + } + + public navigateToVorgangList(): void { + this.router.navigate([this.getRootPath()]); + } + + private getRootPath(): string { + return this.isMeineVorgaengeNavigation() ? '/meineVorgaenge/' : '/'; + } + + public isMeineVorgaengeNavigation(): boolean { + const url: string = this.router.url; + return url.includes('meineVorgaenge'); + } + + public navigateToVorgang(linkUri: ResourceUri): void { + this.router.navigate(['/', 'vorgang', linkUri]); + } } \ No newline at end of file diff --git a/goofy-client/libs/tech-shared/src/lib/tech.util.spec.ts b/goofy-client/libs/tech-shared/src/lib/tech.util.spec.ts index 636332c49fa69d61470b0cbaa8bbb2fc52481b9d..f62d5cef98372722a4ffa61f91ba86e14e8f870b 100644 --- a/goofy-client/libs/tech-shared/src/lib/tech.util.spec.ts +++ b/goofy-client/libs/tech-shared/src/lib/tech.util.spec.ts @@ -1,6 +1,5 @@ import * as faker from 'faker'; -import { isNotEmpty, isNotNil } from './tech.util'; -import { getFirstLetter, replaceAllWhitespaces, replacePlaceholder, replacePlaceholders } from './tech.util'; +import { EMPTY_STRING, getFirstLetter, hasMinLength, isNotEmpty, isNotNil, replaceAllWhitespaces, replacePlaceholder, replacePlaceholders } from './tech.util'; describe('TechUtil', () => { @@ -134,4 +133,47 @@ describe('TechUtil', () => { expect(result).toBeFalsy(); }); }); + + describe('hasMinLength with length of 3', () => { + + const minValue: number = 3; + const SEARCH_STRING = 'i search for...'; + + it('should return false on null value', () => { + const hasLength = hasMinLength(null, minValue); + + expect(hasLength).toBeFalsy(); + }) + + it('should return false on undefined value', () => { + const hasLength = hasMinLength(undefined, minValue); + + expect(hasLength).toBeFalsy(); + }) + + it('should return false on empty string => ""', () => { + const hasLength = hasMinLength(EMPTY_STRING, minValue); + + expect(hasLength).toBeFalsy(); + }) + + it('should return false on length less than 2', () => { + const hasLength = hasMinLength(EMPTY_STRING, minValue); + + expect(hasLength).toBeFalsy(); + }) + + it('should return true on length equals than 3 ', () => { + const hasLength = hasMinLength('huh', minValue); + + expect(hasLength).toBeTruthy(); + }) + + it('should return true on length greater than 3 ', () => { + const hasLength = hasMinLength(SEARCH_STRING, minValue); + + expect(hasLength).toBeTruthy(); + }) + }) + }) \ No newline at end of file diff --git a/goofy-client/libs/tech-shared/src/lib/tech.util.ts b/goofy-client/libs/tech-shared/src/lib/tech.util.ts index 8db28007f704685ae347bcbbd2a2ecfc9891e531..bb77b80f25ae72776ccafd95fd463de5529e8d79 100644 --- a/goofy-client/libs/tech-shared/src/lib/tech.util.ts +++ b/goofy-client/libs/tech-shared/src/lib/tech.util.ts @@ -47,4 +47,8 @@ export function isNotEmpty(value: any): boolean { export function isNotNil(value: any): boolean { return !isNil(value); +} + +export function hasMinLength(value: any, length: number): boolean { + return !isNil(value) && value.length >= length; } \ No newline at end of file diff --git a/goofy-client/libs/tech-shared/test/data-test.ts b/goofy-client/libs/tech-shared/test/data-test.ts index 402cf79d00a1146b33f32c0c5b11cdeb5f91b49d..56194fcdedb2d29ad80e42a4942def117aee6de7 100644 --- a/goofy-client/libs/tech-shared/test/data-test.ts +++ b/goofy-client/libs/tech-shared/test/data-test.ts @@ -1,11 +1,7 @@ -//TODO Name/Implementierung abstimmen -export class DataTest { +export function getDataTestClassOf(value: string): string { + return `[data-test-class="${value}"]`; +} - public static classOf(value: string): string { - return `[data-test-class="${value}"]`; - } - - public static idOf(value: string): string { - return `[data-test-id="${value}"]`; - } +export function getDataTestIdOf(value: string): string { + return `[data-test-id="${value}"]`; } \ No newline at end of file diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.html b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.html index 572a54ca0274c71c0189d30edd226ea31b561fc6..045f3dec044edece9b1d08dadb8187c239daa5ff 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.html +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.html @@ -1,5 +1,5 @@ <ng-container *ngIf="apiRoot$ | async as apiRootStateResource"> - <goofy-client-vorgang-search *ngIf="apiRootStateResource.resource | hasLink: linkRel.SEARCH" data-test-id="vorgang-search" + <goofy-client-vorgang-search *ngIf="(apiRootStateResource.resource | hasLink: apiRootLinkRel.SEARCH) || (apiRootStateResource.resource | hasLink: apiRootLinkRel.SEARCH_MY_VORGAENGE)" data-test-id="vorgang-search" [vorgangSearchPreviewList]="vorgangSearchPreviewList$ | async"> </goofy-client-vorgang-search> </ng-container> \ No newline at end of file diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.spec.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.spec.ts index aa9b4676a8b99e2dd1315c99ec920f3a6a4f3ceb..61d53954972f3816a955d68ffe5109efcdeafb50 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.spec.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.spec.ts @@ -4,6 +4,7 @@ import { createStateResource, HasLinkPipe, StateResource } from '@goofy-client/t import { mock } from '@goofy-client/test-utils'; import { VorgangListService } from '@goofy-client/vorgang-shared'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { BehaviorSubject } from 'rxjs'; import { VorgangSearchContainerComponent } from './vorgang-search-container.component'; @@ -18,7 +19,7 @@ describe('VorgangSearchContainerComponent', () => { const vorgangListService = mock(VorgangListService); - const vorgangSearch: string = '[data-test-id="vorgang-search"]'; + const vorgangSearch: string = getDataTestIdOf('vorgang-search'); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -52,7 +53,7 @@ describe('VorgangSearchContainerComponent', () => { describe('vorgang-search', () => { - it('should hide on no link', () => { + it('should hide on no link exists', () => { apiRootSubj.next(createStateResource(createApiRootResource())); fixture.detectChanges(); @@ -61,13 +62,14 @@ describe('VorgangSearchContainerComponent', () => { expect(element).not.toBeInstanceOf(HTMLElement); }) - it('should show if link exists', () => { - apiRootSubj.next(createStateResource(createApiRootResource([ApiRootLinkRel.SEARCH]))); - fixture.detectChanges(); + it.each([ApiRootLinkRel.SEARCH, ApiRootLinkRel.SEARCH_MY_VORGAENGE]) + ('should show on link "%s"', (linkRel: string) => { + apiRootSubj.next(createStateResource(createApiRootResource([linkRel]))); + fixture.detectChanges(); - const element = fixture.nativeElement.querySelector(vorgangSearch); + const element = fixture.nativeElement.querySelector(vorgangSearch); - expect(element).toBeInstanceOf(HTMLElement); - }) + expect(element).toBeInstanceOf(HTMLElement); + }); }) -}); +}); \ No newline at end of file diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.ts index f36691e8d78096981ac22a02bae02fc06be750d2..206365091dc6720aa654d541db9a13d3f20bfda0 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search-container.component.ts @@ -14,7 +14,7 @@ export class VorgangSearchContainerComponent { public apiRoot$: Observable<StateResource<ApiRootResource>> = of(createEmptyStateResource<ApiRootResource>()); public vorgangSearchPreviewList$: Observable<StateResource<VorgangListResource>> = of(createEmptyStateResource<VorgangListResource>()); - readonly linkRel = ApiRootLinkRel; + readonly apiRootLinkRel = ApiRootLinkRel; constructor(private apiRoot: ApiRootService, private vorgangListService: VorgangListService) { this.apiRoot$ = this.apiRoot.getApiRoot(); diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search-clear-button/vorgang-search-clear-button.component.spec.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search-clear-button/vorgang-search-clear-button.component.spec.ts index 04c41468dd5bf85237d9c77697c27f74af5524f7..1bc0d12135b4f1cb8a82d44a7b4a67a5f9a75d04 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search-clear-button/vorgang-search-clear-button.component.spec.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search-clear-button/vorgang-search-clear-button.component.spec.ts @@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms'; import { MatIcon } from '@angular/material/icon'; import { EMPTY_STRING } from '@goofy-client/tech-shared'; import { mock } from '@goofy-client/test-utils'; -import { DataTest } from 'libs/tech-shared/test/data-test'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { VorgangSearchFormService } from '../vorgang-search.formservice'; import { VorgangSearchClearButtonComponent } from './vorgang-search-clear-button.component'; @@ -11,7 +11,7 @@ describe('VorgangSearchClearButtonComponent', () => { let component: VorgangSearchClearButtonComponent; let fixture: ComponentFixture<VorgangSearchClearButtonComponent>; - const clearButton: string = DataTest.idOf('clear-button'); + const clearButton: string = getDataTestIdOf('clear-button'); const searchFormService = mock(VorgangSearchFormService); diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.spec.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.spec.ts index a0b131ee980e6759c0ca3a1d258aadd8a3de7b99..9a1d63b49141da1bf91fabcfdf4723f35b4c55c7 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.spec.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.component.spec.ts @@ -10,7 +10,7 @@ import { ConvertForDataTestPipe, createEmptyStateResource, createStateResource, import { mock } from '@goofy-client/test-utils'; import { SpinnerComponent } from '@goofy-client/ui'; import { SearchInfo, VorgangHeaderLinkRel, VorgangListService } from '@goofy-client/vorgang-shared'; -import { DataTest } from 'libs/tech-shared/test/data-test'; +import { getDataTestClassOf } from 'libs/tech-shared/test/data-test'; import { createVorgangListResource } from 'libs/vorgang-shared/test/vorgang'; import { MockComponent } from 'ng-mocks'; import { BehaviorSubject, Subject } from 'rxjs'; @@ -28,7 +28,7 @@ describe('VorgangSearchComponent', () => { const searchInfoSubj: Subject<SearchInfo> = new BehaviorSubject({ searchString: EMPTY_STRING, changedAfterSearchDone: false }); const listService = { ...mock(VorgangListService), getSearchInfo: () => searchInfoSubj }; - const searchPreviewOption: string = DataTest.classOf('search-preview-option'); + const searchPreviewOption: string = getDataTestClassOf('search-preview-option'); beforeEach(async () => { await TestBed.configureTestingModule({ diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.spec.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.spec.ts index 4b869767309b4b6015fa134cb5c66459db7ae1e7..fda7ebaf389198e94bdbed84981154a3f7b48904 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.spec.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.spec.ts @@ -1,31 +1,27 @@ import { FormBuilder, FormControl } from '@angular/forms'; -import { Params, Router } from '@angular/router'; +import { Params } from '@angular/router'; import { EMPTY_STRING, NavigationService } from '@goofy-client/tech-shared'; import { Mock, mock, useFromMock } from '@goofy-client/test-utils'; import { VorgangHeaderLinkRel, VorgangListService, VorgangResource } from '@goofy-client/vorgang-shared'; import { getUrl, ResourceUri } from '@ngxp/rest'; +import * as faker from 'faker'; import { createVorgangResource } from 'libs/vorgang-shared/test/vorgang'; import { of } from 'rxjs'; import { VorgangSearchFormService } from './vorgang-search.formservice'; -import * as faker from 'faker'; describe('VorgangSearchFormService', () => { let formService: VorgangSearchFormService;; let vorgangListService: Mock<VorgangListService>; - let router: Mock<Router>; let navigationService: Mock<NavigationService>; const SEARCH_STRING = 'i search for...'; - const MIN_LENGTH: number = 3; - beforeEach(() => { vorgangListService = mock(VorgangListService); vorgangListService.getSearchInfo.mockReturnValue(of({})); - router = mock(Router); navigationService = mock(NavigationService); - formService = new VorgangSearchFormService(useFromMock(vorgangListService), new FormBuilder(), useFromMock(router), useFromMock(navigationService)); + formService = new VorgangSearchFormService(useFromMock(vorgangListService), new FormBuilder(), useFromMock(navigationService)); }) it('should create', () => { @@ -43,8 +39,7 @@ describe('VorgangSearchFormService', () => { expect(formService.unlockSearch).toHaveBeenCalled(); }) - it('should do search for preview list', () => { - formService.hasMinLength = jest.fn().mockReturnValue(true); + it('should do search for preview list on at least 3 character', () => { formService.searchLocked = false; formService.handleValueChanges(SEARCH_STRING); @@ -52,43 +47,15 @@ describe('VorgangSearchFormService', () => { expect(vorgangListService.searchForPreview).toHaveBeenCalled(); }) - it('should clear preview list', () => { - formService.hasMinLength = jest.fn().mockReturnValue(false); + it('should clear preview list on 3 or less character', () => { formService.searchLocked = false; - formService.handleValueChanges(SEARCH_STRING); + formService.handleValueChanges('AH'); expect(vorgangListService.clearVorgangSearchPreviewList).toHaveBeenCalled(); }) }) - describe('hasMinLength with length of 3', () => { - - it('should return false on null value', () => { - const hasMinLength = formService.hasMinLength(null, MIN_LENGTH); - - expect(hasMinLength).toBeFalsy(); - }) - - it('should return false on undefined value', () => { - const hasMinLength = formService.hasMinLength(undefined, MIN_LENGTH); - - expect(hasMinLength).toBeFalsy(); - }) - - it('should return false on empty string => "" ', () => { - const hasMinLength = formService.hasMinLength(EMPTY_STRING, MIN_LENGTH); - - expect(hasMinLength).toBeFalsy(); - }) - - it('should return true on length greater/equals than 3 ', () => { - const hasMinLength = formService.hasMinLength(SEARCH_STRING, MIN_LENGTH); - - expect(hasMinLength).toBeTruthy(); - }) - }) - describe('submitForPreviewList', () => { it('should call submit for preview list', () => { @@ -181,16 +148,12 @@ describe('VorgangSearchFormService', () => { describe('navigateToVorgangListOnSearch', () => { - beforeEach(() => { - formService.navigateToVorgangList = jest.fn().mockImplementation(); - }); - it('should call navigateToVorgangList() when at search page', () => { const params: Params = { search: faker.random.word() } formService.navigateToVorgangListOnSearch(params); - expect(formService.navigateToVorgangList).toHaveBeenCalled(); + expect(navigationService.navigateToVorgangList).toHaveBeenCalled(); }) it('should not call navigateToVorgangList() when at non-search page', () => { @@ -198,7 +161,7 @@ describe('VorgangSearchFormService', () => { formService.navigateToVorgangListOnSearch(params); - expect(formService.navigateToVorgangList).not.toHaveBeenCalled(); + expect(navigationService.navigateToVorgangList).not.toHaveBeenCalled(); }) }) @@ -209,7 +172,7 @@ describe('VorgangSearchFormService', () => { formService.submit(); - expect(router.navigateByUrl).toHaveBeenCalledWith('/'); + expect(navigationService.navigateToVorgangList).toHaveBeenCalled(); }) it('should navigate to itself with search param', () => { @@ -218,7 +181,7 @@ describe('VorgangSearchFormService', () => { formService.submit(); - expect(router.navigate).toHaveBeenCalledWith([formService.SEARCH_FIELD + '/', encodeURI(SEARCH_STRING)]); + expect(navigationService.search).toHaveBeenCalledWith(SEARCH_STRING); }) }) @@ -259,7 +222,7 @@ describe('VorgangSearchFormService', () => { formService.submitByPreviewList(vorgang); - expect(router.navigate).toHaveBeenCalledWith(['/', 'vorgang', btoa(uri).replace('/', '_')]); + expect(navigationService.navigateToVorgang).toHaveBeenCalledWith(btoa(uri).replace('/', '_')); }) }) diff --git a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.ts b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.ts index 01460a41b7282bec559b07d786fb77e0bb8a94f3..d2c93a588b0c00f83a7e98e851301b499aa0b22e 100644 --- a/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.ts +++ b/goofy-client/libs/vorgang-shared-ui/src/lib/vorgang-search-container/vorgang-search/vorgang-search.formservice.ts @@ -1,9 +1,9 @@ import { Injectable, OnDestroy } from '@angular/core'; import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; -import { Params, Router } from '@angular/router'; -import { EMPTY_STRING, isNotEmpty, isNotNil, NavigationService, toResourceUri } from '@goofy-client/tech-shared'; +import { Params } from '@angular/router'; +import { EMPTY_STRING, hasMinLength, isNotEmpty, isNotNil, NavigationService, toResourceUri } from '@goofy-client/tech-shared'; import { SearchInfo, VorgangHeaderLinkRel, VorgangListService, VorgangResource } from '@goofy-client/vorgang-shared'; -import { isEmpty, isNil } from 'lodash-es'; +import { isEmpty } from 'lodash-es'; import { Subscription } from 'rxjs'; import { debounceTime, first } from 'rxjs/operators'; @@ -23,8 +23,7 @@ export class VorgangSearchFormService implements OnDestroy { constructor( private service: VorgangListService, private formBuilder: FormBuilder, - private router: Router, - private navigationService: NavigationService + private navigationService: NavigationService, ) { this.init(); } @@ -51,7 +50,7 @@ export class VorgangSearchFormService implements OnDestroy { this.unlockSearch(); return; } - if (this.hasMinLength(searchString, this.PREVIEW_SEARCH_STRING_MIN_LENGTH)) { + if (hasMinLength(searchString, this.PREVIEW_SEARCH_STRING_MIN_LENGTH)) { this.searchForPreviewList(searchString); } else { this.clearVorgangSearchPreviewList(); @@ -62,12 +61,7 @@ export class VorgangSearchFormService implements OnDestroy { this.searchLocked = false; } - //TODO durch util Methode ersetzen oder in tech-util umziehen - hasMinLength(value: any, length: number): boolean { - return !isNil(value) && value.length >= length; - } - - searchForPreviewList(searchInput: string) { + searchForPreviewList(searchInput: string): void { this.service.searchForPreview(searchInput); } @@ -97,9 +91,9 @@ export class VorgangSearchFormService implements OnDestroy { submit(): void { if (this.hasSearchString()) { - this.router.navigate([this.SEARCH_FIELD + '/', encodeURI(this.getValue())]);//TODO: encoding/decoding abstimmen + this.navigationService.search(this.getValue()); } else { - this.navigateToVorgangList(); + this.navigationService.navigateToVorgangList(); } } @@ -123,16 +117,12 @@ export class VorgangSearchFormService implements OnDestroy { } private navigateToVorgang(resource: VorgangResource): void { - this.router.navigate(['/', 'vorgang', toResourceUri(resource, VorgangHeaderLinkRel.VORGANG_WITH_EINGANG)]); - } - - navigateToVorgangList(): void { - this.router.navigateByUrl('/'); + this.navigationService.navigateToVorgang(toResourceUri(resource, VorgangHeaderLinkRel.VORGANG_WITH_EINGANG)); } navigateToVorgangListOnSearch(params: Params) { - if(NavigationService.isSearch(params)) { - this.navigateToVorgangList(); + if (NavigationService.isSearch(params)) { + this.navigationService.navigateToVorgangList(); } } diff --git a/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.spec.ts b/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.spec.ts index 95e19b8f5d7d4bb796ef798d3294faac8c613ee4..d369e436188193f97817af330f1755e27ec420f0 100644 --- a/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.spec.ts +++ b/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.spec.ts @@ -1,4 +1,4 @@ -import { ApiRootResource } from '@goofy-client/api-root-shared'; +import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared'; import { createEmptyStateResource, createStateResource, EMPTY_STRING, NavigationService } from '@goofy-client/tech-shared'; import { Mock, mock, useFromMock } from '@goofy-client/test-utils'; import { getEmbeddedResource } from '@ngxp/rest'; @@ -223,6 +223,18 @@ describe('VorgangListService', () => { describe('onNavigation', () => { + describe('to meineVorgaenge', () => { + + it('should set vorgang list to reload', () => { + service.setVorgangListToReload = jest.fn(); + navigationService.isMeineVorgaengeNavigation.mockReturnValue(true); + + service.onNavigation({}); + + expect(service.setVorgangListToReload).toHaveBeenCalled(); + }) + }) + describe('to vorgang list page', () => { beforeEach(() => { @@ -241,7 +253,7 @@ describe('VorgangListService', () => { const searchString: string = 'X'; beforeEach(() => { - service.clearVorgangList = jest.fn(); + service.setVorgangListToReload = jest.fn(); service.isVorgangListLoaded = jest.fn(); (<any>service).isVorgangListLoaded.mockReturnValue(true); service.setSearchInfo = jest.fn(); @@ -252,7 +264,7 @@ describe('VorgangListService', () => { it('should clear vorgang list', () => { service.onNavigation({ search: searchString }); - expect(service.clearVorgangList).toHaveBeenCalled(); + expect(service.setVorgangListToReload).toHaveBeenCalled(); }) it('should load vorgang list', () => { @@ -315,22 +327,50 @@ describe('VorgangListService', () => { const apiRootResource: ApiRootResource = createApiRootResource(); - it('should call repository searchVorgaengeBy on existing search by criteria', () => { - const searchValue: string = 'searchParam'; - repository.searchVorgaengeBy.mockReturnValue(of(vorgangListResource)); - service.searchInfo$.next(<SearchInfo>{ searchString: searchValue, changedAfterSearchDone: false }); + describe('on existing search string', () => { - service.loadVorgangList(apiRootResource); + const searchString: string = 'searchParam'; + + beforeEach(() => { + repository.searchVorgaengeBy.mockReturnValue(of(vorgangListResource)); + service.searchInfo$.next(<SearchInfo>{ searchString, changedAfterSearchDone: false }); + }) + it('should call repository loadVorgangList with "' + ApiRootLinkRel.SEARCH_MY_VORGAENGE + '" link', () => { + navigationService.isMeineVorgaengeNavigation.mockReturnValue(true); + + service.loadVorgangList(apiRootResource); + + expect(repository.searchVorgaengeBy).toHaveBeenCalledWith(apiRootResource, searchString, ApiRootLinkRel.SEARCH_MY_VORGAENGE); + }) - expect(repository.searchVorgaengeBy).toHaveBeenCalledWith(apiRootResource, searchValue); + it('should call repository searchVorgaengeBy with "' + ApiRootLinkRel.SEARCH + '" link', () => { + navigationService.isMeineVorgaengeNavigation.mockReturnValue(false); + + service.loadVorgangList(apiRootResource); + + expect(repository.searchVorgaengeBy).toHaveBeenCalledWith(apiRootResource, searchString, ApiRootLinkRel.SEARCH); + }) }) - it('should call repository loadVorgangList on null search criteria', () => { - repository.loadVorgangList.mockReturnValue(of(vorgangListResource)); + describe('on empty search string', () => { - service.loadVorgangList(apiRootResource); + it('should call repository loadMyVorgaengeList on "myVorgange" navigation', () => { + navigationService.isMeineVorgaengeNavigation.mockReturnValue(true); + repository.loadMyVorgaengeList.mockReturnValue(of(vorgangListResource)); - expect(repository.loadVorgangList).toHaveBeenCalledWith(apiRootResource); + service.loadVorgangList(apiRootResource); + + expect(repository.loadMyVorgaengeList).toHaveBeenCalledWith(apiRootResource); + }) + + it('should call repository loadVorgangList on "allVorgange" navigation', () => { + navigationService.isMeineVorgaengeNavigation.mockReturnValue(false); + repository.loadVorgangList.mockReturnValue(of(vorgangListResource)); + + service.loadVorgangList(apiRootResource); + + expect(repository.loadVorgangList).toHaveBeenCalledWith(apiRootResource); + }) }) }) @@ -362,9 +402,11 @@ describe('VorgangListService', () => { describe('loadVorgangSearchPreviewList', () => { it('should call repository', () => { + navigationService.isMeineVorgaengeNavigation.mockReturnValue(false); + service.loadVorgangSearchPreviewList(apiRootResource, SEARCH_STRING); - expect(repository.searchVorgaengeBy).toHaveBeenCalledWith(apiRootResource, SEARCH_STRING, service.SEARCH_PREVIEW_LIMIT); + expect(repository.searchVorgaengeBy).toHaveBeenCalledWith(apiRootResource, SEARCH_STRING, ApiRootLinkRel.SEARCH, service.SEARCH_PREVIEW_LIMIT); }) }) }) diff --git a/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.ts b/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.ts index 7d29016464a48743c61a464b0c4800453aad80ff..ddf5371d45eaf9f2579cb1295183c4ba7b956431 100644 --- a/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.ts +++ b/goofy-client/libs/vorgang-shared/src/lib/vorgang-list.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core'; import { Params } from '@angular/router'; -import { ApiRootResource } from '@goofy-client/api-root-shared'; +import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared'; import { createEmptyStateResource, createStateResource, doIfLoadingRequired, EMPTY_STRING, isNotNil, NavigationService, StateResource } from '@goofy-client/tech-shared'; import { hasLink } from '@ngxp/rest'; import { ApiRootService } from 'libs/api-root-shared/src/lib/api-root.service'; -import { isEmpty, isNil } from 'lodash-es'; -import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { isEmpty } from 'lodash-es'; +import { BehaviorSubject, Observable } from 'rxjs'; import { filter, first, mergeMap } from 'rxjs/operators'; import { VorgangListLinkRel } from './vorgang.linkrel'; import { SearchInfo, VorgangListResource, VorgangResource } from './vorgang.model'; @@ -22,12 +22,9 @@ export class VorgangListService { readonly vorgaenge$: BehaviorSubject<VorgangResource[]> = new BehaviorSubject([]); readonly hasNextPage$: BehaviorSubject<boolean> = new BehaviorSubject(false); - readonly searchInfo$: BehaviorSubject<SearchInfo> = new BehaviorSubject({ searchString: null, changedAfterSearchDone: false }); + readonly searchInfo$: BehaviorSubject<SearchInfo> = new BehaviorSubject({ searchString: null, changedAfterSearchDone: false });//TODO auf einfachen string umstellen private readonly vorgangSearchPreviewList$: BehaviorSubject<StateResource<VorgangListResource>> = new BehaviorSubject(createEmptyStateResource()); - - private subscription: Subscription; - readonly SEARCH_PREVIEW_LIMIT: number = 7; constructor( @@ -39,11 +36,13 @@ export class VorgangListService { } private listenOnNavigation(): void { - this.unsubscribe(); - this.subscription = this.navigationService.urlChanged().subscribe(params => this.onNavigation(params)); + this.navigationService.urlChanged().subscribe(params => this.onNavigation(params)); } onNavigation(params: Params): void { + if (this.navigationService.isMeineVorgaengeNavigation()) { + this.setVorgangListToReload(); + } if (NavigationService.isVorgangListPage(params)) { this.setSearchInfo(EMPTY_STRING, false); this.reloadIfNeccessary(); @@ -62,7 +61,7 @@ export class VorgangListService { this.searchInfo$.next({ ...this.searchInfo$.value, searchString, changedAfterSearchDone }); } - reloadIfNeccessary() { + reloadIfNeccessary(): void { if (this.isVorgangListLoaded()) { this.reloadVorgangList(); } @@ -80,12 +79,12 @@ export class VorgangListService { return this.hasNextPage$; } - clearVorgangList(): void { + setVorgangListToReload(): void { this.vorgangList$.next({ ...this.vorgangList$.value, reload: true }); } public reloadVorgangList(): void { - this.clearVorgangList(); + this.setVorgangListToReload(); this.getVorgangList(); } @@ -107,16 +106,20 @@ export class VorgangListService { this.setVorgangListLoading(); if (isEmpty(this.searchInfo$.value.searchString)) { - return this.vorgangRepository.loadVorgangList(apiRootResource); + if (this.navigationService.isMeineVorgaengeNavigation()) { + return this.vorgangRepository.loadMyVorgaengeList(apiRootResource); + } else { + return this.vorgangRepository.loadVorgangList(apiRootResource); + } } - return this.vorgangRepository.searchVorgaengeBy(apiRootResource, this.searchInfo$.value.searchString); + + return this.vorgangRepository.searchVorgaengeBy(apiRootResource, this.searchInfo$.value.searchString, this.getSearchLinkRel()); } public loadNextPage(): void { if (this.isVorgangListLoading()) { return; } - this.loadNextListPage(); } @@ -127,13 +130,12 @@ export class VorgangListService { private loadNextListPage(): void { this.setVorgangListLoading(); - this.vorgangRepository.getNextVorgangListPage(this.vorgangList$.value.resource).pipe(first()) - .subscribe(vorgangList => { - if (vorgangList !== null) { - this.updateVorgangList(vorgangList); - this.addVorgangListToVorgaenge(getVorgaengeFromList(vorgangList)); - } - }); + this.vorgangRepository.getNextVorgangListPage(this.vorgangList$.value.resource).pipe(first()).subscribe(vorgangList => { + if (vorgangList !== null) { + this.updateVorgangList(vorgangList); + this.addVorgangListToVorgaenge(getVorgaengeFromList(vorgangList)); + } + }); } private setVorgangListLoading(): void { @@ -185,7 +187,11 @@ export class VorgangListService { } loadVorgangSearchPreviewList(apiRootResource: ApiRootResource, searchString: string): Observable<VorgangListResource> { - return this.vorgangRepository.searchVorgaengeBy(apiRootResource, searchString, this.SEARCH_PREVIEW_LIMIT) + return this.vorgangRepository.searchVorgaengeBy(apiRootResource, searchString, this.getSearchLinkRel(), this.SEARCH_PREVIEW_LIMIT) + } + + private getSearchLinkRel(): string { + return this.navigationService.isMeineVorgaengeNavigation() ? ApiRootLinkRel.SEARCH_MY_VORGAENGE : ApiRootLinkRel.SEARCH; } private updateVorgangSearchPreviewList(vorgangList: VorgangListResource): void { @@ -195,12 +201,4 @@ export class VorgangListService { public clearVorgangSearchPreviewList(): void { this.vorgangSearchPreviewList$.next(createEmptyStateResource()); } - - ngOnDestroy(): void { - this.unsubscribe(); - } - - private unsubscribe(): void { - if (isNotNil(this.subscription)) this.subscription.unsubscribe(); - } } \ No newline at end of file diff --git a/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.spec.ts b/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.spec.ts index b58c5e1ad63e8cae8197675a6c7f99bba1e0b806..3463654bb9f65cb99e6bb412972f22c28ec340e5 100644 --- a/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.spec.ts +++ b/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.spec.ts @@ -1,7 +1,7 @@ import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared'; import { HttpErrorHandler } from '@goofy-client/tech-shared'; import { mock, mockClass, useFromMock } from '@goofy-client/test-utils'; -import { getUrl, LinkRel, ResourceFactory } from '@ngxp/rest'; +import { getUrl, LinkRel, ResourceFactory, ResourceUri } from '@ngxp/rest'; import { cold, hot } from 'jest-marbles'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; import { TechSharedModule } from 'libs/tech-shared/src/lib/tech-shared.module'; @@ -148,13 +148,13 @@ describe('VorgangRepository', () => { }) it('should call resourceFactory', () => { - repository.searchVorgaengeBy(apiRootResource, null); + repository.searchVorgaengeBy(apiRootResource, null, ApiRootLinkRel.SEARCH); expect(resourceFactory.fromId).toHaveBeenCalled(); }) it('should call resourceWrapper ', () => { - repository.searchVorgaengeBy(apiRootResource, null); + repository.searchVorgaengeBy(apiRootResource, null, ApiRootLinkRel.SEARCH); expect(resourceWrapper.get).toHaveBeenCalled(); }) @@ -162,15 +162,15 @@ describe('VorgangRepository', () => { it('should call buildSearchByUrl', () => { const searchString: string = 'i search for...'; const limit: number = 7; - repository.searchVorgaengeBy(apiRootResource, searchString, limit); + repository.searchVorgaengeBy(apiRootResource, searchString, ApiRootLinkRel.SEARCH, limit); - expect(repository.buildSearchByUrl).toHaveBeenCalledWith(apiRootResource, searchString, limit) + expect(repository.buildSearchByUrl).toHaveBeenCalledWith(apiRootResource, searchString, ApiRootLinkRel.SEARCH, limit) }) }) describe('buildSearchByUrl', () => { - const baseUrl = 'http://localhost/vorgangs?page=0'; + const baseUrl: string = 'http://localhost/vorgangs?page=0'; it('should build search by url', () => { const _links = { @@ -178,15 +178,15 @@ describe('VorgangRepository', () => { }; const apiRootResource: ApiRootResource = { ...createApiRootResource([ApiRootLinkRel.SEARCH]), _links }; - const resultUri = repository.buildSearchByUrl(apiRootResource, 'searchParam'); + const searchUrl: ResourceUri = repository.buildSearchByUrl(apiRootResource, 'searchParam', ApiRootLinkRel.SEARCH); - expect(resultUri).toEqual(baseUrl + '&' + VorgangRepository.SEARCH_PARAM + '=searchParam'); + expect(searchUrl).toEqual(baseUrl + '&' + VorgangRepository.SEARCH_PARAM + '=searchParam'); }) it('should ignore unused, optional parameters', () => { - const res = buildLinksWithSearch(baseUrl + '{&searchBy,limit}'); + const apiRoot: ApiRootResource = buildLinksWithSearch(baseUrl + '{&searchBy,limit}'); - const searchUrl = repository.buildSearchByUrl(res, 'search'); + const searchUrl: ResourceUri = repository.buildSearchByUrl(apiRoot, 'search', ApiRootLinkRel.SEARCH); expect(searchUrl).toEqual(baseUrl + '&' + VorgangRepository.SEARCH_PARAM + '=search'); }) @@ -195,7 +195,7 @@ describe('VorgangRepository', () => { const limit = 5; const res = buildLinksWithSearch(baseUrl + '{&searchBy,limit}'); - const searchUrl = repository.buildSearchByUrl(res, 'search', limit); + const searchUrl: ResourceUri = repository.buildSearchByUrl(res, 'search', ApiRootLinkRel.SEARCH, limit); expect(searchUrl).toEqual(baseUrl + '&' + VorgangRepository.SEARCH_PARAM + '=search&limit=' + limit); }) @@ -206,4 +206,33 @@ describe('VorgangRepository', () => { return res; } }) + + describe('loadMyVorgaengeList', () => { + const vorgangListResource: VorgangListResource = createVorgangListResource(); + const apiRootResource: ApiRootResource = createApiRootResource([ApiRootLinkRel.MY_VORGAENGE]); + + beforeEach(() => { + resourceWrapper.get.mockReturnValue(hot('a', { a: vorgangListResource })); + }) + + it('should call resourceFactory with uri', () => { + repository.loadMyVorgaengeList(apiRootResource); + + const urlWithoutParameter: URL = repository.getUrlWithoutParameter(apiRootResource, ApiRootLinkRel.MY_VORGAENGE); + expect(resourceFactory.fromId).toHaveBeenCalledWith(urlWithoutParameter.href); + }) + + it('should call resourceWrapper with link', () => { + repository.loadMyVorgaengeList(apiRootResource); + + expect(resourceWrapper.get).toHaveBeenCalledWith() + }) + + it('should return result', () => { + let result = repository.loadMyVorgaengeList(apiRootResource); + + expect(result).not.toBeNull(); + expect(result).toBeObservable(cold('a', { a: vorgangListResource })); + }) + }) }) \ No newline at end of file diff --git a/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.ts b/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.ts index b31d4cb13001b667a2eda83ef3361bba3900a678..191f6b4f4566924909b61ab48f2c30528304fa84 100644 --- a/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.ts +++ b/goofy-client/libs/vorgang-shared/src/lib/vorgang.repository.ts @@ -1,8 +1,8 @@ import { Injectable } from '@angular/core'; import { ApiRootLinkRel, ApiRootResource } from '@goofy-client/api-root-shared'; import { OzgFileListResource } from '@goofy-client/ozg-file-shared'; -import { CatchHttpError } from '@goofy-client/tech-shared'; -import { getUrl, ResourceFactory, ResourceUri } from '@ngxp/rest'; +import { CatchHttpError, EMPTY_STRING } from '@goofy-client/tech-shared'; +import { getUrl, Resource, ResourceFactory, ResourceUri } from '@ngxp/rest'; import { Observable } from 'rxjs'; import { VorgangListLinkRel, VorgangWithEingangLinkRel } from './vorgang.linkrel'; import { VorgangListResource, VorgangWithEingangResource } from './vorgang.model'; @@ -25,6 +25,11 @@ export class VorgangRepository { return this.resourceFactory.from(vorgangListResource).get(VorgangListLinkRel.NEXT); } + public loadMyVorgaengeList(apiRootResource: ApiRootResource): Observable<VorgangListResource> { + const url: URL = this.getUrlWithoutParameter(apiRootResource, ApiRootLinkRel.MY_VORGAENGE); + return this.resourceFactory.fromId(url.href).get(); + } + @CatchHttpError(VorgangRepository.GET_VORGANG) public getVorgang(vorgangWithEingangUrl: ResourceUri): Observable<VorgangWithEingangResource> { return this.resourceFactory.fromId(vorgangWithEingangUrl).get(); @@ -34,15 +39,19 @@ export class VorgangRepository { return this.resourceFactory.from(vorgang).get(VorgangWithEingangLinkRel.ATTACHMENTS); } - public searchVorgaengeBy(apiRootResource: ApiRootResource, searchBy: string, limit?: number): Observable<VorgangListResource> { - return this.resourceFactory.fromId(this.buildSearchByUrl(apiRootResource, searchBy, limit)).get(); + public searchVorgaengeBy(apiRootResource: ApiRootResource, searchBy: string, linkRel: string, limit?: number): Observable<VorgangListResource> { + return this.resourceFactory.fromId(this.buildSearchByUrl(apiRootResource, searchBy, linkRel, limit)).get(); } - buildSearchByUrl(apiRootResource: ApiRootResource, searchBy: string, limit?: number): ResourceUri { + buildSearchByUrl(apiRootResource: ApiRootResource, searchBy: string, linkRel: string, limit?: number): ResourceUri { //TODO sollte mal bei ngxp eingebaut werden und geprüft werden, dass nur erwartete Paramter befüllt werden - const url = new URL(getUrl(apiRootResource, ApiRootLinkRel.SEARCH).replace(/{&[\w,]*}$/, "")); + const url: URL = this.getUrlWithoutParameter(apiRootResource, linkRel); url.searchParams.set(VorgangRepository.SEARCH_PARAM, searchBy); if (limit) url.searchParams.set(VorgangRepository.LIMIT_PARAM, limit.toString()); return url.href; } + + getUrlWithoutParameter(resource: Resource, linkRel: string): URL { + return new URL(getUrl(resource, linkRel).replace(/{&[\w,]*}$/, EMPTY_STRING)); + } } \ No newline at end of file diff --git a/goofy-client/libs/vorgang/src/lib/vorgang.module.ts b/goofy-client/libs/vorgang/src/lib/vorgang.module.ts index f6ef013df728b080cea92bf49bd839cb13a139dc..d2c1e352c3f226fc5d0cfe5ff7805982dbd17c63 100644 --- a/goofy-client/libs/vorgang/src/lib/vorgang.module.ts +++ b/goofy-client/libs/vorgang/src/lib/vorgang.module.ts @@ -24,6 +24,14 @@ const routes: Routes = [ path: 'search/:search', component: VorgangListPageComponent }, + { + path: 'meineVorgaenge', + component: VorgangListPageComponent + }, + { + path: 'meineVorgaenge/search/:search', + component: VorgangListPageComponent + }, { path: 'vorgang/:vorgangWithEingangUrl', loadChildren: () => import('@goofy-client/vorgang-detail').then(m => m.VorgangDetailModule)