diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts index 024419213edd7e67280c2fc9893d6864fd2469df..4ed37075df8da2351a1185a14cf626b5511da4ce 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-laden.cy.ts @@ -1,14 +1,14 @@ -import { OrganisationsEinheitenE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../page-objects/main.po'; -import { exist } from '../../support/cypress.util'; -import { OrganisationsEinheitSyncResultE2E } from '../../support/organisationseinheit'; +import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; +import { exist } from '../../../support/cypress.util'; +import { OrganisationsEinheitSyncResultE2E } from '../../../support/organisationseinheit'; import { createBauamtOrganisationsEinheit, createDenkmalpflegeOrganisationsEinheit, createFundstelleOrganisationsEinheit, initOrganisationsEinheiten, -} from '../../support/organisationseinheit-util'; -import { loginAsAriane } from '../../support/user-util'; +} from '../../../support/organisationseinheit-util'; +import { loginAsAriane } from '../../../support/user-util'; describe('show Organisationsheiten', () => { const mainPage: MainPage = new MainPage(); diff --git a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts index cd484bd20a56578cd03f599cbe2757c000b471ed..53cd8d900df150443bbd18e99c93b1ff5879fe8f 100644 --- a/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts +++ b/alfa-client/apps/admin-e2e/src/e2e/main-tests/organisationseinheiten/organisationseinheiten-signaturen.cy.ts @@ -1,8 +1,8 @@ -import { OrganisationseinheitenSignaturE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten-signatur.e2e.component'; -import { OrganisationsEinheitenE2EComponent } from '../../components/organisationseinheiten/organisationseinheiten.e2e.component'; -import { MainPage, waitForSpinnerToDisappear } from '../../page-objects/main.po'; -import { haveText } from '../../support/cypress.util'; -import { loginAsAriane } from '../../support/user-util'; +import { OrganisationseinheitenSignaturE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten-signatur.e2e.component'; +import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component'; +import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po'; +import { haveText } from '../../../support/cypress.util'; +import { loginAsAriane } from '../../../support/user-util'; describe('Signatur', () => { const mainPage: MainPage = new MainPage(); diff --git a/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml b/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml index ebb5ef1aa0f3ea98b137aa1b83401e4076e965ee..313812a726ff3f48bdab917e1b7ecfe8812cc3c0 100644 --- a/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml +++ b/alfa-client/apps/admin/src/main/helm/templates/deployment.yaml @@ -1,5 +1,5 @@ # -# Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den +# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den # Ministerpräsidenten des Landes Schleswig-Holstein # Staatskanzlei # Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -57,12 +57,12 @@ spec: labelSelector: matchLabels: app.kubernetes.io/name: {{ .Release.Name }} - + containers: - - env: + - env: - name: spring_profiles_active value: {{ include "app.envSpringProfiles" . }} - + {{- with include "app.getCustomList" . }} {{ . | indent 8 }} {{- end }} @@ -89,7 +89,17 @@ spec: periodSeconds: 10 successThreshold: 1 failureThreshold: 3 - + {{- if .Values.enableLivenessProbe }} + livenessProbe: + httpGet: + path: / + port: 8080 + scheme: HTTP + timeoutSeconds: 1 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + {{- end }} resources: {{- with .Values.resources }} {{ toYaml . | indent 10 }} @@ -105,12 +115,16 @@ spec: {{- with (.Values.securityContext).runAsGroup }} runAsGroup: {{ . }} {{- end }} + {{- with (.Values.securityContext).capabilities }} + capabilities: +{{ toYaml . | indent 12 }} + {{- end }} stdin: true terminationMessagePath: /dev/termination-log terminationMessagePolicy: File tty: true - - + + dnsConfig: {} dnsPolicy: ClusterFirst imagePullSecrets: @@ -121,5 +135,8 @@ spec: {{ toYaml . | indent 8 }} {{- end }} schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 + {{- with .Values.podSecurityContext }} + securityContext: +{{ toYaml . | indent 8 }} + {{- end }} + terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/alfa-client/apps/admin/src/main/helm/templates/service_account.yaml b/alfa-client/apps/admin/src/main/helm/templates/service_account.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3bac8e223d1fd108b386d1f06ed4e9fb2284a67c --- /dev/null +++ b/alfa-client/apps/admin/src/main/helm/templates/service_account.yaml @@ -0,0 +1,31 @@ +# +# Copyright (C) 2024 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. +# + +{{- if (.Values.serviceAccount).create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "app.serviceAccountName" . }} + namespace: {{ include "app.namespace" . }} +{{- end }} \ No newline at end of file diff --git a/src/test/helm/deployment_service_account_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml similarity index 81% rename from src/test/helm/deployment_service_account_test.yaml rename to alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml index 3bb33d1d8f3c8a173632a567a88b967863718197..98246ba981cba50008edd137c04040383faa5ecc 100644 --- a/src/test/helm/deployment_service_account_test.yaml +++ b/alfa-client/apps/admin/src/test/helm/deployment_service_account_test.yaml @@ -1,5 +1,5 @@ # -# Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den +# Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den # Ministerpräsidenten des Landes Schleswig-Holstein # Staatskanzlei # Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -24,19 +24,15 @@ suite: deployment service account release: - name: alfa + name: admin-client namespace: sh-helm-test templates: - templates/deployment.yaml set: - baseUrl: test.company.local ozgcloud: - environment: test - bundesland: sh - bezeichner: helm - sso: - serverUrl: https://sso.company.local - imagePullSecret: image-pull-secret + environment: dev + imagePullSecret: test-image-secret + tests: - it: should use service account with default name set: @@ -45,7 +41,7 @@ tests: asserts: - equal: path: spec.template.spec.serviceAccountName - value: alfa-service-account + value: admin-client-service-account - it: should use service account with name set: serviceAccount: @@ -58,4 +54,4 @@ tests: - it: should use default service account asserts: - isNull: - path: spec.template.spec.serviceAccountName + path: spec.template.spec.serviceAccountName \ No newline at end of file diff --git a/alfa-client/apps/admin/src/test/helm/service_account_test.yaml b/alfa-client/apps/admin/src/test/helm/service_account_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e80dde85e375a7ba052a403807c460969c9fdc4e --- /dev/null +++ b/alfa-client/apps/admin/src/test/helm/service_account_test.yaml @@ -0,0 +1,64 @@ +# +# Copyright (C) 2024 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. +# + +suite: test service account +release: + name: admin-client + namespace: sh-helm-test +templates: + - templates/service_account.yaml +tests: + - it: should create service account with default name + set: + serviceAccount: + create: true + asserts: + - isKind: + of: ServiceAccount + - isAPIVersion: + of: v1 + - equal: + path: metadata.name + value: admin-client-service-account + - equal: + path: metadata.namespace + value: sh-helm-test + - it: should create service account with name + set: + serviceAccount: + create: true + name: helm-service-account + asserts: + - isKind: + of: ServiceAccount + - equal: + path: metadata.name + value: helm-service-account + - equal: + path: metadata.namespace + value: sh-helm-test + - it: should not create service account + asserts: + - hasDocuments: + count: 0 \ No newline at end of file diff --git a/alfa-client/apps/alfa-e2e/src/fixtures/kommentar/kommentar.json b/alfa-client/apps/alfa-e2e/src/fixtures/kommentar/kommentar.json index f1bad2ba77535e6b15dc38f9b896a3ef04ec7b2e..66fe5c0999da2a440ff52a1378566bd6d6871d76 100644 --- a/alfa-client/apps/alfa-e2e/src/fixtures/kommentar/kommentar.json +++ b/alfa-client/apps/alfa-e2e/src/fixtures/kommentar/kommentar.json @@ -1,5 +1,6 @@ { "createdAt": "2024-01-10T12:57:35Z[UTC]", "createdBy": "SetInCode", - "text": "Test text to test the test text test" + "text": "Test text to test the test text test", + "attachments": null } diff --git a/alfa-client/apps/demo/src/app/app.component.html b/alfa-client/apps/demo/src/app/app.component.html index bae7d97a832d82ad6e91c77233ff11ea7eb6bc88..25e02f33b59d8a32db4332a88a6fef1d2e675c76 100644 --- a/alfa-client/apps/demo/src/app/app.component.html +++ b/alfa-client/apps/demo/src/app/app.component.html @@ -19,6 +19,7 @@ <a href="#" class="flex flex-col items-start justify-between gap-2 rounded-t-md border-primary-600/50 px-6 py-4 hover:bg-background-150 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus lg:flex-row lg:gap-6" + tooltip="This is tooltip attached to link element" > <div class="flex-1 basis-5/6"> <div class="flex flex-wrap items-center gap-x-3"> @@ -396,7 +397,7 @@ </form> <app-bescheid-dialog-button></app-bescheid-dialog-button> <div class="my-4 flex gap-4"> - <ods-button text="Button 1" /> + <ods-button text="Button 1" tooltip="Sample tooltip" /> <ods-button size="medium" [isLoading]="true" text="Button 2" /> <ods-button type="outline" text="Button 3" /> </div> diff --git a/alfa-client/apps/demo/src/app/app.component.ts b/alfa-client/apps/demo/src/app/app.component.ts index d2ac3257386258a3bd111f893c0afc1616c666cc..eef4a756761f2ad91604ffd019aa645a2c97b9f6 100644 --- a/alfa-client/apps/demo/src/app/app.component.ts +++ b/alfa-client/apps/demo/src/app/app.component.ts @@ -15,10 +15,8 @@ import { CloseIconComponent, ErrorMessageComponent, FieldsetComponent, - FileIconComponent, FileUploadButtonComponent, InstantSearchComponent, - OfficeIconComponent, RadioButtonCardComponent, SaveIconComponent, SendIconComponent, @@ -26,6 +24,7 @@ import { StampIconComponent, TextInputComponent, TextareaComponent, + TooltipDirective, } from '@ods/system'; import { EMPTY_STRING } from '@alfa-client/tech-shared'; @@ -35,8 +34,6 @@ import { InstantSearchResult, } from 'libs/design-system/src/lib/instant-search/instant-search/instant-search.model'; import { BescheidDialogExampleComponent } from './components/bescheid-dialog/bescheid-dialog.component'; -import { BescheidPaperComponent } from './components/bescheid-paper/bescheid-paper.component'; -import { BescheidStepperComponent } from './components/bescheid-stepper/bescheid-stepper.component'; import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.component'; @Component({ @@ -54,12 +51,9 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com CdkStepperModule, CustomStepperComponent, BescheidDialogExampleComponent, - BescheidStepperComponent, - BescheidPaperComponent, RadioButtonCardComponent, ReactiveFormsModule, InstantSearchComponent, - OfficeIconComponent, SaveIconComponent, SendIconComponent, StampIconComponent, @@ -68,11 +62,11 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com BescheidGenerateIconComponent, BescheidUploadIconComponent, SpinnerIconComponent, - FileIconComponent, TextareaComponent, TextInputComponent, TextareaComponent, ErrorMessageComponent, + TooltipDirective, ], selector: 'app-root', templateUrl: './app.component.html', diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts index d64022f37b2b81e968b4bed34e2eed7d5741ccae..055062c48ce72b9d037dea89020b24bd0b706fae 100644 --- a/alfa-client/libs/design-system/src/index.ts +++ b/alfa-client/libs/design-system/src/index.ts @@ -47,3 +47,4 @@ export * from './lib/list/list.component'; export * from './lib/navbar/nav-item/nav-item.component'; export * from './lib/navbar/navbar/navbar.component'; export * from './lib/testbtn/testbtn.component'; +export * from './lib/tooltip/tooltip.directive'; diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.spec.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc39cb7ceafa17c9865a7390f9ab98e2298bf127 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TooltipComponent } from './tooltip.component'; + +describe('TooltipComponent', () => { + let component: TooltipComponent; + let fixture: ComponentFixture<TooltipComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TooltipComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(TooltipComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3ae962af474a3cf0a5cd2aa6744996e65383611 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ods-tooltip', + template: `<p + class="fixed z-50 mt-2 -translate-x-1/2 animate-fadeIn cursor-default rounded bg-ozggray-900 px-3 py-2 text-sm text-whitetext before:absolute before:-top-2 before:left-[calc(50%-0.5rem)] before:size-0 before:border-b-8 before:border-l-8 before:border-r-8 before:border-b-ozggray-900 before:border-l-transparent before:border-r-transparent before:content-[''] dark:bg-white dark:before:border-b-white" + [style.left]="left + 'px'" + [style.top]="top + 'px'" + [attr.id]="id" + role="tooltip" + > + {{ text }} + </p>`, + styles: [':host {@apply contents}'], + standalone: true, +}) +export class TooltipComponent { + text: string = ''; + left: number = 0; + top: number = 0; + id: string; +} diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..07895edc7c9aa5622948043a663ea3c940e0375b --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.spec.ts @@ -0,0 +1,221 @@ +import { InteractivityChecker } from '@angular/cdk/a11y'; +import { ComponentRef, ElementRef, Renderer2, ViewContainerRef } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { TooltipComponent } from './tooltip.component'; +import { TooltipDirective } from './tooltip.directive'; + +class MockElementRef extends ElementRef { + nativeElement = { + contains: jest.fn(), + appendChild: jest.fn(), + }; +} + +describe('TooltipDirective', () => { + let directive: TooltipDirective; + const mockComponentRef: ComponentRef<TooltipComponent> = { + setInput: jest.fn(), + destroy: jest.fn(), + onDestroy: jest.fn(), + componentType: TooltipComponent, + changeDetectorRef: null, + location: null, + hostView: null, + injector: null, + instance: { id: '', left: 0, top: 0, text: '' }, + }; + + beforeEach((): void => { + TestBed.configureTestingModule({ + providers: [ViewContainerRef, { provide: ElementRef, useClass: MockElementRef }, Renderer2, InteractivityChecker], + }); + TestBed.runInInjectionContext(() => { + directive = new TooltipDirective(); + }); + }); + + it('should create a directive', () => { + expect(directive).toBeTruthy(); + }); + + describe('ngOnDestroy', () => { + it('should destroy tooltip', () => { + directive.destroy = jest.fn(); + + directive.ngOnDestroy(); + + expect(directive.destroy).toHaveBeenCalled(); + }); + }); + + describe('createTooltip', () => { + beforeEach(() => { + directive.viewContainerRef.createComponent = jest.fn().mockReturnValue({ location: { nativeElement: {} } }); + directive.setDescribedBy = jest.fn(); + directive.setTooltipProperties = jest.fn(); + }); + + it('should create tooltip component', () => { + directive.createTooltip(); + + expect(directive.viewContainerRef.createComponent).toHaveBeenCalled(); + }); + + it('should insert tooltip component to parent', () => { + directive.createTooltip(); + + expect(directive.elementRef.nativeElement.appendChild).toHaveBeenCalled(); + }); + + it('should set aria described by attribute to parent', () => { + directive.createTooltip(); + + expect(directive.setDescribedBy).toHaveBeenCalled(); + }); + + it('should set tooltip properties', () => { + directive.createTooltip(); + + expect(directive.setTooltipProperties).toHaveBeenCalled(); + }); + }); + + describe('destroyTooltip', () => { + it('should destroy tooltip', () => { + directive.destroy = jest.fn(); + + directive.destroyTooltip(); + + expect(directive.destroy).toHaveBeenCalled(); + }); + }); + + describe('onKeydown', () => { + it('should destroy tooltip if escape key pressed', () => { + directive.destroy = jest.fn(); + const escapeEvent: KeyboardEvent = { ...new KeyboardEvent('esc'), key: 'Escape' }; + + directive.onKeydown(escapeEvent); + + expect(directive.destroy).toHaveBeenCalled(); + }); + }); + + describe('setTooltipProperties', () => { + beforeEach(() => { + directive.componentRef = mockComponentRef; + directive.elementRef.nativeElement.getBoundingClientRect = jest + .fn() + .mockReturnValue({ left: 0, right: 1000, bottom: 1000 }); + }); + + it('should get bounding client rect', () => { + directive.setTooltipProperties(); + + expect(directive.elementRef.nativeElement.getBoundingClientRect).toHaveBeenCalled(); + }); + + it('should set tooltip instance properties', () => { + directive.tooltip = 'I am tooltip'; + directive.tooltipId = 'tooltip-1'; + + directive.setTooltipProperties(); + + expect(directive.componentRef.instance).toStrictEqual({ + id: 'tooltip-1', + left: 500, + text: 'I am tooltip', + top: 1000, + }); + }); + + it('should add margin if parent element focused', () => { + directive.setTooltipProperties(true); + + expect(directive.componentRef.instance.top).toBe(1004); + }); + }); + + describe('setDescribedBy', () => { + beforeEach(() => { + directive.getFocusableElement = jest.fn(); + directive.renderer.setAttribute = jest.fn(); + directive.interactivityChecker.isFocusable = jest.fn(); + }); + + it('should check if parent element focusable', () => { + directive.setDescribedBy(); + + expect(directive.interactivityChecker.isFocusable).toHaveBeenCalled(); + }); + + it('should get focusable element if parent not focusable', () => { + directive.setDescribedBy(); + + expect(directive.getFocusableElement).toHaveBeenCalled(); + }); + + it('should set describedby attribute', () => { + directive.setDescribedBy(); + + expect(directive.renderer.setAttribute).toHaveBeenCalled(); + }); + }); + + describe('removeDescribedBy', () => { + beforeEach(() => { + directive.renderer.removeAttribute = jest.fn(); + }); + + it('should remove describedby attribute', () => { + directive.removeDescribedBy(); + + expect(directive.renderer.removeAttribute).toHaveBeenCalled(); + }); + }); + + describe('getFocusableElement', () => { + it('should return null', () => { + const simpleElement = document.createElement('a'); + + const result: HTMLElement = directive.getFocusableElement(simpleElement); + + expect(result).toBeNull(); + }); + + it('should return focusable child element', () => { + const nestedElement = document.createElement('div'); + const buttonElement = document.createElement('button'); + nestedElement.appendChild(buttonElement); + + const result: HTMLElement = directive.getFocusableElement(nestedElement); + + expect(result).toBe(buttonElement); + }); + }); + + describe('destroy', () => { + beforeEach(() => { + directive.componentRef = mockComponentRef; + directive.removeDescribedBy = jest.fn(); + }); + + it('should set component ref to null', () => { + directive.destroy(); + + expect(directive.componentRef).toBeNull(); + }); + + it('should remove describedby attribute', () => { + directive.destroy(); + + expect(directive.removeDescribedBy).toHaveBeenCalled(); + }); + + it('should set focusable element to null', () => { + directive.destroy(); + + expect(directive.focusableElement).toBeNull(); + }); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..6af9df79e87cf64bdb3c73867a9a631e1f2b210a --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.directive.ts @@ -0,0 +1,98 @@ +import { isEscapeKey } from '@alfa-client/tech-shared'; +import { InteractivityChecker } from '@angular/cdk/a11y'; +import { + ComponentRef, + Directive, + ElementRef, + HostListener, + inject, + Input, + OnDestroy, + Renderer2, + ViewContainerRef, +} from '@angular/core'; +import { uniqueId } from 'lodash-es'; +import { TooltipComponent } from './tooltip.component'; + +@Directive({ + selector: '[tooltip]', + standalone: true, +}) +export class TooltipDirective implements OnDestroy { + @Input() tooltip: string = ''; + + componentRef: ComponentRef<TooltipComponent> = null; + focusableElement: HTMLElement = null; + tooltipId: string; + + public viewContainerRef: ViewContainerRef = inject(ViewContainerRef); + public elementRef: ElementRef<HTMLElement> = inject(ElementRef); + public renderer: Renderer2 = inject(Renderer2); + public interactivityChecker: InteractivityChecker = inject(InteractivityChecker); + + ngOnDestroy(): void { + this.destroy(); + } + + @HostListener('mouseenter') + @HostListener('focusin') + createTooltip(): void { + if (this.componentRef === null) { + const nativeElement: HTMLElement = this.elementRef.nativeElement; + const attachedToFocused: boolean = nativeElement.contains(document.activeElement); + this.componentRef = this.viewContainerRef.createComponent(TooltipComponent); + nativeElement.appendChild(this.componentRef.location.nativeElement); + this.setDescribedBy(); + this.setTooltipProperties(attachedToFocused); + } + } + + @HostListener('mouseleave') + @HostListener('window:scroll') + @HostListener('focusout') + destroyTooltip(): void { + this.destroy(); + } + + @HostListener('keydown', ['$event']) + onKeydown(e: KeyboardEvent): void { + if (isEscapeKey(e)) { + this.destroy(); + } + } + + setTooltipProperties(attachedToFocused = false): void { + if (this.componentRef !== null) { + const { left, right, bottom } = this.elementRef.nativeElement.getBoundingClientRect(); + this.componentRef.instance.left = (right + left) / 2; + this.componentRef.instance.top = attachedToFocused ? bottom + 4 : bottom; + this.componentRef.instance.text = this.tooltip; + this.componentRef.instance.id = this.tooltipId; + } + } + + setDescribedBy(): void { + const nativeElement: HTMLElement = this.elementRef.nativeElement; + this.tooltipId = uniqueId('tooltip'); + this.focusableElement = + this.interactivityChecker.isFocusable(nativeElement) ? nativeElement : this.getFocusableElement(nativeElement); + this.renderer.setAttribute(this.focusableElement, 'aria-describedby', this.tooltipId); + } + + removeDescribedBy(): void { + this.renderer.removeAttribute(this.focusableElement, 'aria-describedby'); + } + + getFocusableElement(element: HTMLElement): HTMLElement { + return element.querySelector('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'); + } + + destroy(): void { + if (this.componentRef !== null) { + this.componentRef.destroy(); + this.componentRef = null; + this.removeDescribedBy(); + this.focusableElement = null; + } + } +} diff --git a/alfa-client/libs/design-system/src/lib/tooltip/tooltip.stories.ts b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..f380cd055b3215670369b0d30fbbe2e00ef18921 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/tooltip/tooltip.stories.ts @@ -0,0 +1,29 @@ +import { moduleMetadata, type Meta, type StoryObj } from '@storybook/angular'; +import { TooltipDirective } from './tooltip.directive'; + +const meta: Meta = { + title: 'Tooltip', + excludeStories: /.*Data$/, + tags: ['autodocs'], + decorators: [ + moduleMetadata({ + imports: [TooltipDirective], + }), + ], + parameters: { + docs: { + description: { + component: 'Tooltip directive that can be used with every element (check out default story to see tooltip working).', + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ({ + template: '<button tooltip="Hello">I have a tooltip!</button>', + }), +}; diff --git a/alfa-server/src/main/resources/application-dev.yml b/alfa-server/src/main/resources/application-dev.yml index 6f11904235f17602539afaca47cd695e69deee8b..ec9f40e322dac00e4bc6dd3f274cfb6d481610da 100644 --- a/alfa-server/src/main/resources/application-dev.yml +++ b/alfa-server/src/main/resources/application-dev.yml @@ -14,7 +14,3 @@ ozgcloud: production: false stage: production: false - xdomea: - behoerdenschluessel: "DUMMY-SCHLUESSEL" - behoerdenschluessel-uri: "DUMMY-URI" - behoerdenschluessel-version: "DUMMY-VERSION" \ No newline at end of file diff --git a/alfa-server/src/main/resources/application-e2e.yml b/alfa-server/src/main/resources/application-e2e.yml index fd8c68442dd1afef3e6dc9f76d2932e0d7b2fc58..1f162b5430da5727476ee84d73e8cdc8dfa51e63 100644 --- a/alfa-server/src/main/resources/application-e2e.yml +++ b/alfa-server/src/main/resources/application-e2e.yml @@ -13,7 +13,3 @@ ozgcloud: url: /assets/benutzerleitfaden/benutzerleitfaden.pdf user-manager: url: http://localhost:9092 - xdomea: - behoerdenschluessel: "DUMMY-SCHLUESSEL-E2E" - behoerdenschluessel-uri: "DUMMY-URI-E2E" - behoerdenschluessel-version: "DUMMY-VERSION-E2E" \ No newline at end of file diff --git a/alfa-server/src/main/resources/application.yml b/alfa-server/src/main/resources/application.yml index ee47f0a2747565c9c998c64bf13e33aee1303306..14fb3b10fdecfbcd1383fb0030be1e1ba3ed33ab 100644 --- a/alfa-server/src/main/resources/application.yml +++ b/alfa-server/src/main/resources/application.yml @@ -103,7 +103,3 @@ ozgcloud: user-manager: profile-template: /api/userProfiles/%s search-template: /api/userProfiles/?searchBy={searchBy} - xdomea: - behoerdenschluessel: - behoerdenschluessel-uri: - behoerdenschluessel-version: diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index b41bd9f4d1b557a5bd5aa329079d778e8626e2fd..19508dd28a03b2ea0be60fe4e12bd93b3aa6538c 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -111,12 +111,6 @@ spec: value: {{ $bescheid.formEngineName }} {{- end }} {{- end}} - - name: ozgcloud_xdomea_behoerdenschluessel - value: {{ ((.Values.ozgcloud).xdomea).behoerdenschluessel | quote }} - - name: ozgcloud_xdomea_behoerdenschluesselUri - value: {{ ((.Values.ozgcloud).xdomea).behoerdenschluesselUri}} - - name: ozgcloud_xdomea_behoerdenschluesselVersion - value: {{ ((.Values.ozgcloud).xdomea).behoerdenschluesselVersion | quote }} - name: ozgcloud_administration_address value: {{ include "app.spring_cloud_config_administration_address" . }} - name: grpc_client_zufi-manager_address diff --git a/src/test/helm/deployment_xdomea_env_test.yaml b/src/test/helm/deployment_xdomea_env_test.yaml deleted file mode 100644 index 351acfe65758cf0fda278e66a28e7b7eed3085e1..0000000000000000000000000000000000000000 --- a/src/test/helm/deployment_xdomea_env_test.yaml +++ /dev/null @@ -1,55 +0,0 @@ -suite: deployment xdomea env -release: - name: alfa - namespace: sh-helm-test -templates: - - templates/deployment.yaml -set: - baseUrl: test.company.local - ozgcloud: - environment: test - bundesland: sh - bezeichner: helm - sso: - serverUrl: https://sso.company.local - imagePullSecret: image-pull-secret -tests: - - it: should not require on behoerdenschlüssel properties - set: - # note: explicitly non-set behoerdenschlüssel properties - asserts: - - notContains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluessel - - notContains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluesselUri - - notContains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluesselVersion - - it: should set (optional) behoerdenschlüssel properties - set: - ozgcloud: - xdomea: - behoerdenschluessel: "123456" - behoerdenschluesselUri: "uri.uri:uri" - behoerdenschluesselVersion: "version 1" - asserts: - - contains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluessel - value: "123456" - - contains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluesselUri - value: "uri.uri:uri" - - contains: - path: spec.template.spec.containers[0].env - content: - name: ozgcloud_xdomea_behoerdenschluesselVersion - value: "version 1" \ No newline at end of file