From ace9602f6e741f718b59084a122085a94b385cb5 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Jun 2026 15:04:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(timeline):=20add=20EventNote=20component?= =?UTF-8?q?=20with=20expand/collapse=20(REQ-002=E2=80=93008)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handles XSS escaping, whitespace-pre-line, 3-line clamp via inline style, and a toggle button that is only shown when content actually overflows. Refs #844 Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/lib/timeline/EventNote.svelte | 47 ++++++++++++ .../lib/timeline/event-note.svelte.spec.ts | 74 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 frontend/src/lib/timeline/EventNote.svelte create mode 100644 frontend/src/lib/timeline/event-note.svelte.spec.ts diff --git a/frontend/src/lib/timeline/EventNote.svelte b/frontend/src/lib/timeline/EventNote.svelte new file mode 100644 index 00000000..96672d78 --- /dev/null +++ b/frontend/src/lib/timeline/EventNote.svelte @@ -0,0 +1,47 @@ + + +{#if hasContent} +
+

+ {description} +

+ {#if clamped || expanded} + + {/if} +
+{/if} diff --git a/frontend/src/lib/timeline/event-note.svelte.spec.ts b/frontend/src/lib/timeline/event-note.svelte.spec.ts new file mode 100644 index 00000000..80124e47 --- /dev/null +++ b/frontend/src/lib/timeline/event-note.svelte.spec.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { tick } from 'svelte'; +import * as m from '$lib/paraglide/messages.js'; +import EventNote from './EventNote.svelte'; + +afterEach(() => cleanup()); + +const LONG_NOTE = Array.from({ length: 15 }, (_, i) => `Zeile ${i + 1}`).join('\n'); + +describe('EventNote (REQ-002–008)', () => { + it('escapesHtml — renders XSS payload as inert text, no injected element (REQ-002)', () => { + render(EventNote, { description: '' }); + // The literal string should appear as text content + expect(document.body.textContent).toContain(''); + // No injected