diff --git a/frontend/src/lib/timeline/TagChip.svelte b/frontend/src/lib/timeline/TagChip.svelte new file mode 100644 index 00000000..94546da0 --- /dev/null +++ b/frontend/src/lib/timeline/TagChip.svelte @@ -0,0 +1,44 @@ + + + + {m.timeline_tag_chip_label()}: + {#if color} + + {:else} + + {/if} + {name} + diff --git a/frontend/src/lib/timeline/TagChip.svelte.spec.ts b/frontend/src/lib/timeline/TagChip.svelte.spec.ts new file mode 100644 index 00000000..4d31199b --- /dev/null +++ b/frontend/src/lib/timeline/TagChip.svelte.spec.ts @@ -0,0 +1,46 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import * as m from '$lib/paraglide/messages.js'; +import TagChip from './TagChip.svelte'; + +afterEach(() => cleanup()); + +describe('TagChip', () => { + it('renders the tag name (REQ-008)', () => { + render(TagChip, { name: 'Familie', color: 'sage' }); + expect(document.body.textContent).toContain('Familie'); + }); + + it('prefixes the name with an sr-only theme label and a decorative square (REQ-011)', () => { + render(TagChip, { name: 'Krieg', color: 'sienna' }); + const srOnly = document.querySelector('.sr-only'); + expect(srOnly?.textContent).toContain(m.timeline_tag_chip_label()); + const square = document.querySelector('[data-testid="tag-chip-square"]'); + expect(square?.getAttribute('aria-hidden')).toBe('true'); + }); + + it('applies the color via var(--c-tag-{token}), never raw hex (REQ-009)', () => { + render(TagChip, { name: 'Krieg', color: 'sienna' }); + const square = document.querySelector('[data-testid="tag-chip-square"]') as HTMLElement; + expect(square.getAttribute('style')).toContain('var(--c-tag-sienna)'); + }); + + it('renders a neutral chip with no --c-tag- binding when color is null (REQ-007)', () => { + render(TagChip, { name: 'Allgemein', color: null }); + expect(document.body.textContent).toContain('Allgemein'); + expect(document.body.innerHTML).not.toContain('var(--c-tag-'); + }); + + it('exposes the full name as the chip title so a truncated name stays reachable (REQ-008a)', () => { + render(TagChip, { name: 'Briefe von der Front', color: 'sienna' }); + const chip = document.querySelector('[data-testid="tag-chip"]') as HTMLElement; + expect(chip.getAttribute('title')).toBe('Briefe von der Front'); + }); + + it('renders an HTML-bearing name as inert text, never markup (REQ-010)', () => { + const evil = ''; + render(TagChip, { name: evil, color: null }); + expect(document.body.textContent).toContain(evil); + expect(document.querySelector('img')).toBeNull(); + }); +});