From 649b6b447c0e853e3896e425e1c1d65c58e862b6 Mon Sep 17 00:00:00 2001 From: Marcel Date: Tue, 16 Jun 2026 19:15:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(shared):=20add=20MetaLine=20primitive=20?= =?UTF-8?q?=E2=80=94=20=C2=B7=20-separated=20meta,=20optional=20icon=20(?= =?UTF-8?q?=C2=A77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #859 Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/shared/primitives/MetaLine.svelte | 26 +++++++ .../shared/primitives/MetaLine.svelte.spec.ts | 74 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 frontend/src/lib/shared/primitives/MetaLine.svelte create mode 100644 frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts diff --git a/frontend/src/lib/shared/primitives/MetaLine.svelte b/frontend/src/lib/shared/primitives/MetaLine.svelte new file mode 100644 index 00000000..77493fd4 --- /dev/null +++ b/frontend/src/lib/shared/primitives/MetaLine.svelte @@ -0,0 +1,26 @@ + + +{#if items.length > 0} +
+ {#if iconSrc} + + {/if} + {#each items as item, i (i)} + {#if i > 0} + + {/if} + {item} + {/each} +
+{/if} diff --git a/frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts b/frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts new file mode 100644 index 00000000..3fe4ba4a --- /dev/null +++ b/frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts @@ -0,0 +1,74 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import MetaLine from './MetaLine.svelte'; + +afterEach(() => cleanup()); + +describe('MetaLine', () => { + it('renders N item spans when given N items', async () => { + render(MetaLine, { items: ['14. März 1923', '14 Dokumente', '4 Personen'] }); + const spans = document.querySelectorAll('[data-testid="meta-item"]'); + expect(spans).toHaveLength(3); + expect(spans[0].textContent).toBe('14. März 1923'); + expect(spans[1].textContent).toBe('14 Dokumente'); + expect(spans[2].textContent).toBe('4 Personen'); + }); + + it('renders separator spans between items', async () => { + render(MetaLine, { items: ['A', 'B', 'C'] }); + const seps = document.querySelectorAll('[data-testid="meta-sep"]'); + // N items → N-1 separators + expect(seps).toHaveLength(2); + expect(seps[0].textContent).toBe('·'); + }); + + it('renders nothing when items is empty', async () => { + const { container } = render(MetaLine, { items: [] }); + // No element children — Svelte may leave an empty comment node but no DOM elements + expect(container.querySelectorAll('[data-testid]')).toHaveLength(0); + expect(container.querySelectorAll('div, span, img')).toHaveLength(0); + }); + + it('renders nothing when items has one element (no separator)', async () => { + render(MetaLine, { items: ['Nur eines'] }); + const seps = document.querySelectorAll('[data-testid="meta-sep"]'); + expect(seps).toHaveLength(0); + const spans = document.querySelectorAll('[data-testid="meta-item"]'); + expect(spans).toHaveLength(1); + }); + + it('shows the leading img when iconSrc is supplied', async () => { + render(MetaLine, { + items: ['Datum'], + iconSrc: '/degruyter-icons/Simple/Small-16px/SVG/Action/Calendar-Add-SM.svg' + }); + const img = document.querySelector('img'); + expect(img).not.toBeNull(); + }); + + it('does NOT render an img when iconSrc is omitted', async () => { + render(MetaLine, { items: ['Datum'] }); + const img = document.querySelector('img'); + expect(img).toBeNull(); + }); + + it('icon has width 14px, height 14px, opacity 0.5, and alt=""', async () => { + render(MetaLine, { + items: ['Datum'], + iconSrc: '/degruyter-icons/Simple/Small-16px/SVG/Action/Calendar-Add-SM.svg' + }); + const img = document.querySelector('img') as HTMLImageElement; + expect(img.alt).toBe(''); + // Inline style values (set directly on the element, not via getComputedStyle) + expect(img.style.width).toBe('14px'); + expect(img.style.height).toBe('14px'); + expect(img.style.opacity).toBe('0.5'); + }); + + it('applies font-size 12px to the wrapper', async () => { + render(MetaLine, { items: ['Test'] }); + const wrapper = document.querySelector('[data-testid="meta-line"]') as HTMLElement; + expect(wrapper).not.toBeNull(); + expect(wrapper.style.fontSize).toBe('12px'); + }); +});