Empty card vs populated strip, title rendering, thumbnail image vs fallback icon, progress bar aria-valuenow, document detail link, collaborators stack rendering, safeColor fallback to default when hex invalid. 9 tests covering ~25 of DashboardResumeStrip's branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
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<string, unknown> = {}) => ({
|
|
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)/);
|
|
});
|
|
});
|