fix(web): Kalender-Tooltip bricht lange Termintitel um (Breite/Klemmung aus einer Konstante)

- Titel-Span im Tooltip: min-w-0 break-words statt truncate
- Breite (288px) und Rand-Klemmung aus TOOLTIP_WIDTH_PX/TOOLTIP_EDGE_PX
- Test 4c: belegt Klassen und Tooltip-Breite

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-16 13:54:34 +02:00
parent 4f823c3e66
commit b16e4b8e67
2 changed files with 46 additions and 5 deletions
@@ -200,6 +200,35 @@ describe('CalendarWidget', () => {
expect(tooltip).not.toHaveTextContent('e6');
});
it('Test 4c: Tooltip bricht lange Termintitel um statt sie abzuschneiden', async () => {
mockFetchEvents.mockResolvedValue([
ev(
'Deutscher Weltkindertag (Aktionstag der Kinderrechte)',
new Date(2026, 6, 20, 9, 0),
new Date(2026, 6, 20, 10, 0),
),
]);
const { CalendarWidget } = await import('./calendar-widget');
render(<CalendarWidget instanceId="cal-4c" config={{}} isEditMode={false} />);
await waitFor(() => {
expect(screen.getByText('Juli 2026')).toBeInTheDocument();
});
const day20 = document.querySelector('[data-date="2026-07-20"]') as HTMLElement;
fireEvent.mouseEnter(day20);
const tooltip = screen.getByTestId('calendar-day-tooltip');
const title = within(tooltip).getByText(
'Deutscher Weltkindertag (Aktionstag der Kinderrechte)',
);
expect(title).toHaveClass('break-words');
expect(title).toHaveClass('min-w-0');
expect(title).not.toHaveClass('truncate');
expect(tooltip).toHaveStyle({ width: '288px' });
});
it('Test 5: Naechste Termine respektiert maxEvents, Reihenfolge und zeigt Ort/Farbpunkt', async () => {
mockFetchEvents.mockResolvedValue([
ev('E1', new Date(2026, 6, 16, 9, 0), new Date(2026, 6, 16, 10, 0)),
@@ -18,6 +18,12 @@ import {
WEEKDAY_LABELS,
} from './calendar-month';
// quick-260916-j4f: Breite und Rand-Klemmung des Termin-Tooltips aus einer
// Quelle, damit Breite und Klemmwert nicht auseinanderlaufen; 288 px
// entspricht Tailwind w-72.
const TOOLTIP_WIDTH_PX = 288;
const TOOLTIP_EDGE_PX = 4;
/**
* Calendar widget (DASH-05, D-10 read-only), neu gebaut nach dem Vorbild des
* alten persoenlichen Dashboards des Anwenders (quick-260916-htc).
@@ -30,7 +36,9 @@ import {
*
* Der Tooltip wird per `createPortal` in `document.body` gerendert, weil die
* Karte in widget-wrapper.tsx `overflow-hidden` traegt und ein absolut
* positionierter Tooltip sonst abgeschnitten wuerde.
* positionierter Tooltip sonst abgeschnitten wuerde. Titel im Tooltip
* brechen um (kein truncate); Breite/Klemmung kommen aus
* `TOOLTIP_WIDTH_PX`/`TOOLTIP_EDGE_PX` (quick-260916-j4f).
*
* Das Ladefenster ist IMMER auf lokale Tagesgrenzen gerundet
* (`computeFetchWindow`), damit der Backend-Cache-Schluessel
@@ -293,10 +301,14 @@ export function CalendarWidget({ config }: WidgetProps) {
<div
data-testid="calendar-day-tooltip"
role="tooltip"
className="pointer-events-none fixed z-50 w-60 rounded border border-border bg-card p-2 text-xs text-foreground shadow-lg"
className="pointer-events-none fixed z-50 rounded border border-border bg-card p-2 text-xs text-foreground shadow-lg"
style={{
top: hover.rect.bottom + 4,
left: Math.max(4, Math.min(hover.rect.left, window.innerWidth - 244)),
width: TOOLTIP_WIDTH_PX,
top: hover.rect.bottom + TOOLTIP_EDGE_PX,
left: Math.max(
TOOLTIP_EDGE_PX,
Math.min(hover.rect.left, window.innerWidth - TOOLTIP_WIDTH_PX - TOOLTIP_EDGE_PX),
),
}}
>
{hoverEvents.slice(0, 5).map((event) => (
@@ -304,7 +316,7 @@ export function CalendarWidget({ config }: WidgetProps) {
<span className="shrink-0 tabular-nums text-muted-foreground">
{event.allDay ? t('calendar.allDay') : formatEventTime(event.start)}
</span>
<span className="truncate">{event.title}</span>
<span className="min-w-0 break-words">{event.title}</span>
</div>
))}
{hoverEvents.length > 5 && (