Skip to content
Snippets Groups Projects
Commit 64151fae authored by Martin's avatar Martin
Browse files

Merge branch 'OZG-7507-admin-organisationsEinheit-loeschen' of...

Merge branch 'OZG-7507-admin-organisationsEinheit-loeschen' of code.schleswig-holstein.de:ozg-cloud/app/frontend-clients into OZG-7507-admin-organisationsEinheit-loeschen
parents e3729e3d d42d528d
No related branches found
No related tags found
1 merge request!82OZG-7507 use new open dialog button
Showing
with 209 additions and 82 deletions
......@@ -24,14 +24,20 @@
import 'cypress-real-events';
import { convertToDataTestId } from '../../support/tech-util';
//TODO BenutzerListPage erstellen welche den Button und die Liste enthaelt.
export class BenutzerListE2EComponent {
private readonly headline: string = 'user-list-headline';
private readonly list: string = 'user-list';
private readonly benutzerHinzufuegenButton: string = 'add-user-button';
public getHeadline(): Cypress.Chainable<Element> {
return cy.getTestElement(this.headline);
}
public getList(): Cypress.Chainable<Element> {
return cy.getTestElement(this.list);
}
public getHinzufuegenButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.benutzerHinzufuegenButton);
}
......@@ -98,10 +104,16 @@ export class BenutzerE2EComponent {
private readonly loeschenCheckbox: string = 'Loschen-checkbox-editor';
private readonly userCheckbox: string = 'User-checkbox-editor';
private readonly postCheckbox: string = 'Poststelle-checkbox-editor';
private readonly datenbeauftragungCheckbox: string = 'Datenbeauftragung-checkbox-editor';
private readonly organisationsEinheitCheckboxSuffix: string = '-checkbox-editor';
private readonly saveButton: string = 'save-button';
private readonly deleteButton: string = 'delete-button';
public getHeadline(): Cypress.Chainable<Element> {
return cy.getTestElement(this.headline);
}
public getVornameInput(): Cypress.Chainable<Element> {
return cy.getTestElement(this.userVorname);
......@@ -135,15 +147,32 @@ export class BenutzerE2EComponent {
return cy.getTestElement(this.postCheckbox);
}
public getSaveButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.saveButton);
public getDatenbeauftragungCheckbox(): Cypress.Chainable<Element> {
return cy.getTestElement(this.datenbeauftragungCheckbox);
}
public getOrganisationsEinheitCheckbox(einheit: string): Cypress.Chainable<Element> {
return cy.getTestElement(einheit + this.organisationsEinheitCheckboxSuffix);
}
public getHeadline(): Cypress.Chainable<Element> {
return cy.getTestElement(this.headline);
public getSaveButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.saveButton);
}
public getDeleteButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.deleteButton);
}
}
export class BenutzerDeleteDialogE2EComponent {
private readonly deleteButton: string = 'dialog-delete';
private readonly cancelButton: string = 'cancel-dialog';
public getCancelButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.cancelButton);
}
public getDeleteButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.deleteButton);
}
}
import { faker } from '@faker-js/faker';
import { E2EBenutzerHelper } from 'apps/admin-e2e/src/helper/benutzer/benutzer.helper';
import { E2EBenutzerVerifier } from 'apps/admin-e2e/src/helper/benutzer/benutzer.verifier';
import { AdminUserE2E } from 'apps/admin-e2e/src/model/util';
import { loginAsAriane } from 'apps/admin-e2e/src/support/user-util';
describe('Benutzer Löschen', () => {
const benutzerVerifier: E2EBenutzerVerifier = new E2EBenutzerVerifier();
const benutzerHelper: E2EBenutzerHelper = new E2EBenutzerHelper();
const userName: string = 'testtheo' + faker.string.uuid();
const user: AdminUserE2E = {
vorname: 'Theo',
nachname: 'Testuser',
username: userName,
email: 'theo' + faker.string.uuid() + '@ozg-sh.de',
isUser: true,
organisationseinheiten: [],
};
before(() => {
loginAsAriane();
});
it('should delete user', () => {
benutzerHelper.openNewBenutzerPage();
benutzerHelper.addBenutzerAndSave(user);
benutzerHelper.deleteBenutzer(userName);
benutzerVerifier.verifyUserNotInList(userName);
});
});
import { BenutzerE2EComponent } from '../../components/benutzer/benutzer.e2e.component';
import {
BenutzerDeleteDialogE2EComponent,
BenutzerE2EComponent,
BenutzerListE2EComponent,
} from '../../components/benutzer/benutzer.e2e.component';
import { SnackBarE2EComponent } from '../../components/ui/snackbar.e2e.component';
import { OrganisationsEinheitE2E } from '../../model/organisations-einheit';
import { AdminUserE2E } from '../../model/util';
......@@ -7,6 +11,8 @@ import { exist, notExist } from '../../support/cypress.util';
export class E2EBenutzerExecutor {
private benutzerPage: BenutzerE2EComponent = new BenutzerE2EComponent();
private snackBar: SnackBarE2EComponent = new SnackBarE2EComponent();
private benutzerDeleteDialog: BenutzerDeleteDialogE2EComponent = new BenutzerDeleteDialogE2EComponent();
private benutzerListPage: BenutzerListE2EComponent = new BenutzerListE2EComponent();
public modifyBenutzer(user: AdminUserE2E): void {
this.benutzerPage.getVornameInput().type(user.vorname);
......@@ -39,9 +45,17 @@ export class E2EBenutzerExecutor {
exist(this.snackBar.getMessage());
this.snackBar.getCloseButton().click();
notExist(this.snackBar.getMessage());
exist(this.benutzerListPage.getList());
}
public saveBenutzer(): void {
this.benutzerPage.getSaveButton().click();
}
public deleteBenutzer(): void {
this.benutzerPage.getDeleteButton().click();
exist(this.benutzerDeleteDialog.getDeleteButton());
this.benutzerDeleteDialog.getDeleteButton().click();
exist(this.benutzerListPage.getList());
}
}
......@@ -17,32 +17,45 @@ export class E2EBenutzerHelper {
public addBenutzerAndSave(user: AdminUserE2E): void {
this.addBenutzer(user);
this.executer.saveAndCloseSnackbar();
this.saveAndCloseSnackbar();
}
public addBenutzer(user: AdminUserE2E): void {
this.executer.modifyBenutzer(user);
}
public openBenutzerPage(userName: string): void {
this.navigator.openBenutzerPage(userName);
this.modifyBenutzer(user);
}
public editBenutzerAndSave(user: AdminUserE2E): void {
this.editBenutzer(user);
this.executer.saveAndCloseSnackbar();
this.saveAndCloseSnackbar();
}
public editBenutzer(user: AdminUserE2E): void {
this.modifyBenutzer(user);
}
private modifyBenutzer(user: AdminUserE2E): void {
this.executer.modifyBenutzer(user);
}
public editOrganisationsEinheitenAndSave(organisationsEinheiten: OrganisationsEinheitE2E[]): void {
this.executer.modifyOrganisationsEinheiten(organisationsEinheiten);
this.saveAndCloseSnackbar();
}
private saveAndCloseSnackbar(): void {
this.executer.saveAndCloseSnackbar();
}
public saveBenutzer(): void {
this.executer.saveBenutzer();
}
public deleteBenutzer(userName: string): void {
this.openBenutzerPage(userName);
this.executer.deleteBenutzer();
}
public openBenutzerPage(userName: string): void {
this.navigator.openBenutzerPage(userName);
}
}
......@@ -4,7 +4,7 @@ import {
BenutzerListItemE2EComponent,
} from '../../components/benutzer/benutzer.e2e.component';
import { AdminUserE2E } from '../../model/util';
import { contains, exist } from '../../support/cypress.util';
import { contains, exist, notExist } from '../../support/cypress.util';
import { AlfaRollen } from '../../support/user-util';
export class E2EBenutzerVerifier {
......@@ -41,6 +41,10 @@ export class E2EBenutzerVerifier {
if (user.isAdmin) contains(benutzer.getRoles(), AlfaRollen.ADMIN);
}
public verifyUserNotInList(userName: string): void {
notExist(this.getBenutzerItem(userName).getRoot());
}
private getBenutzerItem(userName: string): BenutzerListItemE2EComponent {
return this.benutzerListPage.getItem(userName);
}
......
......@@ -49,15 +49,16 @@ describe('KeycloakResourceService', () => {
});
describe('getAll', () => {
const stateResource: StateResource<unknown> = createStateResource([]);
beforeEach(() => {
service.handleChanges = jest.fn();
service.handleChanges = jest.fn().mockReturnValue(singleCold(stateResource));
});
it('should return stateResource as observable', (done) => {
service.getAll().subscribe((stateResource) => {
expect(stateResource).toBe(service.stateResource.value);
done();
});
it('should return stateResource', () => {
const stateResource$: Observable<StateResource<unknown[]>> = service.getAll();
expect(stateResource$).toBeObservable(singleCold(stateResource));
});
it('should call handleChanges ', fakeAsync(() => {
......@@ -68,12 +69,25 @@ describe('KeycloakResourceService', () => {
});
describe('handleChanges', () => {
it('should call doIfLoadingRequired', () => {
const doIfLoadingRequired: jest.SpyInstance<boolean> = jest.spyOn(ResourceUtil, 'doIfLoadingRequired');
let doIfLoadingRequiredSpy: jest.SpyInstance<boolean>;
beforeEach(() => {
doIfLoadingRequiredSpy = jest.spyOn(ResourceUtil, 'doIfLoadingRequired').mockImplementation();
});
it('should call doIfLoadingRequired', () => {
service.handleChanges(emptyStateResource);
expect(doIfLoadingRequired).toHaveBeenCalled();
expect(doIfLoadingRequiredSpy).toHaveBeenCalled();
});
it('should return stateResource', (done) => {
service.stateResource.next(createStateResource([]));
service.handleChanges(emptyStateResource).subscribe((stateResource: StateResource<[]>) => {
expect(stateResource).toEqual(createStateResource([]));
done();
});
});
});
......
......@@ -22,22 +22,20 @@
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { createEmptyStateResource, createStateResource, doIfLoadingRequired, StateResource } from '@alfa-client/tech-shared';
import { BehaviorSubject, first, map, Observable, startWith, tap } from 'rxjs';
import { BehaviorSubject, first, map, Observable, startWith, switchMap, tap } from 'rxjs';
export abstract class KeycloakResourceService<T> {
readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject({
...createStateResource<T[]>([]),
loaded: false,
});
readonly stateResource: BehaviorSubject<StateResource<T[]>> = new BehaviorSubject(createEmptyStateResource());
public getAll(): Observable<StateResource<T[]>> {
return this.stateResource
.asObservable()
.pipe(tap((stateResource: StateResource<T[]>): void => this.handleChanges(stateResource)));
.pipe(switchMap((stateResource: StateResource<T[]>) => this.handleChanges(stateResource)));
}
handleChanges(stateResource: StateResource<T[]>): void {
handleChanges(stateResource: StateResource<T[]>): Observable<StateResource<T[]>> {
doIfLoadingRequired(stateResource, (): void => this.loadResource());
return this.stateResource.asObservable();
}
loadResource(): void {
......
......@@ -26,7 +26,7 @@
<ods-list data-test-id="organisations-einheit-list">
@for (organisationsEinheit of organisationsEinheitList; track $index) {
<ods-list-item [attr.data-test-id]="(organisationsEinheit.name | convertForDataTest) + '-organisation-item'">
<div class="space-between flex w-full">
<div class="space-between flex w-full items-center">
<dl class="flex-1 basis-3/4 font-semibold">
<dt class="sr-only">Name</dt>
<dd data-test-id="organisations-einheit-name">{{ organisationsEinheit.name }}</dd>
......
......@@ -26,8 +26,13 @@ import { ROUTES } from '@admin-client/shared';
import { User, UserService } from '@admin-client/user-shared';
import { PatchConfig } from '@admin/keycloak-shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared';
import { Mock, mock } from '@alfa-client/test-utils';
import {
createEmptyStateResource,
createLoadingStateResource,
createStateResource,
StateResource,
} from '@alfa-client/tech-shared';
import { Mock, mock, mockWindowError } from '@alfa-client/test-utils';
import { SnackBarService } from '@alfa-client/ui';
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { AbstractControl, FormControl, FormGroup, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
......@@ -159,7 +164,13 @@ describe('UserFormService', () => {
});
describe('initOrganisationsEinheiten', () => {
beforeEach(() => {
formService._addOrganisationsEinheitenToForm = jest.fn();
});
it('should call adminOrganisationsEinheitService getAll', () => {
formService._initOrganisationsEinheiten();
expect(adminOrganisationsEinheitService.getAll).toHaveBeenCalled();
});
......@@ -170,8 +181,6 @@ describe('UserFormService', () => {
});
it('should call addOrganisationsEinheitenToForm ', fakeAsync(() => {
formService._addOrganisationsEinheitenToForm = jest.fn();
formService._initOrganisationsEinheiten().subscribe();
tick();
......@@ -189,6 +198,15 @@ describe('UserFormService', () => {
it('should set initOrganisationsEinheiten$', () => {
expect(formService['_initOrganisationsEinheiten$']).toBeDefined();
});
it('should not throw any exception on loading state resource', () => {
adminOrganisationsEinheitService.getAll.mockReturnValue(of(createLoadingStateResource()));
const errorMock: any = mockWindowError();
formService._initOrganisationsEinheiten();
expect(errorMock).not.toHaveBeenCalled();
});
});
describe('addOrganisationsEinheitenToForm', () => {
......
......@@ -26,7 +26,7 @@ import { ROUTES } from '@admin-client/shared';
import { User, UserService } from '@admin-client/user-shared';
import { KeycloakFormService, PatchConfig } from '@admin/keycloak-shared';
import { NavigationService } from '@alfa-client/navigation-shared';
import { createEmptyStateResource, EMPTY_STRING, mapToResource, StateResource } from '@alfa-client/tech-shared';
import { createEmptyStateResource, EMPTY_STRING, isLoaded, mapToResource, StateResource } from '@alfa-client/tech-shared';
import { SnackBarService } from '@alfa-client/ui';
import { Injectable, OnDestroy } from '@angular/core';
import {
......@@ -39,7 +39,7 @@ import {
Validators,
} from '@angular/forms';
import { UrlSegment } from '@angular/router';
import { catchError, Observable, of, Subscription, tap } from 'rxjs';
import { catchError, filter, Observable, of, Subscription, tap } from 'rxjs';
@Injectable()
export class UserFormService extends KeycloakFormService<User> implements OnDestroy {
......@@ -137,6 +137,7 @@ export class UserFormService extends KeycloakFormService<User> implements OnDest
_initOrganisationsEinheiten(): Observable<AdminOrganisationsEinheit[]> {
const organisationsEinheitenGroup: UntypedFormGroup = this.getOrganisationsEinheitenGroup();
return this.adminOrganisationsEinheitService.getAll().pipe(
filter(isLoaded),
mapToResource<AdminOrganisationsEinheit[]>(),
tap((organisationsEinheiten: AdminOrganisationsEinheit[]): void => {
this.setOrganisationsEinheitenIdsInMap(organisationsEinheiten);
......
......@@ -20,6 +20,7 @@ describe('UserListContainerComponent', () => {
userService = {
...mock(UserService),
getAll: jest.fn().mockReturnValue(usersStateResource$),
refresh: jest.fn(),
};
await TestBed.configureTestingModule({
......@@ -55,4 +56,12 @@ describe('UserListContainerComponent', () => {
expect(userList.usersStateResource).toBe(usersStateResource);
});
});
describe('on destroy', () => {
it('should call service to refresh list', () => {
component.ngOnDestroy();
expect(userService.refresh).toHaveBeenCalled();
});
});
});
import { User, UserService } from '@admin-client/user-shared';
import { createEmptyStateResource, StateResource } from '@alfa-client/tech-shared';
import { StateResource } from '@alfa-client/tech-shared';
import { AsyncPipe } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { UserListComponent } from './user-list/user-list.component';
@Component({
......@@ -11,12 +11,16 @@ import { UserListComponent } from './user-list/user-list.component';
imports: [UserListComponent, AsyncPipe],
templateUrl: './user-list-container.component.html',
})
export class UserListContainerComponent implements OnInit {
export class UserListContainerComponent implements OnInit, OnDestroy {
private userService = inject(UserService);
public usersStateResource$: Observable<StateResource<User[]>> = of(createEmptyStateResource<User[]>());
public usersStateResource$: Observable<StateResource<User[]>>;
ngOnInit(): void {
this.usersStateResource$ = this.userService.getAll();
}
ngOnDestroy(): void {
this.userService.refresh();
}
}
......@@ -25,8 +25,10 @@
-->
<h1 class="heading-1 mb-4" data-test-id="user-list-headline">Benutzer & Rollen</h1>
<ods-routing-button [linkPath]="ROUTES.BENUTZER_NEU" text="Benutzer hinzufügen" class="mb-4 w-fit" dataTestId="add-user-button" />
<ods-list>
<ods-spinner [stateResource]="usersStateResource">
<ods-list data-test-id="user-list">
@for (user of usersStateResource.resource; track $index) {
<admin-user [user]="user" class="block w-full" />
}
</ods-list>
</ods-spinner>
......@@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared';
import { createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared';
import { getMockComponent } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RoutingButtonComponent } from '@ods/component';
import { RoutingButtonComponent, SpinnerComponent } from '@ods/component';
import { MockComponent } from 'ng-mocks';
import { createUser } from '../../../../../user-shared/test/user';
import { UserListComponent } from './user-list.component';
......@@ -38,8 +38,10 @@ describe('UsersListComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UserListComponent],
declarations: [MockComponent(RoutingButtonComponent), MockComponent(UserComponent)],
imports: [
UserListComponent,
[MockComponent(RoutingButtonComponent), MockComponent(UserComponent), MockComponent(SpinnerComponent)],
],
}).compileComponents();
fixture = TestBed.createComponent(UserListComponent);
......
......@@ -26,7 +26,7 @@ import { User } from '@admin-client/user-shared';
import { StateResource } from '@alfa-client/tech-shared';
import { AsyncPipe } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ButtonWithSpinnerComponent, RoutingButtonComponent } from '@ods/component';
import { ButtonWithSpinnerComponent, RoutingButtonComponent, SpinnerComponent } from '@ods/component';
import { ListComponent, ListItemComponent } from '@ods/system';
import { UserComponent } from './user/user.component';
......@@ -34,7 +34,15 @@ import { UserComponent } from './user/user.component';
selector: 'admin-user-list',
templateUrl: './user-list.component.html',
standalone: true,
imports: [ButtonWithSpinnerComponent, ListComponent, UserComponent, AsyncPipe, ListItemComponent, RoutingButtonComponent],
imports: [
ButtonWithSpinnerComponent,
ListComponent,
UserComponent,
AsyncPipe,
ListItemComponent,
RoutingButtonComponent,
SpinnerComponent,
],
})
export class UserListComponent {
@Input() usersStateResource: StateResource<User[]>;
......
import { OzgCloudComponentFactory } from '@alfa-client/tech-shared';
import {
createdClosedDialogRefMock,
dispatchEventFromFixture,
Mock,
mock,
MockEvent,
mockGetValue,
} from '@alfa-client/test-utils';
import { dispatchEventFromFixture, Mock, mock, MockEvent, mockGetValue } from '@alfa-client/test-utils';
import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui';
import { DIALOG_DATA } from '@angular/cdk/dialog';
import { ComponentType } from '@angular/cdk/portal';
......@@ -96,21 +89,6 @@ describe('OpenDialogButtonComponent', () => {
});
});
describe('open', () => {
beforeEach(() => {
component._createComponent = jest.fn().mockReturnValue(componentRef);
});
it('should emit close emitter on dialog close', () => {
component.close.emit = jest.fn();
dialogService.openInContext.mockReturnValue(createdClosedDialogRefMock());
component.open();
expect(component.close.emit).toHaveBeenCalled();
});
});
describe('create component', () => {
it('should call component factory to create component', () => {
component.dialogData = dummyDialogData;
......
......@@ -2,9 +2,8 @@ import { OzgCloudComponentFactory } from '@alfa-client/tech-shared';
import { DIALOG_COMPONENT, OzgcloudDialogService } from '@alfa-client/ui';
import { DIALOG_DATA } from '@angular/cdk/dialog';
import { ComponentType } from '@angular/cdk/portal';
import { Component, ComponentRef, EventEmitter, inject, Injector, Input, Output, ViewContainerRef } from '@angular/core';
import { Component, ComponentRef, inject, Injector, Input, ViewContainerRef } from '@angular/core';
import { ButtonComponent, ButtonVariants } from '@ods/system';
import { first } from 'rxjs';
@Component({
selector: 'ods-open-dialog-button',
......@@ -40,13 +39,8 @@ export class OpenDialogButtonComponent {
@Input() dialogData: any;
@Input() size: ButtonVariants['size'];
@Output() close: EventEmitter<void> = new EventEmitter();
public open(): void {
this.dialogService
.openInContext(this._createComponent().instance.constructor, this.viewContainerRef, this.dialogData)
.closed.pipe(first())
.subscribe(() => this.close.emit());
this.dialogService.openInContext(this._createComponent().instance.constructor, this.viewContainerRef, this.dialogData);
}
_createComponent(): ComponentRef<any> {
......
......@@ -53,3 +53,9 @@ export function mockGetValue(object: any, name: string, returnValue: any): void
get: jest.fn(() => returnValue),
});
}
export function mockWindowError(): any {
const errorHandler = jest.fn();
window.onerror = errorHandler;
return errorHandler;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment