diff --git a/frontend/src/lib/shared/dashboard/DashboardResumeStrip.svelte.test.ts b/frontend/src/lib/shared/dashboard/DashboardResumeStrip.svelte.test.ts new file mode 100644 index 00000000..3c446f97 --- /dev/null +++ b/frontend/src/lib/shared/dashboard/DashboardResumeStrip.svelte.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DashboardResumeStrip from './DashboardResumeStrip.svelte'; + +afterEach(cleanup); + +const makeResume = (overrides: Record = {}) => ({ + documentId: 'd1', + title: 'Brief 1923', + caption: 'Sender → Receiver', + excerpt: 'First paragraph', + totalBlocks: 12, + pct: 50, + thumbnailUrl: '/api/d1/thumb', + collaborators: [{ initials: 'AS', color: '#012851', name: null }], + ...overrides +}); + +describe('DashboardResumeStrip', () => { + it('renders the empty card when resumeDoc is null', async () => { + render(DashboardResumeStrip, { props: { resumeDoc: null } }); + + const empty = document.querySelector('[data-testid="resume-strip-empty"]'); + expect(empty).not.toBeNull(); + await expect + .element(page.getByRole('heading', { name: /noch kein dokument begonnen/i })) + .toBeVisible(); + }); + + it('renders the resume strip when resumeDoc is provided', async () => { + render(DashboardResumeStrip, { props: { resumeDoc: makeResume() } }); + + expect(document.querySelector('[data-testid="resume-strip"]')).not.toBeNull(); + }); + + it('renders the document title', async () => { + render(DashboardResumeStrip, { props: { resumeDoc: makeResume() } }); + + await expect.element(page.getByRole('heading', { name: /brief 1923/i })).toBeVisible(); + }); + + it('renders the thumbnail image when thumbnailUrl is set', async () => { + render(DashboardResumeStrip, { props: { resumeDoc: makeResume() } }); + + expect(document.querySelector('[data-testid="resume-thumbnail-img"]')).not.toBeNull(); + }); + + it('renders the placeholder icon when thumbnailUrl is missing', async () => { + render(DashboardResumeStrip, { + props: { resumeDoc: makeResume({ thumbnailUrl: null }) } + }); + + expect(document.querySelector('[data-testid="resume-thumbnail-fallback"]')).not.toBeNull(); + }); + + it('renders the progress bar with correct aria-valuenow', async () => { + render(DashboardResumeStrip, { props: { resumeDoc: makeResume({ pct: 75 }) } }); + + const progress = document.querySelector('[role="progressbar"]') as HTMLElement; + expect(progress.getAttribute('aria-valuenow')).toBe('75'); + }); + + it('renders the resume CTA link to the document detail', async () => { + render(DashboardResumeStrip, { + props: { resumeDoc: makeResume({ documentId: 'doc-42' }) } + }); + + const link = document.querySelector('a[href="/documents/doc-42"]') as HTMLAnchorElement; + expect(link).not.toBeNull(); + }); + + it('renders the collaborators stack', async () => { + render(DashboardResumeStrip, { + props: { + resumeDoc: makeResume({ + collaborators: [ + { initials: 'XR', color: '#012851', name: null }, + { initials: 'YQ', color: '#5A3080', name: null } + ] + }) + } + }); + + await expect.element(page.getByText('XR')).toBeVisible(); + await expect.element(page.getByText('YQ')).toBeVisible(); + }); + + it('falls back to the default color when collaborator color is invalid', async () => { + render(DashboardResumeStrip, { + props: { + resumeDoc: makeResume({ + collaborators: [{ initials: 'ZQ', color: 'not-a-hex', name: null }] + }) + } + }); + + // safeColor falls back to #8c9aa3 — browser may serialize as rgb(140, 154, 163) + const span = Array.from(document.querySelectorAll('span')).find( + (s) => s.textContent?.trim() === 'ZQ' + ) as HTMLElement; + const style = span.getAttribute('style') ?? ''; + expect(style.toLowerCase()).toMatch(/(8c9aa3|140,\s*154,\s*163)/); + }); +});