Skip to content
Snippets Groups Projects
Verified Commit 14aa7326 authored by Sebastian Bergandy's avatar Sebastian Bergandy :keyboard:
Browse files

Merge branch 'main' into OZG-7726-e2e-weiterleiten-poststelle-ea

# Conflicts:
#	alfa-client/apps/alfa-e2e/src/e2e/einheitlicher-ansprechpartner/vorgang-detail/vorgang-forward.ts
parents 7132656c 49828171
No related branches found
No related tags found
1 merge request!96OZG-7726 add e2e tests
Showing
with 305 additions and 57 deletions
......@@ -25,7 +25,7 @@ export class VorgangFormularButtonsE2EComponent {
private readonly abschliessenButton: string = 'abschliessen-button';
private readonly annehmenButton: string = 'annehmen-button';
private readonly bearbeitenButton: string = 'bearbeiten-button';
private readonly forwardButton: string = 'forward-by-ozgcloud-button';
private readonly forwardButton: string = 'forwarding-button';
private readonly bescheidenButton: string = 'bescheiden-button';
private readonly endgueltigLoeschenButton: string = 'endgueltig-loeschen-button';
private readonly loeschAnforderungZuruecknehmenButton: string = 'loesch-anforderung-zuruecknehmen-button';
......
<admin-delete-open-dialog-button />
\ No newline at end of file
import { AdminDeleteOpenDialogButtonComponent } from '@admin-client/shared';
import { DIALOG_COMPONENT } from '@alfa-client/ui';
import { Component } from '@angular/core';
import { UserDeleteDialogContainerComponent } from '../user-form-delete-dialog-container/user-delete-dialog-container.component';
@Component({
selector: 'admin-user-form-delete-container-button',
standalone: true,
imports: [AdminDeleteOpenDialogButtonComponent],
providers: [{ provide: DIALOG_COMPONENT, useValue: UserDeleteDialogContainerComponent }],
templateUrl: './user-form-delete-button-container.component.html',
})
export class UserFormDeleteButtonContainerComponent {}
......@@ -35,7 +35,7 @@
<div class="flex justify-between">
<admin-user-form-save-button />
@if (formService.isPatch()) {
<admin-user-form-delete-container-button data-test-id="delete-button-container"/>
<admin-delete-open-dialog-button data-test-id="delete-button-container"/>
}
</div>
</div>
......
......@@ -21,6 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { AdminDeleteOpenDialogButtonComponent } from '@admin-client/shared';
import { User } from '@admin-client/user-shared';
import { createEmptyStateResource, createStateResource, StateResource } from '@alfa-client/tech-shared';
import { existsAsHtmlElement, getMockComponent, mock, Mock, notExistsAsHtmlElement } from '@alfa-client/test-utils';
......@@ -68,6 +69,7 @@ describe('UserFormComponent', () => {
MockComponent(UserFormOrganisationsEinheitListComponent),
MockComponent(UserFormRolesComponent),
MockComponent(UserFormHeadlineComponent),
MockComponent(AdminDeleteOpenDialogButtonComponent),
],
providers: [{ provide: DIALOG_COMPONENT, useValue: {} }],
})
......
......@@ -21,15 +21,17 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { AdminDeleteOpenDialogButtonComponent } from '@admin-client/shared';
import { User } from '@admin-client/user-shared';
import { StateResource } from '@alfa-client/tech-shared';
import { DIALOG_COMPONENT } from '@alfa-client/ui';
import { AsyncPipe } from '@angular/common';
import { Component, inject, OnInit } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SpinnerComponent } from '@ods/component';
import { Observable } from 'rxjs';
import { UserFormDataComponent } from './user-form-data/user-form-data.component';
import { UserFormDeleteButtonContainerComponent } from './user-form-delete-button/user-form-delete-button-container.component';
import { UserDeleteDialogContainerComponent } from './user-form-delete-dialog-container/user-delete-dialog-container.component';
import { UserFormHeadlineComponent } from './user-form-headline/user-form-headline.component';
import { UserFormOrganisationsEinheitListComponent } from './user-form-organisations-einheit-list/user-form-organisations-einheit-list.component';
import { UserFormRolesComponent } from './user-form-roles/user-form-roles.component';
......@@ -38,7 +40,7 @@ import { UserFormService } from './user.formservice';
@Component({
selector: 'admin-user-form',
providers: [UserFormService],
providers: [UserFormService, { provide: DIALOG_COMPONENT, useValue: UserDeleteDialogContainerComponent }],
templateUrl: './user-form.component.html',
standalone: true,
imports: [
......@@ -51,7 +53,7 @@ import { UserFormService } from './user.formservice';
UserFormOrganisationsEinheitListComponent,
UserFormHeadlineComponent,
UserFormSaveButtonComponent,
UserFormDeleteButtonContainerComponent,
AdminDeleteOpenDialogButtonComponent,
],
})
export class UserFormComponent implements OnInit {
......
......@@ -44,6 +44,7 @@ type ButtonVariants = VariantProps<typeof buttonVariants>;
[size]="size"
[dataTestId]="dataTestId"
[isLoading]="isLoading"
[disabled]="disabled"
(click)="clickEmitter.emit()"
>
<ng-content icon select="[icon]" />
......@@ -57,6 +58,7 @@ export class ButtonWithSpinnerComponent {
@Input() dataTestId: string = '';
@Input() variant: ButtonVariants['variant'] = 'primary';
@Input() size: ButtonVariants['size'] = 'medium';
@Input() disabled: boolean = false;
@Output() public clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
......
import { dispatchEventFromFixture, mock, Mock, MockEvent } from '@alfa-client/test-utils';
import { dispatchEventFromFixture, existsAsHtmlElement, mock, Mock, MockEvent } from '@alfa-client/test-utils';
import { OzgcloudDialogService } from '@alfa-client/ui';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ButtonComponent } from '@ods/system';
......@@ -12,7 +12,8 @@ describe('CancelDialogButtonComponent', () => {
let dialogService: Mock<OzgcloudDialogService>;
const cancelDialog: string = getDataTestIdOf('cancel-dialog');
const cancelDialogButton: string = getDataTestIdOf('cancel-dialog-button');
const cancelDialogIconButton: string = getDataTestIdOf('cancel-dialog-icon-button');
beforeEach(async () => {
dialogService = mock(OzgcloudDialogService);
......@@ -39,9 +40,51 @@ describe('CancelDialogButtonComponent', () => {
describe('on button click', () => {
it('should call dialog service to close all', () => {
dispatchEventFromFixture(fixture, cancelDialog, MockEvent.CLICK);
dispatchEventFromFixture(fixture, cancelDialogButton, MockEvent.CLICK);
expect(dialogService.closeAll).toHaveBeenCalled();
});
});
describe('template', () => {
describe('button', () => {
beforeEach(() => {
component.showAsIconButton = false;
fixture.detectChanges();
});
it('should be visible when showAsIconButton is false', () => {
existsAsHtmlElement(fixture, cancelDialogButton);
});
it('should call dialog service to close all', () => {
dispatchEventFromFixture(fixture, cancelDialogButton, MockEvent.CLICK);
expect(dialogService.closeAll).toHaveBeenCalled();
});
});
describe('icon button', () => {
beforeEach(() => {
component.showAsIconButton = true;
fixture.detectChanges();
});
it('should be visible when showAsIconButton is true', () => {
component.showAsIconButton = true;
fixture.detectChanges();
existsAsHtmlElement(fixture, cancelDialogIconButton);
});
it('should call dialog service to close all', () => {
dispatchEventFromFixture(fixture, cancelDialogIconButton, MockEvent.CLICK);
expect(dialogService.closeAll).toHaveBeenCalled();
});
});
});
});
import { OzgcloudDialogService } from '@alfa-client/ui';
import { Component, inject } from '@angular/core';
import { ButtonComponent } from '@ods/system';
import { Component, inject, Input } from '@angular/core';
import { ButtonComponent, CloseIconComponent, TooltipDirective } from '@ods/system';
@Component({
selector: 'ods-cancel-dialog-button',
standalone: true,
imports: [ButtonComponent],
template: `<ods-button
imports: [ButtonComponent, CloseIconComponent, TooltipDirective],
template: ` @if (showAsIconButton) {
<ods-button
(clickEmitter)="cancel()"
[tooltip]="'Schließen'"
tooltipAriaType="aria-labelledby"
variant="ghost"
size="fit"
dataTestId="cancel-dialog-icon-button"
data-test-id="cancel-dialog-icon-button"
>
<ng-container icon>
<ods-close-icon class="fill-primary" />
</ng-container>
</ods-button>
} @else {
<ods-button
(clickEmitter)="cancel()"
variant="outline"
text="Abbrechen"
dataTestId="cancel-dialog"
data-test-id="cancel-dialog"
/>`,
dataTestId="cancel-dialog-button"
data-test-id="cancel-dialog-button"
/>
}`,
})
export class CancelDialogButtonComponent {
public readonly dialogService = inject(OzgcloudDialogService);
@Input() showAsIconButton: boolean = false;
public cancel(): void {
this.dialogService.closeAll();
}
......
......@@ -41,6 +41,7 @@ export * from './lib/form/file-upload-button/file-upload-button.component';
export * from './lib/form/radio-button-card/radio-button-card.component';
export * from './lib/form/text-input/text-input.component';
export * from './lib/form/textarea/textarea.component';
export * from './lib/forwarding-item/forwarding-item.component';
export * from './lib/icons/accessibility-icon/accessibility-icon.component';
export * from './lib/icons/account-circle-icon/account-circle-icon.component';
export * from './lib/icons/admin-logo-icon/admin-logo-icon.component';
......
......@@ -88,12 +88,16 @@ describe('TextInputComponent', () => {
it('should focus', () => {
component.focus = true;
component.ngAfterViewInit();
expect(component.inputElement.nativeElement.focus).toHaveBeenCalled();
});
it('should not focus', () => {
component.focus = false;
component.ngAfterViewInit();
expect(component.inputElement.nativeElement.focus).not.toHaveBeenCalled();
});
});
......
......@@ -21,9 +21,9 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { convertForDataTest, ConvertForDataTestPipe, EMPTY_STRING, isNotUndefined } from '@alfa-client/tech-shared';
import { convertForDataTest, EMPTY_STRING, isNotUndefined } from '@alfa-client/tech-shared';
import { CommonModule } from '@angular/common';
import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { cva, VariantProps } from 'class-variance-authority';
import { uniqueId } from 'lodash-es';
......@@ -51,7 +51,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
@Component({
selector: 'ods-text-input',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, ConvertForDataTestPipe],
imports: [CommonModule, ReactiveFormsModule],
template: `
<div class="relative">
<label *ngIf="showLabel" [for]="id" class="text-md mb-2 block font-medium text-text">
......@@ -82,7 +82,7 @@ type TextInputVariants = VariantProps<typeof textInputVariants>;
</div>
`,
})
export class TextInputComponent {
export class TextInputComponent implements AfterViewInit {
@ViewChild('inputElement') inputElement: ElementRef;
@Input({ required: true }) set label(label: string) {
......@@ -98,20 +98,19 @@ export class TextInputComponent {
@Input() withPrefix: boolean = false;
@Input() withSuffix: boolean = false;
@Input() showLabel: boolean = true;
@Input() focus: boolean = false;
@Input() set dataTestId(value: string) {
if (isNotUndefined(value)) this._dataTestId = value;
}
@Input() set focus(value: boolean) {
if (value && this.inputElement) {
this.inputElement.nativeElement.focus();
}
}
@Output() clickEmitter: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
inputLabel: string;
id: string;
_dataTestId: string;
textInputVariants = textInputVariants;
ngAfterViewInit() {
if (this.focus) this.inputElement.nativeElement.focus();
}
}
/*
* Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ForwardingItemComponent } from './forwarding-item.component';
describe('ForwardingItemComponent', () => {
let component: ForwardingItemComponent;
let fixture: ComponentFixture<ForwardingItemComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ForwardingItemComponent],
}).compileComponents();
fixture = TestBed.createComponent(ForwardingItemComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
/*
* Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { ForwardVorgangIconComponent } from '../icons/forward-vorgang-icon/forward-vorgang-icon.component';
export enum ForwardingDirection {
FROM = 'Weitergeleitet von',
TO = 'An',
}
@Component({
selector: 'ods-forwarding-item',
standalone: true,
imports: [CommonModule, ForwardVorgangIconComponent],
template: `<div
class="flex flex-col items-start justify-between gap-2 rounded-lg border border-grayborder p-3 md:flex-row md:items-center md:gap-0"
>
<div class="flex gap-3">
<ods-forward-vorgang-icon class="fill-text" />
<p class="text-gray-500">{{ direction }}:</p>
<div>
<p class="font-medium">{{ label }}</p>
<p>{{ address }}</p>
</div>
</div>
<div class="text-end empty:hidden">
<ng-content />
</div>
</div>`,
})
export class ForwardingItemComponent {
@Input({ required: true }) label!: string;
@Input({ required: true }) address!: string;
@Input() direction: ForwardingDirection = ForwardingDirection.TO;
}
/*
* Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
import { argsToTemplate, moduleMetadata, type Meta, type StoryObj } from '@storybook/angular';
import { ButtonComponent } from '../button/button.component';
import { ForwardingDirection, ForwardingItemComponent } from './forwarding-item.component';
const label: string = 'Bund für Umwelt und Naturschutz Kreisgruppe Kiel';
const address: string = 'Kaiserstraße 25, 12443 Kiel';
const meta: Meta<ForwardingItemComponent> = {
title: 'Forwarding item',
component: ForwardingItemComponent,
parameters: {
docs: {
description: {
component: 'Information of forwarded item',
},
},
},
decorators: [
moduleMetadata({
imports: [ForwardingItemComponent, ButtonComponent],
}),
],
excludeStories: /.*Data$/,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<ForwardingItemComponent>;
export const Default: Story = {
args: {
label: label,
address: address,
direction: ForwardingDirection.TO,
},
argTypes: {
direction: { control: 'select', options: [ForwardingDirection.TO, ForwardingDirection.FROM] },
},
};
export const WithButton: Story = {
args: {
label: label,
address: address,
},
render: (args: ForwardingItemComponent) => ({
props: args,
template: `<ods-forwarding-item ${argsToTemplate(args)}>
<ods-button variant="outline" text="Stelle ändern" />
</ods-forwarding-item>`,
}),
};
export const WithCreationInfo: Story = {
args: {
label: label,
address: address,
direction: ForwardingDirection.FROM,
},
render: (args: ForwardingItemComponent) => ({
props: args,
template: `<ods-forwarding-item ${argsToTemplate(args)}>
<p>20. Dez. 09:35</p>
<p class="text-sm">Karin Wanowski-Müller</p>
</ods-forwarding-item>`,
}),
};
......@@ -62,6 +62,7 @@ import { InstantSearchQuery, InstantSearchResult } from './instant-search.model'
[placeholder]="placeholder"
[attr.aria-expanded]="results.length"
[control]="control"
[focusOnInput]="focusOnSearchField"
aria-controls="results"
(inputClicked)="showResults()"
(searchQueryCleared)="searchQueryCleared.emit()"
......@@ -90,6 +91,7 @@ export class InstantSearchComponent implements OnInit, OnDestroy {
@Input() placeholder: string = EMPTY_STRING;
@Input() headerText: string = EMPTY_STRING;
@Input() control: FormControl<string> = new FormControl(EMPTY_STRING);
@Input() focusOnSearchField: boolean = false;
@Input() set searchResults(searchResults: InstantSearchResult<Resource>[]) {
if (!isEqual(searchResults, this.results) && isNotNil(searchResults)) {
......
......@@ -37,6 +37,7 @@ import { SearchIconComponent } from '../../icons/search-icon/search-icon.compone
label="instant search"
[fieldControl]="control"
[placeholder]="placeholder"
[focus]="focusOnInput"
[withPrefix]="true"
[withSuffix]="true"
[showLabel]="false"
......@@ -44,13 +45,7 @@ import { SearchIconComponent } from '../../icons/search-icon/search-icon.compone
role="combobox"
>
<ods-search-icon prefix aria-hidden="true" aria-label="Suchfeld" />
<button
suffix
*ngIf="control.value"
(click)="clearInput()"
aria-label="Eingabe löschen"
data-test-id="clear-instant-search"
>
<button suffix *ngIf="control.value" (click)="clearInput()" aria-label="Eingabe löschen" data-test-id="clear-instant-search">
<ods-close-icon class="fill-primary hover:fill-primary-hover" />
</button>
</ods-text-input>`,
......@@ -58,6 +53,7 @@ import { SearchIconComponent } from '../../icons/search-icon/search-icon.compone
export class SearchFieldComponent {
@Input() placeholder: string = EMPTY_STRING;
@Input() control: FormControl<string> = new FormControl(EMPTY_STRING);
@Input() focusOnInput: boolean = false;
@Output() inputClicked: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
@Output() searchQueryCleared: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
......
......@@ -21,6 +21,6 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
export * from './lib/forward-by-ozgcloud-button-container/forward-by-ozgcloud-button-container.component';
export * from './lib/forwarding-button-container/forwarding-button-container.component';
export * from './lib/forwarding-by-email-container/forwarding-by-email-container.component';
export * from './lib/forwarding.module';
export * from './lib/vorgang-forwarding-container/vorgang-forwarding-container.component';
@if (vorgangWithEingang | hasLink: vorgangWithEingangLinkRel.FORWARD_BY_OZGCLOUD) {
@if (showAsIconButton) {
<ods-button-with-spinner
<ods-open-dialog-button
[tooltip]="'Vorgang weiterleiten'"
tooltipAriaType="aria-labelledby"
variant="ghost"
size="fit"
dataTestId="forward-by-ozgcloud-icon-button"
dataTestId="forwarding-icon-button"
>
<ods-forward-vorgang-icon icon class="fill-text" />
</ods-button-with-spinner>
</ods-open-dialog-button>
} @else {
<ods-button-with-spinner text="Weiterleiten" variant="outline" dataTestId="forward-by-ozgcloud-button">
<ods-forward-vorgang-icon icon />
</ods-button-with-spinner>
<ods-open-dialog-button label="Weiterleiten" variant="outline" dataTestId="forwarding-button">
<ods-forward-vorgang-icon icon class="fill-primary" />
</ods-open-dialog-button>
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment