feat(search): render removable theme chips in InterpretationChipRow

When tagsApplied is true, each resolvedTag renders as a 'Thema: Name'
chip with optional inline color style from the tag's resolved color.
Clicking × calls onRemoveChip('theme', tag.name).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-06 23:33:53 +02:00
parent 847874abb3
commit 5387bc9247
2 changed files with 126 additions and 3 deletions

View File

@@ -6,6 +6,7 @@ import type { components } from '$lib/generated/api';
type NlQueryInterpretation = components['schemas']['NlQueryInterpretation'];
type PersonHint = components['schemas']['PersonHint'];
type TagHint = components['schemas']['TagHint'];
afterEach(() => cleanup());
@@ -132,4 +133,79 @@ describe('InterpretationChipRow', () => {
.element(page.getByRole('button', { name: new RegExp('Absender') }))
.toBeInTheDocument();
});
// ── theme chips ─────────────────────────────────────────────────────────────
const makeTag = (id: string, name: string, color?: string): TagHint => ({ id, name, color });
it('renders theme chips when tagsApplied is true', async () => {
const { container } = render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Hochzeit')],
tagsApplied: true
}),
onRemoveChip: vi.fn()
});
expect(container.querySelectorAll('[data-chip-type="theme"]')).toHaveLength(1);
await expect.element(page.getByText(/Thema: Hochzeit/)).toBeInTheDocument();
});
it('renders no theme chips when tagsApplied is false', async () => {
const { container } = render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Hochzeit')],
tagsApplied: false
}),
onRemoveChip: vi.fn()
});
expect(container.querySelectorAll('[data-chip-type="theme"]')).toHaveLength(0);
});
it('renders exactly N theme chips for N resolved tags', async () => {
const { container } = render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Krieg'), makeTag('t2', 'Hochzeit'), makeTag('t3', 'Familie')],
tagsApplied: true
}),
onRemoveChip: vi.fn()
});
expect(container.querySelectorAll('[data-chip-type="theme"]')).toHaveLength(3);
});
it('calls onRemoveChip with "theme" and tag name when × is clicked', async () => {
const onRemoveChip = vi.fn();
render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Hochzeit')],
tagsApplied: true
}),
onRemoveChip
});
await page.getByRole('button', { name: /Thema: Hochzeit/ }).click();
expect(onRemoveChip).toHaveBeenCalledWith('theme', 'Hochzeit');
});
it('applies inline color style for a tag with a color', async () => {
const { container } = render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Hochzeit', 'sage')],
tagsApplied: true
}),
onRemoveChip: vi.fn()
});
const chip = container.querySelector('[data-chip-type="theme"]') as HTMLElement;
expect(chip.style.backgroundColor).toBeTruthy();
});
it('omits color style for a tag with no color', async () => {
const { container } = render(InterpretationChipRow, {
interpretation: makeInterpretation({
resolvedTags: [makeTag('t1', 'Hochzeit')],
tagsApplied: true
}),
onRemoveChip: vi.fn()
});
const chip = container.querySelector('[data-chip-type="theme"]') as HTMLElement;
expect(chip.getAttribute('style')).toBeFalsy();
});
});