This commit is contained in:
Bastian Wagner
2026-07-16 15:23:53 +02:00
parent 543e8273a7
commit c03b2e17f5
114 changed files with 7631 additions and 506 deletions

View File

@@ -0,0 +1,135 @@
import { Component, ViewChild } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import {
UiButtonComponent,
UiConfirmDialogComponent,
UiEmptyStateComponent,
UiFormFieldComponent,
UiIconButtonComponent,
UiPaginationComponent,
UiStatusBadgeComponent,
UiToastHostComponent,
ToastService,
} from './index';
import { isDesignSystemRouteEnabled } from '../../core/dev-only.guard';
import { devRoutes } from '../../core/dev-routes.prod';
@Component({
standalone: true,
imports: [
UiButtonComponent,
UiConfirmDialogComponent,
UiEmptyStateComponent,
UiFormFieldComponent,
UiIconButtonComponent,
UiPaginationComponent,
UiStatusBadgeComponent,
UiToastHostComponent,
],
template: `
<button id="trigger" type="button">Trigger</button>
<ui-button label="Speichern" [loading]="true" />
<ui-icon-button icon="delete" label="Eintrag loeschen" />
<ui-form-field label="Name" error="Name ist erforderlich">
<input class="ui-control" aria-invalid="true" />
</ui-form-field>
<ui-status-badge label="Deaktiviert" tone="danger" />
<ui-empty-state title="Keine Daten" actionLabel="Neu" (action)="emptyActionCount += 1" />
<ui-pagination [page]="2" [pageSize]="10" [total]="35" (pageChange)="page = $event" />
<ui-confirm-dialog />
<ui-toast-host />
`,
})
class UiTestHostComponent {
@ViewChild(UiConfirmDialogComponent) dialog?: UiConfirmDialogComponent;
page = 2;
emptyActionCount = 0;
}
describe('shared UI components', () => {
it('renders button loading and disabled state', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
fixture.detectChanges();
const button = (fixture.nativeElement as HTMLElement).querySelector('ui-button button');
expect(button?.hasAttribute('disabled')).toBe(true);
expect(button?.getAttribute('aria-busy')).toBe('true');
});
it('requires accessible labels for icon buttons', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
fixture.detectChanges();
const button = (fixture.nativeElement as HTMLElement).querySelector('ui-icon-button button');
expect(button?.getAttribute('aria-label')).toBe('Eintrag loeschen');
});
it('shows form field errors and status text', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
expect(element.textContent).toContain('Name ist erforderlich');
expect(element.textContent).toContain('Deaktiviert');
});
it('opens and closes confirm dialog and restores focus', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
fixture.detectChanges();
const trigger = (fixture.nativeElement as HTMLElement).querySelector<HTMLButtonElement>(
'#trigger',
);
trigger?.focus();
const promise = fixture.componentInstance.dialog?.open({
title: 'Loeschen',
description: 'Wirklich loeschen?',
confirmLabel: 'Loeschen',
danger: true,
});
fixture.detectChanges();
await fixture.whenStable();
expect((fixture.nativeElement as HTMLElement).querySelector('[role="dialog"]')).not.toBeNull();
fixture.componentInstance.dialog?.close(true);
fixture.detectChanges();
await expect(promise).resolves.toBe(true);
expect(document.activeElement).toBe(trigger);
});
it('shows and closes toasts', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
const service = TestBed.inject(ToastService);
const id = service.show({ tone: 'success', title: 'Gespeichert' });
fixture.detectChanges();
expect((fixture.nativeElement as HTMLElement).textContent).toContain('Gespeichert');
service.close(id);
fixture.detectChanges();
expect((fixture.nativeElement as HTMLElement).textContent).not.toContain('Gespeichert');
});
it('emits empty state and pagination actions', async () => {
await TestBed.configureTestingModule({ imports: [UiTestHostComponent] }).compileComponents();
const fixture = TestBed.createComponent(UiTestHostComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
element.querySelector<HTMLButtonElement>('ui-empty-state button')?.click();
element.querySelectorAll<HTMLButtonElement>('ui-pagination button')[0]?.click();
expect(fixture.componentInstance.emptyActionCount).toBe(1);
expect(fixture.componentInstance.page).toBe(1);
});
it('keeps the design system route disabled outside development mode', () => {
expect(isDesignSystemRouteEnabled(false)).toBe(false);
expect(devRoutes).toHaveLength(0);
});
});