test(coverage): drive browser tests to 80% on all metrics (#496) #505
161
frontend/src/lib/activity/DashboardActivityFeed.svelte.test.ts
Normal file
161
frontend/src/lib/activity/DashboardActivityFeed.svelte.test.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest';
|
||||
import { cleanup, render } from 'vitest-browser-svelte';
|
||||
import { page } from 'vitest/browser';
|
||||
import DashboardActivityFeed from './DashboardActivityFeed.svelte';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const baseItem = (overrides: Partial<ActivityFeedItemDTO> = {}): ActivityFeedItemDTO =>
|
||||
({
|
||||
kind: 'TEXT_SAVED',
|
||||
documentId: 'doc-1',
|
||||
documentTitle: 'Brief 1899',
|
||||
actor: {
|
||||
id: 'u-1',
|
||||
name: 'Anna Schmidt',
|
||||
initials: 'AS',
|
||||
color: '#336699'
|
||||
},
|
||||
count: 1,
|
||||
happenedAt: '2026-04-14T14:02:00Z',
|
||||
happenedAtUntil: null,
|
||||
youMentioned: false,
|
||||
...overrides
|
||||
}) as ActivityFeedItemDTO;
|
||||
|
||||
describe('DashboardActivityFeed', () => {
|
||||
it('renders the feed caption and show-all link', async () => {
|
||||
render(DashboardActivityFeed, { props: { feed: [] } });
|
||||
|
||||
await expect.element(page.getByText('Kommentare & Aktivität')).toBeVisible();
|
||||
const link = document.querySelector('a[href="/aktivitaeten"]');
|
||||
expect(link).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders nothing in the list when the feed is empty', async () => {
|
||||
render(DashboardActivityFeed, { props: { feed: [] } });
|
||||
|
||||
const lists = document.querySelectorAll('ul');
|
||||
expect(lists.length).toBe(0);
|
||||
});
|
||||
|
||||
it('renders one row per feed item with the actor initials', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: {
|
||||
feed: [baseItem(), baseItem({ documentId: 'doc-2', documentTitle: 'Brief 1900' })]
|
||||
}
|
||||
});
|
||||
|
||||
const items = document.querySelectorAll('li');
|
||||
expect(items.length).toBe(2);
|
||||
expect(document.body.textContent).toContain('AS');
|
||||
});
|
||||
|
||||
it('renders the question-mark badge when no actor is set', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ actor: null as unknown as undefined })] }
|
||||
});
|
||||
|
||||
const li = document.querySelector('li');
|
||||
expect(li?.textContent).toContain('?');
|
||||
});
|
||||
|
||||
it('renders the rollup count badge when count > 1', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ count: 5 })] }
|
||||
});
|
||||
|
||||
const badge = document.querySelector('[data-testid="feed-rollup-count"]');
|
||||
expect(badge?.textContent?.trim()).toBe('5');
|
||||
});
|
||||
|
||||
it('omits the rollup count badge when count is 1', async () => {
|
||||
render(DashboardActivityFeed, { props: { feed: [baseItem({ count: 1 })] } });
|
||||
|
||||
const badge = document.querySelector('[data-testid="feed-rollup-count"]');
|
||||
expect(badge).toBeNull();
|
||||
});
|
||||
|
||||
it('renders the "für dich" badge when youMentioned is true', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ youMentioned: true })] }
|
||||
});
|
||||
|
||||
await expect.element(page.getByText(/für dich/i)).toBeVisible();
|
||||
});
|
||||
|
||||
it('maps the kind enum to a localized verb (TEXT_SAVED)', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ kind: 'TEXT_SAVED' as ActivityFeedItemDTO['kind'] })] }
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('hat Text gespeichert in');
|
||||
});
|
||||
|
||||
it('maps the kind enum to a localized verb (FILE_UPLOADED)', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ kind: 'FILE_UPLOADED' as ActivityFeedItemDTO['kind'] })] }
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('hat eine Datei hochgeladen');
|
||||
});
|
||||
|
||||
it('falls back to the raw kind when no verb is mapped', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: {
|
||||
feed: [baseItem({ kind: 'UNKNOWN_KIND' as unknown as ActivityFeedItemDTO['kind'] })]
|
||||
}
|
||||
});
|
||||
|
||||
expect(document.body.textContent).toContain('UNKNOWN_KIND');
|
||||
});
|
||||
|
||||
it('renders a rollup time range when happenedAtUntil is set and count > 1', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: {
|
||||
feed: [
|
||||
baseItem({
|
||||
happenedAt: '2026-04-14T14:02:00Z',
|
||||
happenedAtUntil: '2026-04-14T14:32:00Z',
|
||||
count: 3
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// "14:02–14:32" appears (with the en-dash)
|
||||
expect(document.body.textContent).toMatch(/\d{2}:\d{2}–\d{2}:\d{2}/);
|
||||
});
|
||||
|
||||
it('uses the actor initials as the fallback name when name is null', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: {
|
||||
feed: [
|
||||
baseItem({
|
||||
actor: {
|
||||
id: 'u-2',
|
||||
name: null as unknown as undefined,
|
||||
initials: 'XR',
|
||||
color: '#000'
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const strong = document.querySelector('strong');
|
||||
expect(strong?.textContent).toBe('XR');
|
||||
});
|
||||
|
||||
it('builds the document detail href from documentId', async () => {
|
||||
render(DashboardActivityFeed, {
|
||||
props: { feed: [baseItem({ documentId: 'doc-xyz', documentTitle: 'Brief 1901' })] }
|
||||
});
|
||||
|
||||
const link = document.querySelector('a[href="/documents/doc-xyz"]');
|
||||
expect(link).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user