Skip to content
Snippets Groups Projects
Commit 22af8650 authored by OZGCloud's avatar OZGCloud
Browse files

Merge remote-tracking branch 'origin/master' into OZG-6477-7306-listResource-bug

parents a9349a57 2466072c
No related branches found
No related tags found
No related merge requests found
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LinkComponent, TooltipDirective } from '@ods/system';
import { MockComponent, MockDirective } from 'ng-mocks';
import { AccessibilityButtonComponent } from './accessibility-button.component';
describe('AccessibilityButtonComponent', () => {
......@@ -7,7 +9,7 @@ describe('AccessibilityButtonComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AccessibilityButtonComponent],
imports: [AccessibilityButtonComponent, MockComponent(LinkComponent), MockDirective(TooltipDirective)],
}).compileComponents();
fixture = TestBed.createComponent(AccessibilityButtonComponent);
......
......@@ -26,7 +26,9 @@ import { Component } from '@angular/core';
@Component({
selector: 'ods-tooltip',
template: `<p
class="fixed z-[100] 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-[0.5rem] before:border-l-[0.5rem] before:border-r-[0.5rem] before:border-b-ozggray-900 before:border-l-transparent before:border-r-transparent before:content-[''] dark:bg-white dark:before:border-b-white"
class="fixed hidden z-[100] 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-[0.5rem] before:border-l-[0.5rem] before:border-r-[0.5rem] before:border-b-ozggray-900 before:border-l-transparent before:border-r-transparent before:content-[''] dark:bg-white dark:before:border-b-white"
[class.block]="show"
[class.hidden]="!show"
[style.left]="left + 'px'"
[style.top]="top + 'px'"
[attr.id]="id"
......@@ -42,5 +44,6 @@ export class TooltipComponent {
text: string = '';
left: number = 0;
top: number = 0;
show: boolean = false;
id: string;
}
......@@ -45,7 +45,7 @@ describe('TooltipDirective', () => {
location: null,
hostView: null,
injector: null,
instance: { id: '', left: 0, top: 0, text: '' },
instance: { id: '', left: 0, top: 0, text: '', show: false },
};
beforeEach((): void => {
......@@ -74,8 +74,7 @@ describe('TooltipDirective', () => {
describe('createTooltip', () => {
beforeEach(() => {
directive.viewContainerRef.createComponent = jest.fn().mockReturnValue({ location: { nativeElement: {} } });
directive.setAriaDescribedBy = jest.fn();
directive.setTooltipProperties = jest.fn();
directive.setAriaLabeledBy = jest.fn();
});
it('should create tooltip component', () => {
......@@ -90,37 +89,50 @@ describe('TooltipDirective', () => {
expect(directive.elementRef.nativeElement.appendChild).toHaveBeenCalled();
});
it('should set aria-describedby attribute to parent', () => {
it('should set aria-labeledby attribute to parent', () => {
directive.createTooltip();
expect(directive.setAriaDescribedBy).toHaveBeenCalled();
expect(directive.setAriaLabeledBy).toHaveBeenCalled();
});
});
describe('showTooltip', () => {
beforeEach(() => {
directive.setTooltipProperties = jest.fn();
directive.elementRef.nativeElement.contains = jest.fn().mockReturnValue(true);
});
it('should check if element focused', () => {
directive.showTooltip();
expect(directive.elementRef.nativeElement.contains).toHaveBeenCalled();
});
it('should set tooltip properties', () => {
directive.createTooltip();
directive.showTooltip();
expect(directive.setTooltipProperties).toHaveBeenCalled();
expect(directive.setTooltipProperties).toHaveBeenCalledWith(true);
});
});
describe('destroyTooltip', () => {
it('should destroy tooltip', () => {
directive.destroy = jest.fn();
describe('hideTooltip', () => {
it('should hide tooltip', () => {
directive.hide = jest.fn();
directive.destroyTooltip();
directive.hideTooltip();
expect(directive.destroy).toHaveBeenCalled();
expect(directive.hide).toHaveBeenCalled();
});
});
describe('onKeydown', () => {
it('should destroy tooltip if escape key pressed', () => {
directive.destroy = jest.fn();
it('should hide tooltip if escape key pressed', () => {
directive.hide = jest.fn();
const escapeEvent: KeyboardEvent = { ...new KeyboardEvent('esc'), key: 'Escape' };
directive.onKeydown(escapeEvent);
expect(directive.destroy).toHaveBeenCalled();
expect(directive.hide).toHaveBeenCalled();
});
});
......@@ -149,6 +161,7 @@ describe('TooltipDirective', () => {
left: 500,
text: 'I am tooltip',
top: 1000,
show: true,
});
});
......@@ -159,7 +172,7 @@ describe('TooltipDirective', () => {
});
});
describe('setAriaDescribedBy', () => {
describe('setAriaLabeledBy', () => {
beforeEach(() => {
directive.getFocusableElement = jest.fn();
directive.renderer.setAttribute = jest.fn();
......@@ -167,31 +180,31 @@ describe('TooltipDirective', () => {
});
it('should check if parent element focusable', () => {
directive.setAriaDescribedBy();
directive.setAriaLabeledBy();
expect(directive.interactivityChecker.isFocusable).toHaveBeenCalled();
});
it('should get focusable element if parent not focusable', () => {
directive.setAriaDescribedBy();
directive.setAriaLabeledBy();
expect(directive.getFocusableElement).toHaveBeenCalled();
});
it('should set aria-describedby attribute', () => {
directive.setAriaDescribedBy();
it('should set aria-labeledby attribute', () => {
directive.setAriaLabeledBy();
expect(directive.renderer.setAttribute).toHaveBeenCalled();
});
});
describe('removeAriaDescribedBy', () => {
describe('removeAriaLabeledBy', () => {
beforeEach(() => {
directive.renderer.removeAttribute = jest.fn();
});
it('should remove aria-describedby attribute', () => {
directive.removeAriaDescribedBy();
it('should remove aria-labeledby attribute', () => {
directive.removeAriaLabeledBy();
expect(directive.renderer.removeAttribute).toHaveBeenCalled();
});
......@@ -217,10 +230,22 @@ describe('TooltipDirective', () => {
});
});
describe('hide', () => {
beforeEach(() => {
directive.componentRef = Object.assign(mockComponentRef, { instance: { ...mockComponentRef.instance, show: true } });
});
it('should hide component', () => {
directive.hide();
expect(directive.componentRef.instance.show).toBeFalsy;
});
});
describe('destroy', () => {
beforeEach(() => {
directive.componentRef = mockComponentRef;
directive.removeAriaDescribedBy = jest.fn();
directive.removeAriaLabeledBy = jest.fn();
});
it('should set component ref to null', () => {
......@@ -229,10 +254,10 @@ describe('TooltipDirective', () => {
expect(directive.componentRef).toBeNull();
});
it('should remove aria-describedby attribute', () => {
it('should remove aria-labeledby attribute', () => {
directive.destroy();
expect(directive.removeAriaDescribedBy).toHaveBeenCalled();
expect(directive.removeAriaLabeledBy).toHaveBeenCalled();
});
it('should set focusable element to null', () => {
......
......@@ -31,6 +31,7 @@ import {
inject,
Input,
OnDestroy,
OnInit,
Renderer2,
ViewContainerRef,
} from '@angular/core';
......@@ -43,7 +44,7 @@ const OUTLINE_INDENT = 4; // Outline offset (2) + outline width (2)
selector: '[tooltip]',
standalone: true,
})
export class TooltipDirective implements OnDestroy {
export class TooltipDirective implements OnInit, OnDestroy {
@Input() tooltip: string = '';
componentRef: ComponentRef<TooltipComponent> = null;
......@@ -55,22 +56,19 @@ export class TooltipDirective implements OnDestroy {
public renderer: Renderer2 = inject(Renderer2);
public interactivityChecker: InteractivityChecker = inject(InteractivityChecker);
ngOnInit(): void {
this.createTooltip();
}
ngOnDestroy(): void {
this.destroy();
}
@HostListener('mouseenter')
@HostListener('focusin')
createTooltip(): void {
if (isNotNull(this.componentRef)) {
return;
}
showTooltip(): void {
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.setAriaDescribedBy();
this.setTooltipProperties(attachedToFocused);
}
......@@ -78,17 +76,28 @@ export class TooltipDirective implements OnDestroy {
@HostListener('window:scroll')
@HostListener('focusout')
@HostListener('window:resize')
destroyTooltip(): void {
this.destroy();
hideTooltip(): void {
this.hide();
}
@HostListener('keydown', ['$event'])
onKeydown(e: KeyboardEvent): void {
if (isEscapeKey(e)) {
this.destroy();
this.hide();
}
}
createTooltip(): void {
if (isNotNull(this.componentRef)) {
return;
}
const nativeElement: HTMLElement = this.elementRef.nativeElement;
this.componentRef = this.viewContainerRef.createComponent(TooltipComponent);
nativeElement.appendChild(this.componentRef.location.nativeElement);
this.setAriaLabeledBy();
}
setTooltipProperties(attachedToFocused = false): void {
if (isNull(this.componentRef)) {
return;
......@@ -99,24 +108,33 @@ export class TooltipDirective implements OnDestroy {
this.componentRef.instance.top = attachedToFocused ? bottom + OUTLINE_INDENT : bottom;
this.componentRef.instance.text = this.tooltip;
this.componentRef.instance.id = this.tooltipId;
this.componentRef.instance.show = true;
}
setAriaDescribedBy(): void {
setAriaLabeledBy(): 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);
this.renderer.setAttribute(this.focusableElement, 'aria-labeledby', this.tooltipId);
}
removeAriaDescribedBy(): void {
this.renderer.removeAttribute(this.focusableElement, 'aria-describedby');
removeAriaLabeledBy(): void {
this.renderer.removeAttribute(this.focusableElement, 'aria-labeledby');
}
getFocusableElement(element: HTMLElement): HTMLElement {
return element.querySelector('a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])');
}
hide(): void {
if (isNull(this.componentRef)) {
return;
}
this.componentRef.instance.show = false;
}
destroy(): void {
if (isNull(this.componentRef)) {
return;
......@@ -124,7 +142,7 @@ export class TooltipDirective implements OnDestroy {
this.componentRef.destroy();
this.componentRef = null;
this.removeAriaDescribedBy();
this.removeAriaLabeledBy();
this.focusableElement = null;
}
}
......@@ -22,8 +22,7 @@
# unter der Lizenz sind dem Lizenztext zu entnehmen.
#
{{- if not (.Values.sso).disableOzgOperator -}}
---
{{ if not (.Values.sso).disableOzgOperator -}}
apiVersion: operator.ozgcloud.de/v1
kind: OzgCloudKeycloakRealm
metadata:
......@@ -32,6 +31,7 @@ metadata:
spec:
keep_after_delete: {{ (.Values.sso).keep_after_delete | default false }}
displayName: {{ include "app.ssoRealmDisplayName" . }}
bundesland: {{ include "app.ozgcloudBundesland" . }}
{{- with ((.Values.sso).keycloak_realm).roles }}
realmRoles:
{{ toYaml . | indent 4}}
......
......@@ -32,6 +32,7 @@ set:
ozgcloud:
environment: test
bezeichner: helm
bundesland: sh
tests:
- it: should contain header data
asserts:
......@@ -137,3 +138,8 @@ tests:
asserts:
- isNull:
path: spec.realmRoles
- it: should have a bundesland
asserts:
- equal:
path: spec.bundesland
value: sh
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment