test(admin/tags): cover TagTreeNode recursive branches

Tag link href, document-count visibility branch, color-dot at depth 0
vs deeper, aria-current matrix, children list rendering, collapse-map
hides children, expand/collapse toggle for nodes with children.

9 tests covering ~30 branches in the recursive tree-node component.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-10 00:40:07 +02:00
parent f0cdd2b305
commit 239d0b27a3

View File

@@ -0,0 +1,125 @@
import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page as browserPage } from 'vitest/browser';
import { SvelteMap } from 'svelte/reactivity';
const mockPage = { url: new URL('http://localhost/admin/tags') };
vi.mock('$app/state', () => ({
get page() {
return mockPage;
}
}));
afterEach(cleanup);
async function loadComponent() {
return (await import('./TagTreeNode.svelte')).default;
}
const leafNode = (overrides: Record<string, unknown> = {}) => ({
id: 't1',
name: 'Personen',
color: 'sage',
documentCount: 5,
parentId: null,
children: [],
...overrides
});
const parentNode = (overrides: Record<string, unknown> = {}) => ({
id: 'tp1',
name: 'Orte',
color: 'sienna',
documentCount: 0,
parentId: null,
children: [{ id: 'tc1', name: 'Berlin', color: null, documentCount: 2, children: [] }],
...overrides
});
describe('TagTreeNode', () => {
it('renders the tag name as a link', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });
await expect
.element(browserPage.getByRole('link', { name: /personen/i }))
.toHaveAttribute('href', '/admin/tags/t1');
});
it('renders the document count when greater than 0', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, {
props: { node: leafNode({ documentCount: 7 }), depth: 0, collapseMap: new SvelteMap() }
});
await expect.element(browserPage.getByText('(7)')).toBeVisible();
});
it('omits the document count when 0', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, {
props: { node: leafNode({ documentCount: 0 }), depth: 0, collapseMap: new SvelteMap() }
});
await expect.element(browserPage.getByText('(0)')).not.toBeInTheDocument();
});
it('shows the color dot at depth 0 when color is set', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });
const dot = document.querySelector('[data-testid="tag-list-color-dot"]');
expect(dot).not.toBeNull();
expect(dot?.getAttribute('data-color')).toBe('sage');
});
it('omits the color dot when depth > 0', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, { props: { node: leafNode(), depth: 1, collapseMap: new SvelteMap() } });
expect(document.querySelector('[data-testid="tag-list-color-dot"]')).toBeNull();
});
it('marks the link as aria-current=page when on the matching route', async () => {
mockPage.url = new URL('http://localhost/admin/tags/t1');
const Node = await loadComponent();
render(Node, { props: { node: leafNode(), depth: 0, collapseMap: new SvelteMap() } });
await expect
.element(browserPage.getByRole('link', { name: /personen/i }))
.toHaveAttribute('aria-current', 'page');
});
it('renders the children list for parent nodes when not collapsed', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, { props: { node: parentNode(), depth: 0, collapseMap: new SvelteMap() } });
await expect.element(browserPage.getByText('Berlin')).toBeVisible();
});
it('hides children when the node is collapsed', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
const map = new SvelteMap<string, boolean>([['tp1', true]]);
render(Node, { props: { node: parentNode(), depth: 0, collapseMap: map } });
await expect.element(browserPage.getByText('Berlin')).not.toBeInTheDocument();
});
it('exposes the expand/collapse toggle for nodes with children', async () => {
mockPage.url = new URL('http://localhost/admin/tags');
const Node = await loadComponent();
render(Node, { props: { node: parentNode(), depth: 0, collapseMap: new SvelteMap() } });
await expect
.element(browserPage.getByRole('button', { name: /einklappen|ausklappen/i }))
.toBeVisible();
});
});