diff --git a/alfa-client/apps/admin/src/app/app.module.ts b/alfa-client/apps/admin/src/app/app.module.ts index 7c9c63cf24edf02b34858e9a0f9f1ce98b2ffdcd..29e55f5de54331beb3a2195b83e6265c056dcb5b 100644 --- a/alfa-client/apps/admin/src/app/app.module.ts +++ b/alfa-client/apps/admin/src/app/app.module.ts @@ -28,7 +28,8 @@ import { OAuthModule } from 'angular-oauth2-oidc'; import { HttpUnauthorizedInterceptor } from 'libs/authentication/src/lib/http-unauthorized.interceptor'; import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; import { environment } from '../environments/environment'; -import { OrganisationseinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; +import { OrganisationsEinheitFormPageComponent } from '../pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component'; +import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; import { UserRolesPageComponent } from '../pages/users-roles/user-roles-page/user-roles-page.component'; @@ -40,7 +41,8 @@ import { appRoutes } from './app.routes'; AppComponent, PostfachPageComponent, UserRolesPageComponent, - OrganisationseinheitPageComponent, + OrganisationsEinheitPageComponent, + OrganisationsEinheitFormPageComponent, UserProfileButtonContainerComponent, UnavailablePageComponent, ], diff --git a/alfa-client/apps/admin/src/app/app.routes.ts b/alfa-client/apps/admin/src/app/app.routes.ts index e880d84e0c7dfad1f26a506cf820dfa770d41e33..4225458cdaeb73330edbafa34f76c46209ebfc27 100644 --- a/alfa-client/apps/admin/src/app/app.routes.ts +++ b/alfa-client/apps/admin/src/app/app.routes.ts @@ -1,5 +1,6 @@ import { Route } from '@angular/router'; -import { OrganisationseinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; +import { OrganisationsEinheitFormPageComponent } from '../pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component'; +import { OrganisationsEinheitPageComponent } from '../pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component'; import { PostfachPageComponent } from '../pages/postfach/postfach-page/postfach-page.component'; import { UserRolesPageComponent } from '../pages/users-roles/user-roles-page/user-roles-page.component'; @@ -21,7 +22,12 @@ export const appRoutes: Route[] = [ }, { path: 'organisationseinheiten', - component: OrganisationseinheitPageComponent, + component: OrganisationsEinheitPageComponent, title: 'Admin | Organisationseinheiten', }, + { + path: 'organisationseinheiten/:organisationsEinheitUrl', + component: OrganisationsEinheitFormPageComponent, + title: 'Admin | Organisationseinheit', + }, ]; diff --git a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.html b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.html new file mode 100644 index 0000000000000000000000000000000000000000..2449a303b186022b68d062f7b59ba73c5cb6a790 --- /dev/null +++ b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.html @@ -0,0 +1 @@ +<admin-organisationseinheit-form-container/> \ No newline at end of file diff --git a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.spec.ts b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ad49ca4595e598e66a9e6b0d40b46cca0d606b5 --- /dev/null +++ b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.spec.ts @@ -0,0 +1,26 @@ +import { OrganisationsEinheitFormContainerComponent } from '@admin-client/admin-settings'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockComponent } from 'ng-mocks'; +import { OrganisationsEinheitFormPageComponent } from './organisationseinheit-form-page.component'; + +describe('OrganisationsEinheitFormPageComponent', () => { + let component: OrganisationsEinheitFormPageComponent; + let fixture: ComponentFixture<OrganisationsEinheitFormPageComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OrganisationsEinheitFormPageComponent, MockComponent(OrganisationsEinheitFormContainerComponent)], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(OrganisationsEinheitFormPageComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.ts b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7c9e0ea72a6ccd313239a4461f3a75692be5d9d --- /dev/null +++ b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-form-page/organisationseinheit-form-page.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'organisationseinheit-form-page', + templateUrl: './organisationseinheit-form-page.component.html', +}) +export class OrganisationsEinheitFormPageComponent {} diff --git a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.spec.ts b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.spec.ts index bd3942464b237bac2b0e3e07640eb6d4e3201d62..59ddda81ab9c26fbac8f8bd976298596c9283b5a 100644 --- a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.spec.ts +++ b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.spec.ts @@ -1,20 +1,20 @@ import { OrganisationsEinheitContainerComponent } from '@admin-client/admin-settings'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockComponent } from 'ng-mocks'; -import { OrganisationseinheitPageComponent } from './organisationseinheit-page.component'; +import { OrganisationsEinheitPageComponent } from './organisationseinheit-page.component'; -describe('OrganisationseinheitPageComponent', () => { - let component: OrganisationseinheitPageComponent; - let fixture: ComponentFixture<OrganisationseinheitPageComponent>; +describe('OrganisationsEinheitPageComponent', () => { + let component: OrganisationsEinheitPageComponent; + let fixture: ComponentFixture<OrganisationsEinheitPageComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [OrganisationseinheitPageComponent, MockComponent(OrganisationsEinheitContainerComponent)], + declarations: [OrganisationsEinheitPageComponent, MockComponent(OrganisationsEinheitContainerComponent)], }).compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(OrganisationseinheitPageComponent); + fixture = TestBed.createComponent(OrganisationsEinheitPageComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.ts b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.ts index a87b271644db44803dd63e84c99ca71b0be15443..4d653b6ed7b3701a4f74ce763f313cc93ca29dc6 100644 --- a/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.ts +++ b/alfa-client/apps/admin/src/pages/organisationseinheit/organisationseinheit-page/organisationseinheit-page.component.ts @@ -4,4 +4,4 @@ import { Component } from '@angular/core'; selector: 'organisationseinheit-page', templateUrl: './organisationseinheit-page.component.html', }) -export class OrganisationseinheitPageComponent {} +export class OrganisationsEinheitPageComponent {} diff --git a/alfa-client/libs/admin/settings/src/index.ts b/alfa-client/libs/admin/settings/src/index.ts index 4dd1dcaa1ce0f662154503f9043523cea9b37980..a13f84185d2480694cbb145fb300e553cc668381 100644 --- a/alfa-client/libs/admin/settings/src/index.ts +++ b/alfa-client/libs/admin/settings/src/index.ts @@ -1,6 +1,7 @@ export * from './lib/admin-settings.module'; export * from './lib/organisationseinheit/organisations-einheit.model'; export * from './lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component'; +export * from './lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component'; export * from './lib/postfach/postfach-container/postfach-container.component'; export * from './lib/shared/navigation-item/navigation-item.component'; export * from './lib/users-roles/users-roles.component'; diff --git a/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts b/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts index 416b42ee1b310cb81e42343ef961ccf886e6c2c4..ad13c6426adbf09d39909c175b215c4e79f6c932 100644 --- a/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts +++ b/alfa-client/libs/admin/settings/src/lib/admin-settings.module.ts @@ -1,5 +1,6 @@ import { ApiRootService } from '@alfa-client/api-root-shared'; import { Environment, ENVIRONMENT_CONFIG } from '@alfa-client/environment-shared'; +import { NavigationSharedModule } from '@alfa-client/navigation-shared'; import { ResourceRepository, TechSharedModule } from '@alfa-client/tech-shared'; import { UiModule } from '@alfa-client/ui'; import { CommonModule } from '@angular/common'; @@ -24,8 +25,15 @@ import { createOrganisationsEinheitListResourceService, OrganisationsEinheitListResourceService, } from './organisationseinheit/organisations-einheit-list-resource.service'; +import { + createOrganisationsEinheitResourceService, + OrganisationsEinheitResourceService, +} from './organisationseinheit/organisations-einheit-resource.service'; import { OrganisationsEinheitContainerComponent } from './organisationseinheit/organisationseinheit-container/organisationseinheit-container.component'; import { OrganisationsEinheitListComponent } from './organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component'; +import { OrganisationsEinheitFormContainerComponent } from './organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component'; +import { OrganisationsEinheitFormComponent } from './organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component'; +import { OrganisationsEinheitSignaturComponent } from './organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component'; import { PostfachContainerComponent } from './postfach/postfach-container/postfach-container.component'; import { PostfachFormComponent } from './postfach/postfach-container/postfach-form/postfach-form.component'; import { PostfachSignaturComponent } from './postfach/postfach-container/postfach-form/postfach-signatur/postfach-signatur.component'; @@ -49,9 +57,12 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; NavigationItemComponent, TextFieldComponent, OrganisationsEinheitContainerComponent, + OrganisationsEinheitListComponent, + OrganisationsEinheitFormContainerComponent, + OrganisationsEinheitFormComponent, + OrganisationsEinheitSignaturComponent, PrimaryButtonComponent, SecondaryButtonComponent, - OrganisationsEinheitListComponent, MoreMenuComponent, MoreItemButtonComponent, SpinnerComponent, @@ -72,8 +83,15 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; ListItemComponent, ExclamationIconComponent, UiModule, + NavigationSharedModule, + ], + exports: [ + PostfachContainerComponent, + OrganisationsEinheitContainerComponent, + OrganisationsEinheitFormContainerComponent, + NavigationItemComponent, + UsersRolesComponent, ], - exports: [PostfachContainerComponent, OrganisationsEinheitContainerComponent, NavigationItemComponent, UsersRolesComponent], providers: [ ConfigurationService, SettingsService, @@ -107,6 +125,11 @@ import { UsersRolesComponent } from './users-roles/users-roles.component'; useFactory: createOrganisationsEinheitListResourceService, deps: [ResourceRepository, ApiRootService], }, + { + provide: OrganisationsEinheitResourceService, + useFactory: createOrganisationsEinheitResourceService, + deps: [ResourceRepository, OrganisationsEinheitListResourceService], + }, ], }) export class AdminSettingsModule {} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit-resource.service.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit-resource.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..7722ed1bc822c933a67f33f74b7175ae2e7e2f47 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit-resource.service.ts @@ -0,0 +1,25 @@ +import { AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { ApiResourceService, ResourceRepository, ResourceServiceConfig } from '@alfa-client/tech-shared'; +import { ConfigurationResource } from 'libs/admin/settings/src/lib/configuration/configuration.model'; +import { OrganisationsEinheitListResourceService } from './organisations-einheit-list-resource.service'; +import { OrganisationsEinheitLinkRel } from './organisations-einheit.linkrel'; + +export class OrganisationsEinheitResourceService extends ApiResourceService< + AdminOrganisationsEinheitResource, + AdminOrganisationsEinheitResource +> {} + +export function createOrganisationsEinheitResourceService( + repository: ResourceRepository, + listResourceService: OrganisationsEinheitListResourceService, +) { + return new ApiResourceService(buildConfig(listResourceService), repository); +} + +function buildConfig(listResourceService: OrganisationsEinheitListResourceService): ResourceServiceConfig<ConfigurationResource> { + return { + resource: listResourceService.getSelected(), + getLinkRel: OrganisationsEinheitLinkRel.SELF, + edit: { linkRel: OrganisationsEinheitLinkRel.SELF }, + }; +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.linkrel.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.linkrel.ts index fdd7c73d80b258fc1a2718e153b5baf68b7df4d4..6e941e1fc3ac3406b32d44085fcde10284fde8ba 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.linkrel.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.linkrel.ts @@ -1,3 +1,7 @@ export enum OrganisationsEinheitListLinkRel { LIST = 'organisationsEinheitList', } + +export enum OrganisationsEinheitLinkRel { + SELF = 'self', +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.model.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.model.ts index 29e29215a5a5887d5a017b628af12e8cc71de84b..f74a83a382373287cfc21afe8654c9023d67e341 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.model.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisations-einheit.model.ts @@ -4,12 +4,12 @@ import { Resource } from '@ngxp/rest'; export interface AdminOrganisationsEinheit { name: string; organisationsEinheitId: string; - syncResult: SyncResult; - settings?: Settings; + syncResult: AdminOrganisationsEinheitSyncResult; + settings?: AdminOrganisationsEinheitSettings; isChild?: boolean; } -export enum SyncResult { +export enum AdminOrganisationsEinheitSyncResult { OK = 'OK', NOT_FOUND_IN_PVOG = 'NOT_FOUND_IN_PVOG', NAME_MISMATCH = 'NAME_MISMATCH', @@ -17,7 +17,7 @@ export enum SyncResult { DELETED = 'DELETED', } -export interface Settings { +export interface AdminOrganisationsEinheitSettings { signatur?: string; } diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html index cb464e20397898b3055ac773bae2d7c581fc8d6b..5dc954e2c97ce3b5bc3cef43b0cee24532dc987c 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.html @@ -1,7 +1,7 @@ <h1 class="heading-1 pb-4">Organisationseinheiten</h1> <admin-organisationseinheit-list - *ngIf="organisationsEinheiten$ | async as organisationsEinheiten" - [organisationsEinheiten]="organisationsEinheiten" + *ngIf="organisationsEinheitResources$ | async as organisationsEinheitResources" + [organisationsEinheitResources]="organisationsEinheitResources" data-test-id="organisations-einheit-list" /> diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.spec.ts index f56ad9731ed419db63aa66db7fd27ce5d71b5246..43ee626c39887b1f882a4b5af5d23619171a0a63 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.spec.ts @@ -1,12 +1,15 @@ -import { AdminOrganisationsEinheit, OrganisationsEinheitContainerComponent } from '@admin-client/admin-settings'; +import { AdminOrganisationsEinheitResource, OrganisationsEinheitContainerComponent } from '@admin-client/admin-settings'; import { createStateResource } from '@alfa-client/tech-shared'; import { Mock, existsAsHtmlElement, mock } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ButtonWithSpinnerComponent } from '@ods/component'; import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; -import { createAdminOrganisationsEinheitListResource } from '../../../../test/organisations-einheit/organisations-einheit'; +import { + createAdminOrganisationsEinheitListResource, + createAdminOrganisationsEinheitResource, +} from '../../../../test/organisations-einheit/organisations-einheit'; import { OrganisationsEinheitService } from '../organisationseinheit.service'; import { OrganisationsEinheitListComponent } from './organisationseinheit-list/organisationseinheit-list.component'; @@ -39,19 +42,40 @@ describe('OrganisationsEinheitContainerComponent', () => { describe('component', () => { describe('ngOnInit', () => { - it('should call organisationsEinheitService getList', () => { - component.ngOnInit(); + beforeEach(() => { + component.loadOrganisationsEinheitResources = jest.fn().mockReturnValue(of([createAdminOrganisationsEinheitResource()])); + }); - component.organisationsEinheiten$.subscribe(() => { - expect(organisationsEinheitService.getList).toHaveBeenCalled(); + it('should call loadOrganisationsEinheitResources', () => { + component.organisationsEinheitResources$.subscribe(() => { + expect(component.loadOrganisationsEinheitResources).toHaveBeenCalled(); }); }); - it('should set organisationsEinheiten', () => { + it('should set organisationsEinheitResources', () => { component.ngOnInit(); - component.organisationsEinheiten$.subscribe((organisationsEinheiten: AdminOrganisationsEinheit[]) => { - expect(organisationsEinheiten.length).toBe(1); + component.organisationsEinheitResources$.subscribe( + (organisationsEinheitResources: AdminOrganisationsEinheitResource[]) => { + expect(organisationsEinheitResources.length).toBe(1); + }, + ); + }); + }); + + describe('loadOrganisationsEinheitResources', () => { + it('should call organisationsEinheitService getList', () => { + component.loadOrganisationsEinheitResources(); + + expect(organisationsEinheitService.getList).toHaveBeenCalled(); + }); + + it('should set organisationsEinheitResources', () => { + const organisationsEinheitResources$: Observable<AdminOrganisationsEinheitResource[]> = + component.loadOrganisationsEinheitResources(); + + organisationsEinheitResources$.subscribe((organisationsEinheitResources: AdminOrganisationsEinheitResource[]) => { + expect(organisationsEinheitResources.length).toBe(1); }); }); }); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts index 08ac5f6861564b937508c1d422492e4399d34ad1..ce5610b8b2d295c591265cc930f96f4b5c1684a4 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-container.component.ts @@ -2,7 +2,7 @@ import { getEmbeddedResources, StateResource } from '@alfa-client/tech-shared'; import { Component, OnInit } from '@angular/core'; import { map, Observable } from 'rxjs'; import { OrganisationsEinheitListLinkRel } from '../organisations-einheit.linkrel'; -import { AdminOrganisationsEinheit, AdminOrganisationsEinheitListResource } from '../organisations-einheit.model'; +import { AdminOrganisationsEinheitListResource, AdminOrganisationsEinheitResource } from '../organisations-einheit.model'; import { OrganisationsEinheitService } from '../organisationseinheit.service'; @Component({ @@ -10,16 +10,23 @@ import { OrganisationsEinheitService } from '../organisationseinheit.service'; templateUrl: './organisationseinheit-container.component.html', }) export class OrganisationsEinheitContainerComponent implements OnInit { - organisationsEinheiten$: Observable<AdminOrganisationsEinheit[]>; + organisationsEinheitResources$: Observable<AdminOrganisationsEinheitResource[]>; constructor(private organisationsEinheitService: OrganisationsEinheitService) {} ngOnInit(): void { - this.organisationsEinheiten$ = this.organisationsEinheitService + this.organisationsEinheitResources$ = this.loadOrganisationsEinheitResources(); + } + + loadOrganisationsEinheitResources(): Observable<AdminOrganisationsEinheitResource[]> { + return this.organisationsEinheitService .getList() .pipe( map((organisationsEinheitListResource: StateResource<AdminOrganisationsEinheitListResource>) => - getEmbeddedResources<AdminOrganisationsEinheit>(organisationsEinheitListResource, OrganisationsEinheitListLinkRel.LIST), + getEmbeddedResources<AdminOrganisationsEinheitResource>( + organisationsEinheitListResource, + OrganisationsEinheitListLinkRel.LIST, + ), ), ); } diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.html index 604f902059e85f956490c2be85443f2af621a664..8794ba5f94c60bb6908be41c82d8543810cb6583 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.html +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.html @@ -1,25 +1,25 @@ -<ods-list *ngIf="organisationsEinheiten.length > 0" data-test-id="organisations-einheit-list"> +<ods-list *ngIf="organisationsEinheitResources.length > 0" data-test-id="organisations-einheit-list"> <ods-list-item - *ngFor="let organisationsEinheit of organisationsEinheiten" - [routerLink]="'TODO'" - [class.text-red-500]="syncResultIsNotOk(organisationsEinheit)" + *ngFor="let organisationsEinheitResource of organisationsEinheitResources" + [routerLink]="getEncodedSelfLink(organisationsEinheitResource)" + [class.text-red-500]="syncResultIsNotOk(organisationsEinheitResource)" data-test-id="organisations-einheit-list-item" > - <dl class="flex-1 basis-3/4 font-semibold" [class.pl-4]="organisationsEinheit.isChild"> + <dl class="flex-1 basis-3/4 font-semibold" [class.pl-4]="organisationsEinheitResource.isChild"> <dt class="sr-only">Name</dt> - <dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd> + <dd data-test-id="organisations-einheit-name">{{ organisationsEinheitResource.name }}</dd> </dl> <dl class="flex-1 basis-3/12"> <dt class="sr-only">Organisationseinheit-ID</dt> - <dd data-test-id="organisations-einheit-id">{{ organisationsEinheit.organisationsEinheitId }}</dd> + <dd data-test-id="organisations-einheit-id">{{ organisationsEinheitResource.organisationsEinheitId }}</dd> </dl> <dl class="flex-1 basis-1/12"> <dt class="sr-only">Synchronisationsergebnis</dt> <dd> <ods-exclamation-icon - *ngIf="syncResultIsNotOk(organisationsEinheit)" + *ngIf="syncResultIsNotOk(organisationsEinheitResource)" matTooltip="Organisationseinheit wurde nicht in den PVOG-Daten gefunden." size="small" /> diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.spec.ts index 227569fc92304405061edc43614e4c175e7a9dec..fa4d2943bd5e3e46e03c4d532379ac3a077290c9 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.spec.ts @@ -1,4 +1,4 @@ -import { AdminOrganisationsEinheit, SyncResult } from '@admin-client/admin-settings'; +import { AdminOrganisationsEinheitResource, AdminOrganisationsEinheitSyncResult } from '@admin-client/admin-settings'; import { ConvertForDataTestPipe } from '@alfa-client/tech-shared'; import { existsAsHtmlElement, @@ -10,10 +10,11 @@ import { import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ActivatedRoute } from '@angular/router'; +import { ResourceUri } from '@ngxp/rest'; import { ExclamationIconComponent, ListComponent, ListItemComponent } from '@ods/system'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { MockModule } from 'ng-mocks'; -import { createAdminOrganisationsEinheit } from '../../../../../test/organisations-einheit/organisations-einheit'; +import { createAdminOrganisationsEinheitResource } from '../../../../../test/organisations-einheit/organisations-einheit'; import { OrganisationsEinheitListComponent } from './organisationseinheit-list.component'; describe('OrganisationsEinheitListComponent', () => { @@ -45,29 +46,31 @@ describe('OrganisationsEinheitListComponent', () => { }); describe('input', () => { - describe('organisationsEinheiten', () => { - const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(SyncResult.NAME_MISMATCH); + describe('organisationsEinheitResources', () => { + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource( + AdminOrganisationsEinheitSyncResult.NAME_MISMATCH, + ); beforeEach(() => { - component.organisationsEinheiten = [organisationsEinheit]; + component.organisationsEinheitResources = [organisationsEinheitResource]; fixture.detectChanges(); }); - it('should set organisationsEinheit name', () => { + it('should set organisationsEinheitResource name', () => { const listItemElement: HTMLElement = getElementFromFixture(fixture, listItemSelector); const nameElement: Element = listItemElement.querySelector('[data-test-id="organisations-einheit-name"]'); - expect(nameElement.textContent).toBe(organisationsEinheit.name); + expect(nameElement.textContent).toBe(organisationsEinheitResource.name); }); - it('should set organisationsEinheit organisationsEinheitId', () => { + it('should set organisationsEinheitResource organisationsEinheitId', () => { const listItemElement: HTMLElement = getElementFromFixture(fixture, listItemSelector); const idElement: Element = listItemElement.querySelector('[data-test-id="organisations-einheit-id"]'); - expect(idElement.textContent).toBe(organisationsEinheit.organisationsEinheitId); + expect(idElement.textContent).toBe(organisationsEinheitResource.organisationsEinheitId); }); - it('should set organisationsEinheiten', () => { + it('should set exclamation icon', () => { const listItemElement: HTMLElement = getElementFromFixture(fixture, listItemSelector); const iconElement: Element = listItemElement.querySelector('ods-exclamation-icon'); @@ -79,40 +82,59 @@ describe('OrganisationsEinheitListComponent', () => { describe('component', () => { describe('moveChildrenIntoParentLevel', () => { it('should move children one level up', () => { - const childList: AdminOrganisationsEinheit[] = [createAdminOrganisationsEinheit(), createAdminOrganisationsEinheit()]; - const item1: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); - const item2: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); + const childList: AdminOrganisationsEinheitResource[] = [ + createAdminOrganisationsEinheitResource(), + createAdminOrganisationsEinheitResource(), + ]; + const item1: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + const item2: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); item2['_embedded'] = { childList }; - component.organisationsEinheiten = [item1, item2]; + component.organisationsEinheitResources = [item1, item2]; - expect(component.organisationsEinheiten.length).toBe(4); + expect(component.organisationsEinheitResources.length).toBe(4); }); it('should set isChild property at moved children', () => { - const childList: AdminOrganisationsEinheit[] = [createAdminOrganisationsEinheit(), createAdminOrganisationsEinheit()]; - const item1: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); - const item2: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(); + const childList: AdminOrganisationsEinheitResource[] = [ + createAdminOrganisationsEinheitResource(), + createAdminOrganisationsEinheitResource(), + ]; + const item1: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + const item2: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); item2['_embedded'] = { childList }; - component.organisationsEinheiten = [item1, item2]; + component.organisationsEinheitResources = [item1, item2]; - expect(component.organisationsEinheiten[2].isChild).toBeTruthy(); + expect(component.organisationsEinheitResources[2].isChild).toBeTruthy(); }); }); describe('syncResultIsNotOk', () => { it('should return true', () => { - const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(SyncResult.NAME_MISMATCH); - const result: boolean = component.syncResultIsNotOk(organisationsEinheit); + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource( + AdminOrganisationsEinheitSyncResult.NAME_MISMATCH, + ); + const result: boolean = component.syncResultIsNotOk(organisationsEinheitResource); expect(result).toBeTruthy(); }); it('should return false', () => { - const organisationsEinheit: AdminOrganisationsEinheit = createAdminOrganisationsEinheit(SyncResult.OK); - const result: boolean = component.syncResultIsNotOk(organisationsEinheit); + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource( + AdminOrganisationsEinheitSyncResult.OK, + ); + const result: boolean = component.syncResultIsNotOk(organisationsEinheitResource); expect(result).toBeFalsy(); }); }); + + describe('getEncodedSelfLink', () => { + it('should return encoded self link', () => { + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + const result: ResourceUri = component.getEncodedSelfLink(organisationsEinheitResource); + + expect(result).toBeDefined(); + }); + }); }); describe('template', () => { @@ -124,13 +146,13 @@ describe('OrganisationsEinheitListComponent', () => { }); describe('with organisationsEinheiten', () => { - const organisationsEinheiten: AdminOrganisationsEinheit[] = [ - createAdminOrganisationsEinheit(), - createAdminOrganisationsEinheit(), + const organisationsEinheiten: AdminOrganisationsEinheitResource[] = [ + createAdminOrganisationsEinheitResource(), + createAdminOrganisationsEinheitResource(), ]; beforeEach(() => { - component.organisationsEinheiten = organisationsEinheiten; + component.organisationsEinheitResources = organisationsEinheiten; fixture.detectChanges(); }); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.ts index 5340864b2ab47dc20d96d825f76d9571dec80e7d..aab7414ed051b840b30523baa114828fba2a4fc6 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-list/organisationseinheit-list.component.ts @@ -1,33 +1,44 @@ +import { AdminOrganisationsEinheitResource, AdminOrganisationsEinheitSyncResult } from '@admin-client/admin-settings'; +import { encodeUrlForEmbedding } from '@alfa-client/tech-shared'; import { Component, Input } from '@angular/core'; -import { AdminOrganisationsEinheit, SyncResult } from '../../organisations-einheit.model'; +import { ResourceUri, getLink } from '@ngxp/rest'; +import { OrganisationsEinheitLinkRel } from '../../organisations-einheit.linkrel'; @Component({ selector: 'admin-organisationseinheit-list', templateUrl: './organisationseinheit-list.component.html', }) export class OrganisationsEinheitListComponent { - private _organisationsEinheiten: AdminOrganisationsEinheit[] = []; + private _organisationsEinheitResources: AdminOrganisationsEinheitResource[] = []; + @Input() - public get organisationsEinheiten(): AdminOrganisationsEinheit[] { - return this._organisationsEinheiten; + public get organisationsEinheitResources(): AdminOrganisationsEinheitResource[] { + return this._organisationsEinheitResources; } - public set organisationsEinheiten(list: AdminOrganisationsEinheit[]) { + + public set organisationsEinheitResources(list: AdminOrganisationsEinheitResource[]) { this.moveChildrenIntoParentLevel(list); } - moveChildrenIntoParentLevel(list: AdminOrganisationsEinheit[]): void { + moveChildrenIntoParentLevel(list: AdminOrganisationsEinheitResource[]): void { list.forEach((parent) => { - this._organisationsEinheiten.push(parent); - if (parent['_embedded'] && parent['_embedded'].childList?.length > 0) { - parent['_embedded'].childList.forEach((child: AdminOrganisationsEinheit) => { - child['isChild'] = true; - this._organisationsEinheiten.push(child); + this._organisationsEinheitResources.push(parent); + + if (parent._embedded && Array.isArray(parent._embedded.childList)) { + parent._embedded.childList.forEach((child: AdminOrganisationsEinheitResource) => { + child.isChild = true; + this._organisationsEinheitResources.push(child); }); } }); } - syncResultIsNotOk(organisationsEinheit: AdminOrganisationsEinheit): boolean { - return organisationsEinheit.syncResult !== SyncResult.OK; + syncResultIsNotOk(organisationsEinheitResource: AdminOrganisationsEinheitResource): boolean { + return organisationsEinheitResource.syncResult !== AdminOrganisationsEinheitSyncResult.OK; + } + + getEncodedSelfLink(organisationsEinheitResource: AdminOrganisationsEinheitResource): ResourceUri { + const resourceUri: ResourceUri = getLink(organisationsEinheitResource, OrganisationsEinheitLinkRel.SELF).href; + return encodeUrlForEmbedding(resourceUri); } } diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..f31e92e3c557900090fce127c00d206ce8f19d51 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.html @@ -0,0 +1,8 @@ +<h1 class="heading-1" data-test-id="organisations-form-container-headline"> + {{ (organisationsEinheitStateResource$ | async)?.resource?.name }} +</h1> + +<admin-organisationseinheit-form + [organisationsEinheitStateResource]="organisationsEinheitStateResource$ | async" + data-test-id="organisations-form" +/> \ No newline at end of file diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0854bf5313ee7fc349c7049dc8f7befe498c869 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.spec.ts @@ -0,0 +1,73 @@ +import { AdminOrganisationsEinheitResource, OrganisationsEinheitFormContainerComponent } from '@admin-client/admin-settings'; +import { StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { Mock, existsAsHtmlElement, mock } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { getDataTestIdOf } from '../../../../../../tech-shared/test/data-test'; +import { createAdminOrganisationsEinheitResource } from '../../../../test/organisations-einheit/organisations-einheit'; +import { OrganisationsEinheitService } from '../organisationseinheit.service'; +import { OrganisationsEinheitFormComponent } from './organisationseinheit-form/organisationseinheit-form.component'; + +describe('OrganisationsEinheitFormContainerComponent', () => { + let component: OrganisationsEinheitFormContainerComponent; + let fixture: ComponentFixture<OrganisationsEinheitFormContainerComponent>; + + const organisationsEinheitService: Mock<OrganisationsEinheitService> = mock(OrganisationsEinheitService); + + const organisationsEinheitStateResource = createStateResource(createAdminOrganisationsEinheitResource()); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OrganisationsEinheitFormContainerComponent, MockComponent(OrganisationsEinheitFormComponent)], + providers: [{ provide: OrganisationsEinheitService, useValue: organisationsEinheitService }], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitFormContainerComponent); + component = fixture.componentInstance; + + organisationsEinheitService.get = jest.fn().mockReturnValue(of(organisationsEinheitStateResource)); + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('component', () => { + describe('ngOnInit', () => { + it('should call organisationsEinheitService get', () => { + component.ngOnInit(); + + component.organisationsEinheitStateResource$.subscribe(() => { + expect(organisationsEinheitService.get).toHaveBeenCalled(); + }); + }); + + it('should set organisationsEinheitStateResource', () => { + component.ngOnInit(); + + component.organisationsEinheitStateResource$.subscribe( + (organisationsEinheitStateResource: StateResource<AdminOrganisationsEinheitResource>) => { + expect(organisationsEinheitStateResource).toEqual(organisationsEinheitStateResource); + }, + ); + }); + }); + }); + + describe('template', () => { + describe('headline', () => { + it('should show organisationsEinheit name', () => { + existsAsHtmlElement(fixture, getDataTestIdOf('organisations-form-container-headline')); + }); + }); + + describe('organisationsEinheit form', () => { + it('should show organisationsEinheit form', () => { + existsAsHtmlElement(fixture, getDataTestIdOf('organisations-form')); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc46ee5d86126541187b2ebd9d41dce928d553e8 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form-container.component.ts @@ -0,0 +1,19 @@ +import { StateResource } from '@alfa-client/tech-shared'; +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { AdminOrganisationsEinheitResource } from '../organisations-einheit.model'; +import { OrganisationsEinheitService } from '../organisationseinheit.service'; + +@Component({ + selector: 'admin-organisationseinheit-form-container', + templateUrl: './organisationseinheit-form-container.component.html', +}) +export class OrganisationsEinheitFormContainerComponent implements OnInit { + organisationsEinheitStateResource$: Observable<StateResource<AdminOrganisationsEinheitResource>>; + + constructor(private organisationsEinheitService: OrganisationsEinheitService) {} + + ngOnInit(): void { + this.organisationsEinheitStateResource$ = this.organisationsEinheitService.get(); + } +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.html new file mode 100644 index 0000000000000000000000000000000000000000..3e8708c6939b72e0796be10b943baf18bc9cf892 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.html @@ -0,0 +1,18 @@ +<form class="form flex-col" [formGroup]="formService.form"> + <admin-organisationseinheit-signatur class="mb-6 block" data-test-id="organisations-einheit-signatur-component" /> + + <ods-button-with-spinner + data-test-id="save-button" + text="Speichern" + [stateResource]="submitInProgress$ | async" + (clickEmitter)="submit()" + ></ods-button-with-spinner> + + <span + *ngIf="formService.invalidEmpty" + data-test-id="invalid-empty-message-span" + class="m-2 text-red-500" + > + *Es müssen alle Felder ausgefüllt sein. + </span> +</form> \ No newline at end of file diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1976562c9a9fa7aa270e230278e4ce3093775503 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.spec.ts @@ -0,0 +1,120 @@ +import { AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { createEmptyStateResource, createStateResource, ProblemDetail } from '@alfa-client/tech-shared'; +import { existsAsHtmlElement, Mock, mock, notExistsAsHtmlElement } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ButtonWithSpinnerComponent } from '@ods/component'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { createInvalidParam, createProblemDetail } from '../../../../../../../tech-shared/test/error'; +import { createAdminOrganisationsEinheitResource } from '../../../../../test/organisations-einheit/organisations-einheit'; +import { TextFieldComponent } from '../../../shared/text-field/text-field.component'; +import { OrganisationsEinheitService } from '../../organisationseinheit.service'; +import { OrganisationsEinheitFormComponent } from './organisationseinheit-form.component'; +import { OrganisationsEinheitSignaturComponent } from './organisationseinheit-signatur/organisationseinheit-signatur.component'; +import { OrganisationsEinheitFormService } from './organisationseinheit.formservice'; + +describe('OrganisationsEinheitFormComponent', () => { + let component: OrganisationsEinheitFormComponent; + let fixture: ComponentFixture<OrganisationsEinheitFormComponent>; + let formService: OrganisationsEinheitFormService; + + const organisationsEinheitService: Mock<OrganisationsEinheitService> = mock(OrganisationsEinheitService); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + OrganisationsEinheitFormComponent, + MockComponent(TextFieldComponent), + MockComponent(OrganisationsEinheitSignaturComponent), + MockComponent(ButtonWithSpinnerComponent), + ], + imports: [ReactiveFormsModule, FormsModule], + providers: [{ provide: OrganisationsEinheitService, useValue: organisationsEinheitService }], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitFormComponent); + component = fixture.componentInstance; + formService = component.formService; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('input', () => { + describe('organisationsEinheitStateResource', () => { + it('should return resource', () => { + const updateOrganisationsEinheitResourceFn: jest.SpyInstance = jest.spyOn( + component, + 'updateOrganisationsEinheitResource', + ); + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + + component.organisationsEinheitStateResource = createStateResource(organisationsEinheitResource); + + expect(updateOrganisationsEinheitResourceFn).toHaveBeenCalledWith(organisationsEinheitResource); + }); + }); + }); + + describe('component', () => { + describe('updateOrganisationsEinheitResource', () => { + it('should call formService patch', () => { + formService.patch = jest.fn(); + + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + + component.updateOrganisationsEinheitResource(organisationsEinheitResource); + + expect(formService.patch).toHaveBeenCalledWith(organisationsEinheitResource.settings); + }); + }); + + describe('submit', () => { + it('should call formService submit', () => { + formService.submit = jest.fn().mockReturnValue(of(createEmptyStateResource())); + + component.submit(); + + expect(formService.submit).toHaveBeenCalled(); + }); + }); + }); + + describe('template', () => { + describe('organisationsEinheit signatur component', () => { + it('should show signatur component', () => { + existsAsHtmlElement(fixture, getDataTestIdOf('organisations-einheit-signatur-component')); + }); + }); + + describe('save button', () => { + it('should show save button', () => { + existsAsHtmlElement(fixture, getDataTestIdOf('save-button')); + }); + }); + + describe('invalid message', () => { + const invalidMessageSpan: string = getDataTestIdOf('invalid-empty-message-span'); + + it('should show if form invalidEmpty', () => { + const problemDetail: ProblemDetail = { + ...createProblemDetail(), + invalidParams: [{ ...createInvalidParam(), name: 'settingBody.signatur' }], + }; + formService.setErrorByProblemDetail(problemDetail); + + fixture.detectChanges(); + + existsAsHtmlElement(fixture, invalidMessageSpan); + }); + + it('should not show if form valid', () => { + notExistsAsHtmlElement(fixture, invalidMessageSpan); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e23d78e48258b6444fa11e4375ce8259f46742fd --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-form.component.ts @@ -0,0 +1,31 @@ +import { AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { StateResource, createEmptyStateResource, isNotNil } from '@alfa-client/tech-shared'; +import { Component, Input } from '@angular/core'; +import { Resource } from '@ngxp/rest'; +import { Observable, of } from 'rxjs'; +import { OrganisationsEinheitFormService } from './organisationseinheit.formservice'; + +@Component({ + selector: 'admin-organisationseinheit-form', + templateUrl: './organisationseinheit-form.component.html', + providers: [OrganisationsEinheitFormService], +}) +export class OrganisationsEinheitFormComponent { + submitInProgress$: Observable<StateResource<Resource>> = of(createEmptyStateResource<Resource>()); + + @Input() set organisationsEinheitStateResource(stateResource: StateResource<AdminOrganisationsEinheitResource>) { + this.updateOrganisationsEinheitResource(stateResource.resource); + } + + updateOrganisationsEinheitResource(organisationsEinheitResource: AdminOrganisationsEinheitResource): void { + if (isNotNil(organisationsEinheitResource)) { + this.formService.patch(organisationsEinheitResource.settings); + } + } + + constructor(public formService: OrganisationsEinheitFormService) {} + + public submit(): void { + this.submitInProgress$ = this.formService.submit(); + } +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.html b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.html new file mode 100644 index 0000000000000000000000000000000000000000..08db87e61bb3198d993070fdb54ab8ae5a68eb0c --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.html @@ -0,0 +1,16 @@ +<form [formGroup]="formService.form" class="max-w-[960px]"> + <h2 class="heading-2">Signatur</h2> + <p id="signatur-desc"> + Diese Signatur gilt für die ausgewählte Organisationseinheit und wird bei allen Nachrichten an den Antragsteller angezeigt. + </p> + <ods-textarea-editor + [formControlName]="formServiceClass.ORGANISATIONSEINHEIT_SIGNATUR_FIELD" + [isResizable]="false" + [showLabel]="false" + data-test-id="signatur-text" + label="signature" + rows="6" + class="w-full" + aria-describedby="signatur-desc" + /> +</form> \ No newline at end of file diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..da98a3b87601a0830743d69fbe6ed99cb2325f1f --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.spec.ts @@ -0,0 +1,57 @@ +import { NavigationSharedModule } from '@alfa-client/navigation-shared'; +import { getElementFromFixture, mock, useFromMock } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { EffectsModule } from '@ngrx/effects'; +import { StoreModule } from '@ngrx/store'; +import { TextareaEditorComponent } from '@ods/component'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { OrganisationsEinheitService } from '../../../organisationseinheit.service'; +import { OrganisationsEinheitFormService } from '../organisationseinheit.formservice'; +import { OrganisationsEinheitSignaturComponent } from './organisationseinheit-signatur.component'; + +describe('OrganisationsEinheitSignaturComponent', () => { + let component: OrganisationsEinheitSignaturComponent; + let fixture: ComponentFixture<OrganisationsEinheitSignaturComponent>; + + const formService: OrganisationsEinheitFormService = new OrganisationsEinheitFormService( + new FormBuilder(), + useFromMock(mock(OrganisationsEinheitService)), + ); + + const signaturTextarea = getDataTestIdOf('signatur-text'); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, NavigationSharedModule, StoreModule.forRoot({}), EffectsModule.forRoot([])], + declarations: [OrganisationsEinheitSignaturComponent, MockComponent(TextareaEditorComponent)], + providers: [ + { + provide: OrganisationsEinheitFormService, + useValue: formService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitSignaturComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('template', () => { + describe('ods-textarea-editor', () => { + describe('input', () => { + it('should set signatur field', () => { + const textAreaEditor = getElementFromFixture(fixture, signaturTextarea); + + expect(textAreaEditor.getAttribute('rows')).toEqual('6'); + }); + }); + }); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..112353d7b5cf1a613f3f81fd77662b7b11c8a2c0 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit-signatur/organisationseinheit-signatur.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { OrganisationsEinheitFormService } from '../organisationseinheit.formservice'; + +@Component({ + selector: 'admin-organisationseinheit-signatur', + templateUrl: './organisationseinheit-signatur.component.html', +}) +export class OrganisationsEinheitSignaturComponent { + protected readonly formServiceClass = OrganisationsEinheitFormService; + + constructor(public formService: OrganisationsEinheitFormService) {} +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..249af7021abcd360680e7c7318c2123763089d20 --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts @@ -0,0 +1,42 @@ +import { AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import { FormBuilder } from '@angular/forms'; +import { of } from 'rxjs'; +import { createAdminOrganisationsEinheitResource } from '../../../../../test/organisations-einheit/organisations-einheit'; +import { OrganisationsEinheitService } from '../../organisationseinheit.service'; +import { OrganisationsEinheitFormService } from './organisationseinheit.formservice'; + +describe('OrganisationsEinheitFormService', () => { + let service: OrganisationsEinheitFormService; + let organisationsEinheitService: Mock<OrganisationsEinheitService>; + const formBuilder: FormBuilder = new FormBuilder(); + + beforeEach(() => { + organisationsEinheitService = mock(OrganisationsEinheitService); + service = new OrganisationsEinheitFormService(formBuilder, useFromMock(organisationsEinheitService)); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + describe('submit', () => { + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + + beforeEach(() => { + const stateResource: StateResource<AdminOrganisationsEinheitResource> = createStateResource(organisationsEinheitResource); + organisationsEinheitService.patch.mockReturnValue(of(stateResource)); + organisationsEinheitService.get.mockReturnValue(of(stateResource)); + service.form.setValue({ + [OrganisationsEinheitFormService.ORGANISATIONSEINHEIT_SIGNATUR_FIELD]: organisationsEinheitResource.settings.signatur, + }); + }); + + it('should call organisationsEinheitService patch', () => { + service.submit(); + + expect(organisationsEinheitService.patch).toHaveBeenCalledWith(organisationsEinheitResource.settings); + }); + }); +}); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.ts new file mode 100644 index 0000000000000000000000000000000000000000..82beca0dfb6fd1cb9210090f7a11cac3c2b497eb --- /dev/null +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit-form-container/organisationseinheit-form/organisationseinheit.formservice.ts @@ -0,0 +1,36 @@ +import { AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; +import { Injectable } from '@angular/core'; +import { FormControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { OrganisationsEinheitService } from '../../organisationseinheit.service'; + +@Injectable() +export class OrganisationsEinheitFormService extends AbstractFormService { + public static readonly ORGANISATIONSEINHEIT_SIGNATUR_FIELD: string = 'signatur'; + + constructor( + formBuilder: UntypedFormBuilder, + private organisationsEinheitService: OrganisationsEinheitService, + ) { + super(formBuilder); + } + + protected initForm(): UntypedFormGroup { + return this.formBuilder.group({ + [OrganisationsEinheitFormService.ORGANISATIONSEINHEIT_SIGNATUR_FIELD]: new FormControl(EMPTY_STRING), + }); + } + + protected doSubmit(): Observable<StateResource<AdminOrganisationsEinheitResource>> { + return this.organisationsEinheitService.patch(this.getFormValue()); + } + + protected getPathPrefix(): string { + return 'settingBody'; + } + + public get invalidEmpty(): boolean { + return this.form.invalid; + } +} diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.spec.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.spec.ts index 602dc29bbf9abc9136760c092773a5f0ffb67839..77d2d39309893316e9a99db8d6e6796fcd1a9d05 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.spec.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.spec.ts @@ -1,37 +1,95 @@ -import { AdminOrganisationsEinheitListResource } from '@admin-client/admin-settings'; -import { createStateResource, StateResource } from '@alfa-client/tech-shared'; -import { mock, Mock, useFromMock } from '@alfa-client/test-utils'; +import { AdminOrganisationsEinheitListResource, AdminOrganisationsEinheitResource } from '@admin-client/admin-settings'; +import { NavigationService } from '@alfa-client/navigation-shared'; +import { StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import { SnackBarService } from '@alfa-client/ui'; import { singleColdCompleted } from 'libs/tech-shared/test/marbles'; import { Observable, of } from 'rxjs'; -import { createAdminOrganisationsEinheitListResource } from '../../../test/organisations-einheit/organisations-einheit'; +import { + createAdminOrganisationsEinheitListResource, + createAdminOrganisationsEinheitResource, +} from '../../../test/organisations-einheit/organisations-einheit'; import { OrganisationsEinheitListResourceService } from './organisations-einheit-list-resource.service'; +import { OrganisationsEinheitResourceService } from './organisations-einheit-resource.service'; import { OrganisationsEinheitService } from './organisationseinheit.service'; jest.mock('./organisations-einheit-list-resource.service'); +jest.mock('./organisations-einheit-resource.service'); +jest.mock('../../../../../navigation-shared/src/lib/navigation.service'); +jest.mock('../../../../../ui/src/lib/snackbar/snackbar.service'); describe('OrganisationsEinheitService', () => { let service: OrganisationsEinheitService; - let listService: Mock<OrganisationsEinheitListResourceService>; - - const organisationsEinheitListResource: AdminOrganisationsEinheitListResource = createAdminOrganisationsEinheitListResource(); + let listResourceService: Mock<OrganisationsEinheitListResourceService>; + let resourceService: Mock<OrganisationsEinheitResourceService>; + let navigationService: Mock<NavigationService>; + let snackBarService: Mock<SnackBarService>; beforeEach(() => { - listService = mock(OrganisationsEinheitListResourceService); - service = new OrganisationsEinheitService(useFromMock(listService)); + listResourceService = mock(OrganisationsEinheitListResourceService); + resourceService = mock(OrganisationsEinheitResourceService); + + navigationService = mock(NavigationService); + navigationService.urlChanged.mockReturnValue(of({})); + + snackBarService = mock(SnackBarService); + service = new OrganisationsEinheitService( + useFromMock(listResourceService), + useFromMock(resourceService), + useFromMock(navigationService), + useFromMock(snackBarService), + ); + }); + + describe('onNavigation', () => { + it('should not call listResourceService select', () => { + service.onNavigation({}); + + expect(listResourceService.select).not.toHaveBeenCalled(); + }); + + it('should call listResourceService select', () => { + service.onNavigation({ [OrganisationsEinheitService.ORGANISATIONS_EINHEIT_URL]: 'some-uri' }); + + expect(listResourceService.select).toHaveBeenCalled(); + }); + + it('should call getOrganisationsEinheitUrl', () => { + (<any>service).getOrganisationsEinheitUrl = jest.fn(); + + service.onNavigation({ [OrganisationsEinheitService.ORGANISATIONS_EINHEIT_URL]: 'some-uri' }); + + expect((<any>service).getOrganisationsEinheitUrl).toHaveBeenCalled(); + }); + }); + + describe('getOrganisationsEinheitUrl', () => { + it('should call navigationService getDecodedParam', () => { + service.getOrganisationsEinheitUrl(); + + expect(navigationService.getDecodedParam).toHaveBeenCalled(); + }); + + it('should return uri', () => { + navigationService.getDecodedParam.mockReturnValue('some-uri'); + + expect(service.getOrganisationsEinheitUrl()).toBe('some-uri'); + }); }); describe('getList', () => { + const organisationsEinheitListResource: AdminOrganisationsEinheitListResource = createAdminOrganisationsEinheitListResource(); const organisationsEinheitStateListResource: StateResource<AdminOrganisationsEinheitListResource> = createStateResource(organisationsEinheitListResource); beforeEach(() => { - listService.getList.mockReturnValue(of(organisationsEinheitStateListResource)); + listResourceService.getList.mockReturnValue(of(organisationsEinheitStateListResource)); }); - it('should call listService', () => { + it('should call listResourceService', () => { service.getList(); - expect(listService.getList).toHaveBeenCalled(); + expect(listResourceService.getList).toHaveBeenCalled(); }); it('should return value', () => { @@ -40,4 +98,58 @@ describe('OrganisationsEinheitService', () => { expect(list$).toBeObservable(singleColdCompleted(organisationsEinheitStateListResource)); }); }); + + describe('get', () => { + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + const organisationsEinheitStateResource: StateResource<AdminOrganisationsEinheitResource> = + createStateResource(organisationsEinheitResource); + + beforeEach(() => { + resourceService.get.mockReturnValue(of(organisationsEinheitStateResource)); + }); + + it('should call resourceService', () => { + service.get(); + + expect(resourceService.get).toHaveBeenCalled(); + }); + + it('should return value', () => { + const resource$: Observable<StateResource<AdminOrganisationsEinheitResource>> = service.get(); + + expect(resource$).toBeObservable(singleColdCompleted(organisationsEinheitStateResource)); + }); + }); + + describe('patch', () => { + const organisationsEinheitResource: AdminOrganisationsEinheitResource = createAdminOrganisationsEinheitResource(); + const organisationsEinheitStateResource: StateResource<AdminOrganisationsEinheitResource> = + createStateResource(organisationsEinheitResource); + + beforeEach(() => { + resourceService.patch.mockReturnValue(of(organisationsEinheitStateResource)); + }); + + it('should call resourceService', () => { + service.patch(organisationsEinheitResource.settings).subscribe(); + + expect(resourceService.patch).toHaveBeenCalled(); + }); + + it('should call snackBarService showInfo', () => { + service.patch(organisationsEinheitResource.settings).subscribe(); + + expect(snackBarService.showInfo).toHaveBeenCalled(); + }); + + it('should return value', () => { + const resource$: Observable<StateResource<AdminOrganisationsEinheitResource>> = service.patch( + organisationsEinheitResource.settings, + ); + + resource$.subscribe((result: StateResource<AdminOrganisationsEinheitResource>) => { + expect(result).toEqual(organisationsEinheitStateResource); + }); + }); + }); }); diff --git a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.ts b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.ts index c3a8522eb4dac8c05cfc652e478d6068f8e879e5..d20e30cc1f824e8f912a624540ff8047b3771041 100644 --- a/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.ts +++ b/alfa-client/libs/admin/settings/src/lib/organisationseinheit/organisationseinheit.service.ts @@ -1,16 +1,81 @@ -import { AdminOrganisationsEinheitListResource } from '@admin-client/admin-settings'; -import { StateResource } from '@alfa-client/tech-shared'; +import { + AdminOrganisationsEinheitListResource, + AdminOrganisationsEinheitResource, + AdminOrganisationsEinheitSettings, +} from '@admin-client/admin-settings'; +import { NavigationService } from '@alfa-client/navigation-shared'; +import { StateResource, createEmptyStateResource, isNotUndefined } from '@alfa-client/tech-shared'; +import { SnackBarService } from '@alfa-client/ui'; import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Params } from '@angular/router'; +import { ResourceUri } from '@ngxp/rest'; +import { isNil } from 'lodash-es'; +import { Observable, Subscription, startWith, tap } from 'rxjs'; import { OrganisationsEinheitListResourceService } from './organisations-einheit-list-resource.service'; +import { OrganisationsEinheitResourceService } from './organisations-einheit-resource.service'; @Injectable({ providedIn: 'root', }) export class OrganisationsEinheitService { - constructor(private listService: OrganisationsEinheitListResourceService) {} + static ORGANISATIONS_EINHEIT_URL: string = 'organisationsEinheitUrl'; + + private subscription: Subscription; + + constructor( + private listResourceService: OrganisationsEinheitListResourceService, + private resourceService: OrganisationsEinheitResourceService, + private navigationService: NavigationService, + private snackBarService: SnackBarService, + ) { + this.listenToNavigation(); + } + + private listenToNavigation(): void { + this.unsubscribe(); + this.subscription = this.navigationService.urlChanged().subscribe((params: Params) => this.onNavigation(params)); + } + + private unsubscribe(): void { + if (!isNil(this.subscription)) { + this.subscription.unsubscribe(); + } + } + + onNavigation(params: Params): void { + if (this.navigateToOrganisationsEinheitDetailPage(params)) { + this.listResourceService.select(this.getOrganisationsEinheitUrl()); + } + } + + private navigateToOrganisationsEinheitDetailPage(params: Params): boolean { + return isNotUndefined(params[OrganisationsEinheitService.ORGANISATIONS_EINHEIT_URL]); + } + + getOrganisationsEinheitUrl(): ResourceUri { + return this.navigationService.getDecodedParam(OrganisationsEinheitService.ORGANISATIONS_EINHEIT_URL); + } public getList(): Observable<StateResource<AdminOrganisationsEinheitListResource>> { - return this.listService.getList(); + return this.listResourceService.getList(); + } + + public get(): Observable<StateResource<AdminOrganisationsEinheitResource>> { + return this.resourceService.get(); + } + + public patch( + organisationsEinheitSettings: AdminOrganisationsEinheitSettings, + ): Observable<StateResource<AdminOrganisationsEinheitResource>> { + return this.resourceService.patch(organisationsEinheitSettings).pipe( + tap((stateResource: StateResource<AdminOrganisationsEinheitResource>) => this.showInfoAfterPatch(stateResource)), + startWith(createEmptyStateResource<AdminOrganisationsEinheitResource>(true)), + ); + } + + private showInfoAfterPatch(stateResource: StateResource<AdminOrganisationsEinheitResource>) { + if (!stateResource.loading) { + this.snackBarService.showInfo('Die Signatur wurde erfolgreich gespeichert.'); + } } } diff --git a/alfa-client/libs/admin/settings/test/organisations-einheit/organisations-einheit.ts b/alfa-client/libs/admin/settings/test/organisations-einheit/organisations-einheit.ts index 654a21ec586c526125c306f7ab54b701eee8fda3..600e815386e1d52dce43597b5187f1310a1be713 100644 --- a/alfa-client/libs/admin/settings/test/organisations-einheit/organisations-einheit.ts +++ b/alfa-client/libs/admin/settings/test/organisations-einheit/organisations-einheit.ts @@ -5,23 +5,26 @@ import { AdminOrganisationsEinheit, AdminOrganisationsEinheitListResource, AdminOrganisationsEinheitResource, - SyncResult, + AdminOrganisationsEinheitSyncResult, } from '../../src'; import { OrganisationsEinheitListLinkRel } from '../../src/lib/organisationseinheit/organisations-einheit.linkrel'; -export function createAdminOrganisationsEinheit(syncResult?: SyncResult): AdminOrganisationsEinheit { +export function createAdminOrganisationsEinheit(syncResult?: AdminOrganisationsEinheitSyncResult): AdminOrganisationsEinheit { return { name: faker.random.word(), organisationsEinheitId: faker.random.word(), - syncResult: syncResult ?? SyncResult.OK, + syncResult: syncResult ?? AdminOrganisationsEinheitSyncResult.OK, settings: { signatur: faker.random.words(5), }, }; } -export function createAdminOrganisationsEinheitResource(linkRel: string[] = []): AdminOrganisationsEinheitResource { - return toResource(createAdminOrganisationsEinheit(), linkRel); +export function createAdminOrganisationsEinheitResource( + syncResult?: AdminOrganisationsEinheitSyncResult, + linkRel: string[] = [], +): AdminOrganisationsEinheitResource { + return toResource(createAdminOrganisationsEinheit(syncResult), linkRel); } export function createAdminOrganisationsEinheitResources(linkRelations: string[] = []): AdminOrganisationsEinheitResource[] { diff --git a/alfa-client/libs/command-shared/src/lib/command-resource.service.spec.ts b/alfa-client/libs/command-shared/src/lib/command-resource.service.spec.ts index 01af08536ee53c2b081a2da1a34d0823958d5e5c..282d49ecc2c827b47980b536a152681c9df26a3c 100644 --- a/alfa-client/libs/command-shared/src/lib/command-resource.service.spec.ts +++ b/alfa-client/libs/command-shared/src/lib/command-resource.service.spec.ts @@ -42,26 +42,31 @@ describe('CommandResourceService', () => { repository = mock(ResourceRepository); commandService = mock(CommandService); - service = new CommandResourceService( - config, - useFromMock(repository), - useFromMock(commandService), - ); + service = new CommandResourceService(config, useFromMock(repository), useFromMock(commandService)); }); it('should be created', () => { expect(service).toBeTruthy(); }); + describe('doSave', () => { + it('should throw error', () => { + expect(() => service.doSave(configResource, {})).toThrowError('Method not implemented.'); + }); + }); + + describe('doPatch', () => { + it('should throw error', () => { + expect(() => service.doPatch(configResource, {})).toThrowError('Method not implemented.'); + }); + }); + describe('delete', () => { const resourceWithDeleteLinkRel: Resource = createDummyResource([deleteLinkRel]); - const stateResourceWithDeleteLink: StateResource<Resource> = - createStateResource(resourceWithDeleteLinkRel); + const stateResourceWithDeleteLink: StateResource<Resource> = createStateResource(resourceWithDeleteLinkRel); beforeEach(() => { - commandService.createCommandByProps.mockReturnValue( - of(createStateResource(createCommandResource())), - ); + commandService.createCommandByProps.mockReturnValue(of(createStateResource(createCommandResource()))); service.stateResource.next(stateResourceWithDeleteLink); }); diff --git a/alfa-client/libs/command-shared/src/lib/command-resource.service.ts b/alfa-client/libs/command-shared/src/lib/command-resource.service.ts index 9a564442fd23862a0c85f645a6a93573ddca3612..4ec5a90e60890c26a04a3fd2d8aeed14794176d4 100644 --- a/alfa-client/libs/command-shared/src/lib/command-resource.service.ts +++ b/alfa-client/libs/command-shared/src/lib/command-resource.service.ts @@ -11,10 +11,7 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { CommandResource, CreateCommandProps } from './command.model'; import { CommandService } from './command.service'; -export class CommandResourceService<B extends Resource, T extends Resource> extends ResourceService< - B, - T -> { +export class CommandResourceService<B extends Resource, T extends Resource> extends ResourceService<B, T> { deleteStateCommandResource: BehaviorSubject<StateResource<CommandResource>> = new BehaviorSubject< StateResource<CommandResource> >(createEmptyStateResource()); @@ -31,6 +28,10 @@ export class CommandResourceService<B extends Resource, T extends Resource> exte throw new Error('Method not implemented.'); } + doPatch(resource: T, toPatch: unknown): Observable<T> { + throw new Error('Method not implemented.'); + } + public delete(): Observable<StateResource<CommandResource>> { return this.commandService.createCommandByProps(this.buildDeleteCommandProps()); } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.spec.ts index 977db3428026e075b535d45b53e6cdd9865579fb..e608186534a4f3573d8a78a4fdda739926d7c7fb 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.spec.ts @@ -91,4 +91,56 @@ describe('ApiResourceService', () => { expect(service.stateResource.value).toEqual(createStateResource(loadedResource)); })); }); + + describe('patch', () => { + const dummyToPatch: unknown = {}; + const loadedResource: Resource = createDummyResource(); + + const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]); + + it('should call repository', fakeAsync(() => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + repository.patch.mockReturnValue(of(loadedResource)); + + service.patch(dummyToPatch).subscribe(); + tick(); + + const expectedSaveResourceData: SaveResourceData<Resource> = { + resource: resourceWithEditLinkRel, + linkRel: editLinkRel, + toSave: dummyToPatch, + }; + expect(repository.patch).toHaveBeenCalledWith(expectedSaveResourceData); + })); + + it('should return patched object', () => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + repository.patch.mockReturnValue(singleHot(loadedResource)); + + const saved: Observable<StateResource<Resource>> = service.patch(dummyToPatch); + + expect(saved).toBeObservable(singleCold(createStateResource(loadedResource))); + }); + + it('should call handleError', () => { + service.stateResource.next(createStateResource(createDummyResource([config.edit.linkRel]))); + const errorResponse: ProblemDetail = createProblemDetail(); + repository.patch.mockReturnValue(throwError(() => errorResponse)); + service.handleError = jest.fn(); + + service.patch(<any>{}).subscribe(); + + expect(service.handleError).toHaveBeenCalledWith(errorResponse); + }); + + it('should update state resource subject', fakeAsync(() => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + repository.patch.mockReturnValue(of(loadedResource)); + + service.patch(dummyToPatch).subscribe(); + tick(); + + expect(service.stateResource.value).toEqual(createStateResource(loadedResource)); + })); + }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.ts index d19c4a6fdc52813b270661c560bf76bed7f3ba24..f27414058aae8584e3ce77dd8668735232c33acd 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/api-resource.service.ts @@ -4,10 +4,7 @@ import { ResourceServiceConfig } from './resource.model'; import { ResourceRepository } from './resource.repository'; import { ResourceService } from './resource.service'; -export class ApiResourceService<B extends Resource, T extends Resource> extends ResourceService< - B, - T -> { +export class ApiResourceService<B extends Resource, T extends Resource> extends ResourceService<B, T> { constructor( protected config: ResourceServiceConfig<B>, protected repository: ResourceRepository, @@ -22,4 +19,12 @@ export class ApiResourceService<B extends Resource, T extends Resource> extends toSave, }); } + + doPatch(resource: T, toPatch: unknown): Observable<T> { + return <Observable<T>>this.repository.patch({ + resource, + linkRel: this.config.edit.linkRel, + toSave: toPatch, + }); + } } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts index 9983f8acf61e11a6c642761e7b0d6e8e3351c253..a2aa79b4339145dbe72c5d3bd4b0be155a9ab354 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.spec.ts @@ -4,29 +4,14 @@ import faker from '@faker-js/faker'; import { Resource, ResourceUri } from '@ngxp/rest'; import { cold } from 'jest-marbles'; import { DummyLinkRel, DummyListLinkRel } from 'libs/tech-shared/test/dummy'; -import { - createDummyListResource, - createDummyResource, - createFilledDummyListResource, -} from 'libs/tech-shared/test/resource'; +import { createDummyListResource, createDummyResource, createFilledDummyListResource } from 'libs/tech-shared/test/resource'; import { BehaviorSubject, Observable, of } from 'rxjs'; import { singleCold, singleHot } from '../../../test/marbles'; +import { EMPTY_ARRAY } from '../tech.util'; import { ResourceListService } from './list-resource.service'; -import { - CreateResourceData, - LinkRelationName, - ListItemResource, - ListResourceServiceConfig, -} from './resource.model'; +import { CreateResourceData, LinkRelationName, ListItemResource, ListResourceServiceConfig } from './resource.model'; import { ResourceRepository } from './resource.repository'; -import { - ListResource, - StateResource, - createEmptyStateResource, - createStateResource, -} from './resource.util'; - -import { EMPTY_ARRAY } from '../tech.util'; +import { ListResource, StateResource, createEmptyStateResource, createStateResource } from './resource.util'; import * as ResourceUtil from './resource.util'; @@ -42,9 +27,9 @@ describe('ListResourceService', () => { const baseResource: Resource = createDummyResource(); const baseStateResource: StateResource<Resource> = createStateResource(baseResource); - const baseResourceSubj: BehaviorSubject<StateResource<Resource>> = new BehaviorSubject< - StateResource<Resource> - >(baseStateResource); + const baseResourceSubj: BehaviorSubject<StateResource<Resource>> = new BehaviorSubject<StateResource<Resource>>( + baseStateResource, + ); beforeEach(() => { config = { @@ -63,8 +48,7 @@ describe('ListResourceService', () => { }); describe('getList', () => { - const listStateResource: StateResource<ListResource> = - createStateResource(createDummyListResource()); + const listStateResource: StateResource<ListResource> = createStateResource(createDummyListResource()); let isInvalidResourceCombinationSpy: jest.SpyInstance; @@ -73,9 +57,7 @@ describe('ListResourceService', () => { service.handleNullConfigResource = jest.fn(); service.handleChanges = jest.fn(); - isInvalidResourceCombinationSpy = jest - .spyOn(ResourceUtil, 'isInvalidResourceCombination') - .mockReturnValue(true); + isInvalidResourceCombinationSpy = jest.spyOn(ResourceUtil, 'isInvalidResourceCombination').mockReturnValue(true); }); it('should handle config resource changed', fakeAsync(() => { @@ -104,15 +86,12 @@ describe('ListResourceService', () => { const apiRootStateResource$: Observable<StateResource<Resource>> = service.getList(); - expect(apiRootStateResource$).toBeObservable( - cold('a', { a: createEmptyStateResource(true) }), - ); + expect(apiRootStateResource$).toBeObservable(cold('a', { a: createEmptyStateResource(true) })); }); }); describe('handle changes', () => { - const listStateResource: StateResource<ListResource> = - createStateResource(createDummyListResource()); + const listStateResource: StateResource<ListResource> = createStateResource(createDummyListResource()); const changedConfigResource: Resource = createDummyResource(); describe('on different config resource', () => { @@ -127,8 +106,7 @@ describe('ListResourceService', () => { }); describe('on same config resource', () => { - const listStateResource: StateResource<ListResource> = - createStateResource(createDummyListResource()); + const listStateResource: StateResource<ListResource> = createStateResource(createDummyListResource()); beforeEach(() => { service.baseResource = baseResource; @@ -210,8 +188,7 @@ describe('ListResourceService', () => { it('should keep current current list resource on unstable state resource', () => { jest.spyOn(ResourceUtil, 'isStateResoureStable').mockReturnValue(false); - const currentListStateResource: StateResource<ListResource> = - createStateResource(createDummyListResource()); + const currentListStateResource: StateResource<ListResource> = createStateResource(createDummyListResource()); service.listResource.next(currentListStateResource); const configResuorceWithoutLink: Resource = createDummyListResource(); @@ -318,18 +295,6 @@ describe('ListResourceService', () => { resourceRepository.getResource.mockReturnValue(of(loadedResource)); }); - it('should throw error if listResource is not valid', () => { - service.listResource.next(createEmptyStateResource()); - - expect(() => service.select(selfHref)).toThrowError('No list resource available.'); - }); - - it('should throw error if uri not exists in list resource', () => { - expect(() => service.select('uriNotExistsInListResource')).toThrowError( - 'No entry match with given uri.', - ); - }); - it('should set resource loading', () => { service.setSelectedResourceLoading = jest.fn(); @@ -375,8 +340,7 @@ describe('ListResourceService', () => { describe('getSelected', () => { it('should return selected resource', (done) => { - const dummyStateResource: StateResource<Resource> = - createStateResource(createDummyResource()); + const dummyStateResource: StateResource<Resource> = createStateResource(createDummyResource()); service.getSelected().subscribe((selected) => { expect(selected).toEqual(dummyStateResource); @@ -411,10 +375,7 @@ describe('ListResourceService', () => { service.prev(); - expect(resourceRepository.getListResource).toHaveBeenCalledWith( - listResource, - service.prevLink, - ); + expect(resourceRepository.getListResource).toHaveBeenCalledWith(listResource, service.prevLink); }); it('should update listResource', () => { @@ -443,10 +404,7 @@ describe('ListResourceService', () => { service.next(); - expect(resourceRepository.getListResource).toHaveBeenCalledWith( - listResourceWithPrevLink, - service.nextLink, - ); + expect(resourceRepository.getListResource).toHaveBeenCalledWith(listResourceWithPrevLink, service.nextLink); }); it('should update listResource', () => { @@ -485,9 +443,7 @@ describe('ListResourceService', () => { describe('get items', () => { const listResourceItems: ListItemResource[] = [createDummyResource()]; - const stateListResource: StateResource<ListResource> = createStateResource( - createFilledDummyListResource(listResourceItems), - ); + const stateListResource: StateResource<ListResource> = createStateResource(createFilledDummyListResource(listResourceItems)); let getListSpy: jest.SpyInstance; beforeEach(() => { diff --git a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts index 68dc6359c0b71963d75b8305ce16ad3616e58f8f..4bb6beecc38df96224fa5adf1b4ede5ed4729039 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/list-resource.service.ts @@ -115,7 +115,6 @@ export class ResourceListService<B extends Resource, T extends ListResource, I e } public select(uri: ResourceUri): void { - this.verifyBeforeSelection(uri); this.setSelectedResourceLoading(); this.repository .getResource(uri) diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts index 2c8c97bfd4304f4bad5545a11cbe97dcb8a7258f..dfbdeb0e8b3889808c4839004012f82e65b89875 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts @@ -13,7 +13,7 @@ describe('ResourceRepository', () => { let repository: ResourceRepository; let resourceFactory = mock(ResourceFactory); - let resourceWrapper = { get: jest.fn(), post: jest.fn(), put: jest.fn(), delete: jest.fn() }; + let resourceWrapper = { get: jest.fn(), post: jest.fn(), put: jest.fn(), patch: jest.fn(), delete: jest.fn() }; beforeEach(() => { resourceFactory.from.mockReturnValue(resourceWrapper); @@ -42,9 +42,7 @@ describe('ResourceRepository', () => { it('should call get url without parameter', () => { repository.getListResource(baseResource, listLinkRel); - expect(repository.getUrlWithoutParameter).toHaveBeenCalledWith( - getUrl(baseResource, listLinkRel), - ); + expect(repository.getUrlWithoutParameter).toHaveBeenCalledWith(getUrl(baseResource, listLinkRel)); }); it('should call resourceFactory with uri', () => { @@ -131,7 +129,7 @@ describe('ResourceRepository', () => { expect(resourceFactory.from).toHaveBeenCalledWith(resource); }); - it('should call resourceWrapper with linkel and object to create', () => { + it('should call resourceWrapper with linkRel and object to create', () => { repository.createResource(createResourceData); expect(resourceWrapper.post).toHaveBeenCalledWith(linkRel, toCreate); @@ -162,7 +160,7 @@ describe('ResourceRepository', () => { expect(resourceFactory.from).toHaveBeenCalledWith(resource); }); - it('should call resourceWrapper with linkel and object to save', () => { + it('should call resourceWrapper with linkRel and object to save', () => { repository.save(saveResourceData); expect(resourceWrapper.put).toHaveBeenCalledWith(linkRel, toSave); @@ -175,6 +173,37 @@ describe('ResourceRepository', () => { }); }); + describe('patch', () => { + const toPatch: unknown = {}; + const linkRel: string = DummyLinkRel.DUMMY; + const resource: Resource = createDummyResource([linkRel]); + const patchResourceData: SaveResourceData<Resource> = { toSave: toPatch, linkRel, resource }; + + const patchedResource: Resource = createDummyResource(); + + beforeEach(() => { + resourceWrapper.patch.mockReturnValue(singleCold(patchedResource)); + }); + + it('should call resourceFactory with resource', () => { + repository.patch(patchResourceData); + + expect(resourceFactory.from).toHaveBeenCalledWith(resource); + }); + + it('should call resourceWrapper with linkRel and object to patch', () => { + repository.patch(patchResourceData); + + expect(resourceWrapper.patch).toHaveBeenCalledWith(linkRel, toPatch); + }); + + it('should return value', () => { + const result: Observable<Resource> = repository.patch(patchResourceData); + + expect(result).toBeObservable(singleHot(patchedResource)); + }); + }); + describe('delete', () => { const deleteLinkRel: LinkRelationName = faker.random.word(); const resourceToDelete: Resource = createDummyResource([deleteLinkRel]); @@ -191,7 +220,7 @@ describe('ResourceRepository', () => { expect(resourceFactory.from).toHaveBeenCalledWith(resourceToDelete); }); - it('should call resourceWrapper with linkel', () => { + it('should call resourceWrapper with linkRel', () => { repository.delete(resourceToDelete, deleteLinkRel); expect(resourceWrapper.delete).toHaveBeenCalledWith(deleteLinkRel); @@ -218,10 +247,7 @@ describe('ResourceRepository', () => { repository.search(dummyResource, linkRel, searchBy); - expect(repository.buildSearchUri).toHaveBeenCalledWith( - new URL(getUrl(dummyResource, linkRel)), - searchBy, - ); + expect(repository.buildSearchUri).toHaveBeenCalledWith(new URL(getUrl(dummyResource, linkRel)), searchBy); }); it('should call resourceFactory', () => { diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts index b0fcb3995f0961983b8caef53160762ad3fc4416..fe199d6e0f55c932f385ecfa4d3dac96dd930230 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts @@ -23,9 +23,7 @@ export class ResourceRepository { } public createResource(createResourceData: CreateResourceData<Resource>): Observable<Resource> { - return this.resourceFactory - .from(createResourceData.resource) - .post(createResourceData.linkRel, createResourceData.toCreate); + return this.resourceFactory.from(createResourceData.resource).post(createResourceData.linkRel, createResourceData.toCreate); } public getResource<T>(uri: ResourceUri): Observable<T> { @@ -33,9 +31,11 @@ export class ResourceRepository { } public save(saveResourceData: SaveResourceData<Resource>): Observable<Resource> { - return this.resourceFactory - .from(saveResourceData.resource) - .put(saveResourceData.linkRel, saveResourceData.toSave); + return this.resourceFactory.from(saveResourceData.resource).put(saveResourceData.linkRel, saveResourceData.toSave); + } + + public patch(patchResourceData: SaveResourceData<Resource>): Observable<Resource> { + return this.resourceFactory.from(patchResourceData.resource).patch(patchResourceData.linkRel, patchResourceData.toSave); } public delete(resource: Resource, linkRel: LinkRelationName): Observable<Resource> { @@ -43,9 +43,7 @@ export class ResourceRepository { } public search<T>(resource: Resource, linkRel: LinkRelationName, searchBy: string): Observable<T> { - return this.resourceFactory - .fromId(this.buildSearchUri(new URL(getUrl(resource, linkRel)), searchBy)) - .get(); + return this.resourceFactory.fromId(this.buildSearchUri(new URL(getUrl(resource, linkRel)), searchBy)).get(); } buildSearchUri(url: URL, searchBy: string): ResourceUri { diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts index 61f1ae622b0c4621da50136718665d0c5a94756e..1a7c4254c31475dde3488c115c80245b758d0145 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts @@ -12,12 +12,7 @@ import { ProblemDetail } from '../tech.model'; import { LinkRelationName, ResourceServiceConfig } from './resource.model'; import { ResourceRepository } from './resource.repository'; import { ResourceService } from './resource.service'; -import { - StateResource, - createEmptyStateResource, - createErrorStateResource, - createStateResource, -} from './resource.util'; +import { StateResource, createEmptyStateResource, createErrorStateResource, createStateResource } from './resource.util'; import * as ResourceUtil from './resource.util'; @@ -62,9 +57,7 @@ describe('ResourceService', () => { service.stateResource.next(stateResource); service.handleResourceChanges = jest.fn(); - isInvalidResourceCombinationSpy = jest - .spyOn(ResourceUtil, 'isInvalidResourceCombination') - .mockReturnValue(true); + isInvalidResourceCombinationSpy = jest.spyOn(ResourceUtil, 'isInvalidResourceCombination').mockReturnValue(true); }); it('should handle config resource changed', fakeAsync(() => { @@ -82,15 +75,11 @@ describe('ResourceService', () => { })); it('should return initial value', () => { - service.stateResource.asObservable = jest - .fn() - .mockReturnValue(singleHot(stateResource, '-a')); + service.stateResource.asObservable = jest.fn().mockReturnValue(singleHot(stateResource, '-a')); const apiRootStateResource$: Observable<StateResource<Resource>> = service.get(); - expect(apiRootStateResource$).toBeObservable( - cold('a', { a: createEmptyStateResource(true) }), - ); + expect(apiRootStateResource$).toBeObservable(cold('a', { a: createEmptyStateResource(true) })); }); }); @@ -167,10 +156,7 @@ describe('ResourceService', () => { it('should update stateresource by configresource', () => { service.handleResourceChanges(stateResource, configResource); - expect(service.updateStateResourceByConfigResource).toHaveBeenCalledWith( - stateResource, - configResource, - ); + expect(service.updateStateResourceByConfigResource).toHaveBeenCalledWith(stateResource, configResource); }); }); @@ -235,10 +221,7 @@ describe('ResourceService', () => { }); it('should return true if configresource has no get link', () => { - const shouldClear: boolean = service.shouldClearStateResource( - dummyStateResource, - createDummyResource(), - ); + const shouldClear: boolean = service.shouldClearStateResource(dummyStateResource, createDummyResource()); expect(shouldClear).toBeTruthy(); }); @@ -246,19 +229,13 @@ describe('ResourceService', () => { describe('on empty stateresource', () => { it('should return false', () => { - const shouldClear: boolean = service.shouldClearStateResource( - createEmptyStateResource(), - null, - ); + const shouldClear: boolean = service.shouldClearStateResource(createEmptyStateResource(), null); expect(shouldClear).toBeFalsy(); }); it('should return false if configresource has no get link', () => { - const shouldClear: boolean = service.shouldClearStateResource( - createEmptyStateResource(), - createDummyResource(), - ); + const shouldClear: boolean = service.shouldClearStateResource(createEmptyStateResource(), createDummyResource()); expect(shouldClear).toBeFalsy(); }); @@ -306,9 +283,7 @@ describe('ResourceService', () => { service.loadResource(configResourceWithGetLinkRel); - expect(service.doLoadResource).toHaveBeenCalledWith( - getUrl(configResourceWithGetLinkRel, config.getLinkRel), - ); + expect(service.doLoadResource).toHaveBeenCalledWith(getUrl(configResourceWithGetLinkRel, config.getLinkRel)); }); }); @@ -394,9 +369,7 @@ describe('ResourceService', () => { it('should do save', fakeAsync(() => { const stateResource: StateResource<Resource> = createStateResource(resourceWithEditLinkRel); service.stateResource.next(stateResource); - const doSaveMock: jest.Mock = (service.doSave = jest.fn()).mockReturnValue( - of(loadedResource), - ); + const doSaveMock: jest.Mock = (service.doSave = jest.fn()).mockReturnValue(of(loadedResource)); service.save(dummyToSave).subscribe(); tick(); @@ -435,16 +408,62 @@ describe('ResourceService', () => { })); }); + describe('patch', () => { + const dummyToPatch: unknown = {}; + const loadedResource: Resource = createDummyResource(); + + const resourceWithEditLinkRel: Resource = createDummyResource([editLinkRel]); + + it('should do patch', fakeAsync(() => { + const stateResource: StateResource<Resource> = createStateResource(resourceWithEditLinkRel); + service.stateResource.next(stateResource); + const doPatchMock: jest.Mock = (service.doPatch = jest.fn()).mockReturnValue(of(loadedResource)); + + service.patch(dummyToPatch).subscribe(); + tick(); + + expect(doPatchMock).toHaveBeenCalledWith(resourceWithEditLinkRel, dummyToPatch); + })); + + it('should return patched object', () => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + service.doPatch = jest.fn().mockReturnValue(singleHot(loadedResource)); + + const patched: Observable<StateResource<Resource>> = service.patch(dummyToPatch); + + expect(patched).toBeObservable(singleCold(createStateResource(loadedResource))); + }); + + it('should call handleError', () => { + service.stateResource.next(createStateResource(createDummyResource([config.edit.linkRel]))); + const errorResponse: ProblemDetail = createProblemDetail(); + service.doPatch = jest.fn().mockReturnValue(throwError(() => errorResponse)); + service.handleError = jest.fn(); + + service.patch(<any>{}).subscribe(); + + expect(service.handleError).toHaveBeenCalledWith(errorResponse); + }); + + it('should update state resource subject', fakeAsync(() => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + service.doPatch = jest.fn().mockReturnValue(of(loadedResource)); + + service.patch(dummyToPatch).subscribe(); + tick(); + + expect(service.stateResource.value).toEqual(createStateResource(loadedResource)); + })); + }); + describe('handleError', () => { it('should return error stateresource on problem unprocessable entity', (done: jest.DoneCallback) => { const error: ProblemDetail = createProblemDetail(); - service - .handleError(<HttpErrorResponse>(<any>error)) - .subscribe((responseError: StateResource<unknown>) => { - expect(responseError).toEqual(createErrorStateResource(error)); - done(); - }); + service.handleError(<HttpErrorResponse>(<any>error)).subscribe((responseError: StateResource<unknown>) => { + expect(responseError).toEqual(createErrorStateResource(error)); + done(); + }); }); it('should rethrow error', () => { @@ -503,10 +522,7 @@ describe('ResourceService', () => { }); }); -export class DummyResourceService<B extends Resource, T extends Resource> extends ResourceService< - B, - T -> { +export class DummyResourceService<B extends Resource, T extends Resource> extends ResourceService<B, T> { constructor( protected config: ResourceServiceConfig<B>, protected repository: ResourceRepository, @@ -517,4 +533,8 @@ export class DummyResourceService<B extends Resource, T extends Resource> extend doSave(resource: T, toSave: unknown): Observable<T> { return of(resource); } + + doPatch(resource: T, toPatch: unknown): Observable<T> { + return of(resource); + } } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts index 6c9eb0a56f13fb81555b915c436119b742b2ec45..02abe975e853fe01912920f1bff836914c4779aa 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts @@ -1,19 +1,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { getUrl, hasLink, Resource, ResourceUri } from '@ngxp/rest'; import { isEqual, isNull } from 'lodash-es'; -import { - BehaviorSubject, - catchError, - combineLatest, - filter, - first, - map, - Observable, - of, - startWith, - tap, - throwError, -} from 'rxjs'; +import { BehaviorSubject, catchError, combineLatest, filter, first, map, Observable, of, startWith, tap, throwError } from 'rxjs'; import { isUnprocessableEntity } from '../http.util'; import { HttpError } from '../tech.model'; import { isNotNull } from '../tech.util'; @@ -35,9 +23,7 @@ import { * T = Type of the resource which is working on */ export abstract class ResourceService<B extends Resource, T extends Resource> { - readonly stateResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject( - createEmptyStateResource(), - ); + readonly stateResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject(createEmptyStateResource()); configResource: B = null; @@ -48,12 +34,8 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { public get(): Observable<StateResource<T>> { return combineLatest([this.stateResource.asObservable(), this.getConfigResource()]).pipe( - tap(([stateResource, configResource]) => - this.handleResourceChanges(stateResource, configResource), - ), - filter( - ([stateResource]) => !isInvalidResourceCombination(stateResource, this.configResource), - ), + tap(([stateResource, configResource]) => this.handleResourceChanges(stateResource, configResource)), + filter(([stateResource]) => !isInvalidResourceCombination(stateResource, this.configResource)), mapToFirst<T, B>(), startWith(createEmptyStateResource<T>(true)), ); @@ -61,10 +43,7 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { private getConfigResource(): Observable<B> { return this.config.resource.pipe( - filter( - (configStateResource: StateResource<B>) => - !configStateResource.loading && !configStateResource.reload, - ), + filter((configStateResource: StateResource<B>) => !configStateResource.loading && !configStateResource.reload), mapToResource<B>(), ); } @@ -93,10 +72,7 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { } shouldClearStateResource(stateResource: StateResource<T>, configResource: B): boolean { - return ( - (isNull(configResource) || this.hasNotGetLink(configResource)) && - !this.isStateResourceEmpty(stateResource) - ); + return (isNull(configResource) || this.hasNotGetLink(configResource)) && !this.isStateResourceEmpty(stateResource); } private hasNotGetLink(configResource: B): boolean { @@ -152,6 +128,15 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { ); } + public patch(toPatch: unknown): Observable<StateResource<T>> { + const previousResource: T = this.stateResource.value.resource; + return this.doPatch(previousResource, toPatch).pipe( + tap((loadedResource: T) => this.stateResource.next(createStateResource(loadedResource))), + map(() => this.stateResource.value), + catchError((errorResponse: HttpErrorResponse) => this.handleError(errorResponse)), + ); + } + handleError(errorResponse: HttpErrorResponse): Observable<StateResource<T>> { if (isUnprocessableEntity(errorResponse.status)) { return of(createErrorStateResource((<any>errorResponse) as HttpError)); @@ -161,6 +146,8 @@ export abstract class ResourceService<B extends Resource, T extends Resource> { abstract doSave(resource: T, toSave: unknown): Observable<T>; + abstract doPatch(resource: T, toPatch: unknown): Observable<T>; + public refresh(): void { this.stateResource.next({ ...this.stateResource.value, reload: true }); }