feat(timeline): show the root-tag chip on the letter card
LetterCard now renders a TagChip beneath the sender→receiver/date line whenever the entry carries a rootTagName, mapping rootTagColor to the chip (neutral when null). Because the chip lives on LetterCard it shows up wherever a LetterCard does — the global timeline and the expanded YearLetterStrip — with no per-surface special-casing; a tagless letter shows no chip. A long name truncates inline so the card never overflows at 320px. Refs #835 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
import { timelineDateLabel } from './dateLabel';
|
||||
import GlyphLabel from './GlyphLabel.svelte';
|
||||
import TagChip from './TagChip.svelte';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type TimelineEntryDTO = components['schemas']['TimelineEntryDTO'];
|
||||
@@ -46,4 +47,9 @@ const receiver = $derived(
|
||||
<span data-testid="letter-date"> · {dateLabel}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{#if entry.rootTagName}
|
||||
<!-- The primary root-tag chip sits on its own line beneath the meta line
|
||||
(#835 §3); absent when the letter has no tag (REQ-005). -->
|
||||
<TagChip name={entry.rootTagName} color={entry.rootTagColor ?? null} />
|
||||
{/if}
|
||||
</a>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
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 LetterCard from './LetterCard.svelte';
|
||||
import YearLetterStrip from './YearLetterStrip.svelte';
|
||||
import { timelineDateLabel } from './dateLabel';
|
||||
import { makeEntry } from './test-factories';
|
||||
|
||||
@@ -86,4 +88,42 @@ describe('LetterCard', () => {
|
||||
expect(document.body.textContent).toContain(evil);
|
||||
expect(document.querySelector('a script')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders one root-tag chip beneath the meta line when rootTagName is present (REQ-008)', () => {
|
||||
render(LetterCard, { entry: makeEntry({ rootTagName: 'Familie', rootTagColor: 'sage' }) });
|
||||
const chips = document.querySelectorAll('[data-testid="tag-chip"]');
|
||||
expect(chips).toHaveLength(1);
|
||||
expect(chips[0].textContent).toContain('Familie');
|
||||
});
|
||||
|
||||
it('renders no chip when the letter has no root tag (REQ-005/006)', () => {
|
||||
render(LetterCard, { entry: makeEntry({ rootTagName: undefined, rootTagColor: undefined }) });
|
||||
expect(document.querySelector('[data-testid="tag-chip"]')).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps a long tag name from overflowing the card at 320px, full name in the title (REQ-008a)', () => {
|
||||
document.body.style.width = '320px';
|
||||
render(LetterCard, {
|
||||
entry: makeEntry({
|
||||
rootTagName: 'Briefe von der Front und aus der Heimat',
|
||||
rootTagColor: 'sienna'
|
||||
})
|
||||
});
|
||||
const link = document.querySelector('a') as HTMLAnchorElement;
|
||||
expect(link.scrollWidth).toBeLessThanOrEqual(link.clientWidth);
|
||||
const chip = document.querySelector('[data-testid="tag-chip"]') as HTMLElement;
|
||||
expect(chip.getAttribute('title')).toBe('Briefe von der Front und aus der Heimat');
|
||||
document.body.style.width = '';
|
||||
});
|
||||
|
||||
it('renders the chip inside an expanded YearLetterStrip too (REQ-012)', async () => {
|
||||
render(YearLetterStrip, {
|
||||
letters: [makeEntry({ rootTagName: 'Familie', rootTagColor: 'sage', documentId: 'doc-1' })],
|
||||
year: 1909
|
||||
});
|
||||
(document.querySelector('[data-testid="strip-expand"]') as HTMLButtonElement).click();
|
||||
await tick();
|
||||
const chip = document.querySelector('[data-testid="tag-chip"]');
|
||||
expect(chip?.textContent).toContain('Familie');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user