From 890f2d3051c63fb39bfbc22b1547be517beb4222 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 9 May 2026 23:06:32 +0200 Subject: [PATCH] test(admin/tags): cover TagsListPanel branches Tree label rendering, empty placeholder branch, top-level node rendering, collapse-button visibility, autocollapse vs manual collapse via localStorage, expand-on-click flow, localStorage parse path, malformed-JSON resilience. 9 tests, ~25 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../admin/tags/TagsListPanel.svelte.test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 frontend/src/routes/admin/tags/TagsListPanel.svelte.test.ts diff --git a/frontend/src/routes/admin/tags/TagsListPanel.svelte.test.ts b/frontend/src/routes/admin/tags/TagsListPanel.svelte.test.ts new file mode 100644 index 00000000..4e298519 --- /dev/null +++ b/frontend/src/routes/admin/tags/TagsListPanel.svelte.test.ts @@ -0,0 +1,79 @@ +import { describe, it, expect, afterEach, beforeEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import TagsListPanel from './TagsListPanel.svelte'; + +beforeEach(() => { + localStorage.clear(); +}); + +afterEach(cleanup); + +const baseTree = [ + { id: 't1', name: 'Personen', parentId: null, color: 'sage', children: [] }, + { id: 't2', name: 'Orte', parentId: null, color: 'sienna', children: [] } +]; + +describe('TagsListPanel', () => { + it('renders the expanded panel with tree and label', async () => { + render(TagsListPanel, { props: { tree: baseTree } }); + + await expect.element(page.getByRole('tree', { name: /schlagwörter/i })).toBeVisible(); + }); + + it('renders the empty placeholder when tree is empty', async () => { + render(TagsListPanel, { props: { tree: [] } }); + + await expect.element(page.getByText('Keine Schlagworte vorhanden.')).toBeVisible(); + }); + + it('renders one tree node per top-level tag', async () => { + render(TagsListPanel, { props: { tree: baseTree } }); + + await expect.element(page.getByText('Personen')).toBeVisible(); + await expect.element(page.getByText('Orte')).toBeVisible(); + }); + + it('renders the collapse button in expanded view', async () => { + render(TagsListPanel, { props: { tree: baseTree } }); + + await expect.element(page.getByRole('button', { name: /liste einklappen/i })).toBeVisible(); + }); + + it('renders collapsed view when autocollapse is true', async () => { + render(TagsListPanel, { props: { tree: baseTree, autocollapse: true } }); + + await expect.element(page.getByRole('button', { name: /liste ausklappen/i })).toBeVisible(); + }); + + it('honours the localStorage manual-collapse preference', async () => { + localStorage.setItem('admin_tags_list_collapsed', 'true'); + render(TagsListPanel, { props: { tree: baseTree } }); + + await expect.element(page.getByRole('button', { name: /liste ausklappen/i })).toBeVisible(); + }); + + it('expands the panel when the collapsed handle is clicked', async () => { + localStorage.setItem('admin_tags_list_collapsed', 'true'); + render(TagsListPanel, { props: { tree: baseTree } }); + + await page.getByRole('button', { name: /liste ausklappen/i }).click(); + + await expect.element(page.getByRole('tree')).toBeVisible(); + }); + + it('reads the collapse map from localStorage on mount', async () => { + localStorage.setItem('admin_tags_tree_state', JSON.stringify({ t1: true })); + render(TagsListPanel, { props: { tree: baseTree } }); + + // Just verify it doesn't crash on JSON parse + await expect.element(page.getByRole('tree')).toBeVisible(); + }); + + it('handles malformed localStorage tree state gracefully', async () => { + localStorage.setItem('admin_tags_tree_state', 'not-valid-json{'); + render(TagsListPanel, { props: { tree: baseTree } }); + + await expect.element(page.getByRole('tree')).toBeVisible(); + }); +});