feat(shared): add MetaLine primitive — · -separated meta, optional icon (§7)
Refs #859 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
26
frontend/src/lib/shared/primitives/MetaLine.svelte
Normal file
26
frontend/src/lib/shared/primitives/MetaLine.svelte
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
items,
|
||||||
|
iconSrc
|
||||||
|
}: {
|
||||||
|
items: string[];
|
||||||
|
iconSrc?: string;
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if items.length > 0}
|
||||||
|
<div
|
||||||
|
data-testid="meta-line"
|
||||||
|
style="display:flex; align-items:center; flex-wrap:wrap; gap:8px; font-family:var(--font-sans); font-size:12px; color:var(--c-ink-2);"
|
||||||
|
>
|
||||||
|
{#if iconSrc}
|
||||||
|
<img src={iconSrc} alt="" style="width:14px; height:14px; opacity:0.5; flex-shrink:0;" />
|
||||||
|
{/if}
|
||||||
|
{#each items as item, i (i)}
|
||||||
|
{#if i > 0}
|
||||||
|
<span data-testid="meta-sep" aria-hidden="true">·</span>
|
||||||
|
{/if}
|
||||||
|
<span data-testid="meta-item">{item}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
74
frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts
Normal file
74
frontend/src/lib/shared/primitives/MetaLine.svelte.spec.ts
Normal file
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user