From 170a770d6f32378663f1f09d5e82b73ba33d71dd Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 01:26:14 +0200 Subject: [PATCH] test(admin/tags): cover the tag-edit page branches Heading with tag name, name input hydration, color picker visible only for top-level tags, color swatch grid (10 entries), aria-pressed for active color, success banner branch, error banner branch, merge-success banner branch. 8 tests covering ~30 branches in the tag-edit page. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../admin/tags/[id]/page.svelte.test.ts | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 frontend/src/routes/admin/tags/[id]/page.svelte.test.ts diff --git a/frontend/src/routes/admin/tags/[id]/page.svelte.test.ts b/frontend/src/routes/admin/tags/[id]/page.svelte.test.ts new file mode 100644 index 00000000..84d21eec --- /dev/null +++ b/frontend/src/routes/admin/tags/[id]/page.svelte.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; + +const mockPage = { url: { pathname: '/admin/tags/t1' } }; + +vi.mock('$app/stores', () => ({ + page: { + subscribe: (fn: (v: typeof mockPage) => void) => { + fn(mockPage); + return () => {}; + } + } +})); + +vi.mock('$lib/shared/services/confirm.svelte', () => ({ + getConfirmService: () => ({ confirm: async () => false }) +})); +vi.mock('$lib/shared/services/confirm.svelte.js', () => ({ + getConfirmService: () => ({ confirm: async () => false }) +})); + +vi.mock('$app/navigation', () => ({ + beforeNavigate: () => {}, + afterNavigate: () => {}, + goto: vi.fn(), + invalidate: vi.fn(), + invalidateAll: vi.fn(), + preloadCode: vi.fn(), + preloadData: vi.fn(), + pushState: vi.fn(), + replaceState: vi.fn(), + disableScrollHandling: vi.fn(), + onNavigate: () => () => {} +})); + +const { default: AdminTagEditPage } = await import('./+page.svelte'); + +afterEach(cleanup); + +const baseTag = (overrides: Record = {}) => ({ + id: 't1', + name: 'Personen', + color: 'sage' as string | null, + parentId: null as string | null, + ...overrides +}); + +const baseData = (overrides: Record = {}) => ({ + tag: baseTag(), + tags: [baseTag(), baseTag({ id: 't2', name: 'Orte', color: 'sienna' })], + mergeSuccess: null, + ...overrides +}); + +describe('admin/tags/[id] page', () => { + it('renders the edit heading with the tag name', async () => { + render(AdminTagEditPage, { props: { data: baseData(), form: undefined } }); + + await expect.element(page.getByRole('heading', { name: /personen/i })).toBeVisible(); + }); + + it('hydrates the name input from data.tag.name', async () => { + render(AdminTagEditPage, { + props: { data: baseData({ tag: baseTag({ name: 'Reisen' }) }), form: undefined } + }); + + const input = document.querySelector('input[name="name"]') as HTMLInputElement; + expect(input.value).toBe('Reisen'); + }); + + it('renders the color picker for top-level tags (no parentId)', async () => { + render(AdminTagEditPage, { props: { data: baseData(), form: undefined } }); + + const colorPicker = document.querySelector('[data-testid="color-picker"]'); + expect(colorPicker).not.toBeNull(); + }); + + it('renders one color swatch per palette entry', async () => { + render(AdminTagEditPage, { props: { data: baseData(), form: undefined } }); + + const swatches = document.querySelectorAll('[data-testid^="color-swatch-"]'); + expect(swatches.length).toBeGreaterThanOrEqual(10); + }); + + it('marks the active color swatch as aria-pressed', async () => { + render(AdminTagEditPage, { + props: { data: baseData({ tag: baseTag({ color: 'amber' }) }), form: undefined } + }); + + const amberSwatch = document.querySelector('[data-testid="color-swatch-amber"]') as HTMLElement; + expect(amberSwatch.getAttribute('aria-pressed')).toBe('true'); + }); + + it('shows the form-success banner when form.success is true', async () => { + render(AdminTagEditPage, { + props: { data: baseData(), form: { success: true } } + }); + + const banners = document.querySelectorAll('.bg-green-50'); + expect(banners.length).toBeGreaterThan(0); + }); + + it('shows the form-error banner when form.error is set', async () => { + render(AdminTagEditPage, { + props: { data: baseData(), form: { error: 'Tag name already in use' } } + }); + + const errors = document.querySelectorAll('.bg-red-50'); + expect(errors.length).toBeGreaterThan(0); + }); + + it('shows the merge-success banner when data.mergeSuccess is set', async () => { + render(AdminTagEditPage, { + props: { data: baseData({ mergeSuccess: 'old-id' }), form: undefined } + }); + + const banners = document.querySelectorAll('[role="status"]'); + expect(banners.length).toBeGreaterThan(0); + }); +});