loadError branches (FuerDichBox skipped vs shown), first-run vs filter-empty empty-state variants, timeline rendering when feed has items, FilterPills + FuerDichBox conditional render. 7 tests covering ~12 branches in the page file. Refs #496. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
4.3 KiB
TypeScript
152 lines
4.3 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
|
|
const mockNavigating = { type: null };
|
|
const mockPage = { url: new URL('http://localhost/aktivitaeten') };
|
|
|
|
vi.mock('$app/state', () => ({
|
|
get navigating() {
|
|
return mockNavigating;
|
|
},
|
|
get page() {
|
|
return mockPage;
|
|
}
|
|
}));
|
|
|
|
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: () => () => {}
|
|
}));
|
|
|
|
vi.mock('$lib/notification/notifications.svelte', () => ({
|
|
notificationStore: {
|
|
notifications: [],
|
|
init: vi.fn(),
|
|
destroy: vi.fn(),
|
|
markRead: vi.fn(),
|
|
markAllRead: vi.fn()
|
|
}
|
|
}));
|
|
|
|
const { default: AktivitaetenPage } = await import('./+page.svelte');
|
|
|
|
afterEach(cleanup);
|
|
|
|
const baseData = (overrides: Record<string, unknown> = {}) => ({
|
|
filter: 'alle' as const,
|
|
activityFeed: [],
|
|
unreadNotifications: [],
|
|
loadError: null,
|
|
...overrides
|
|
});
|
|
|
|
describe('aktivitaeten page', () => {
|
|
it('renders the page heading', async () => {
|
|
render(AktivitaetenPage, { props: { data: baseData() } });
|
|
|
|
await expect.element(page.getByRole('heading', { name: /aktivitäten/i })).toBeVisible();
|
|
});
|
|
|
|
it('renders the error card when loadError is "activity"', async () => {
|
|
render(AktivitaetenPage, { props: { data: baseData({ loadError: 'activity' }) } });
|
|
|
|
// ChronikErrorCard renders some retry mechanism
|
|
const main = document.querySelector('main');
|
|
expect(main).not.toBeNull();
|
|
// FuerDichBox should NOT render when loadError is set
|
|
const fuerDich = document.querySelector('[data-testid="chronik-inbox-zero"]');
|
|
expect(fuerDich).toBeNull();
|
|
});
|
|
|
|
it('renders the FuerDichBox and FilterPills when loadError is null', async () => {
|
|
render(AktivitaetenPage, { props: { data: baseData() } });
|
|
|
|
// FuerDichBox shows the inbox-zero state when no unread
|
|
const fuerDich = document.querySelector('[data-testid="chronik-inbox-zero"]');
|
|
expect(fuerDich).not.toBeNull();
|
|
|
|
// FilterPills present
|
|
const radiogroup = document.querySelector('[role="radiogroup"]');
|
|
expect(radiogroup).not.toBeNull();
|
|
});
|
|
|
|
it('renders the first-run empty state when activityFeed is empty', async () => {
|
|
render(AktivitaetenPage, { props: { data: baseData() } });
|
|
|
|
const empty = document.querySelector('[data-testid="chronik-empty-state"]');
|
|
expect(empty?.getAttribute('data-variant')).toBe('first-run');
|
|
});
|
|
|
|
it('renders the filter-empty empty state when feed has items but filter rules out all', async () => {
|
|
render(AktivitaetenPage, {
|
|
props: {
|
|
data: baseData({
|
|
filter: 'fuer-dich' as const,
|
|
activityFeed: [
|
|
{
|
|
kind: 'TEXT_SAVED',
|
|
documentId: 'doc-1',
|
|
documentTitle: 'Brief',
|
|
actor: { id: 'u1', name: 'Anna', initials: 'AS', color: '#000' },
|
|
count: 1,
|
|
happenedAt: '2026-01-01T00:00:00Z',
|
|
happenedAtUntil: null,
|
|
youMentioned: false
|
|
}
|
|
]
|
|
})
|
|
}
|
|
});
|
|
|
|
const empty = document.querySelector('[data-testid="chronik-empty-state"]');
|
|
expect(empty?.getAttribute('data-variant')).toBe('filter-empty');
|
|
});
|
|
|
|
it('renders the timeline when displayFeed is non-empty', async () => {
|
|
render(AktivitaetenPage, {
|
|
props: {
|
|
data: baseData({
|
|
filter: 'alle' as const,
|
|
activityFeed: [
|
|
{
|
|
kind: 'TEXT_SAVED',
|
|
documentId: 'doc-1',
|
|
documentTitle: 'Brief 1899',
|
|
actor: { id: 'u1', name: 'Anna', initials: 'AS', color: '#000' },
|
|
count: 1,
|
|
happenedAt: '2026-01-01T00:00:00Z',
|
|
happenedAtUntil: null,
|
|
youMentioned: false
|
|
}
|
|
]
|
|
})
|
|
}
|
|
});
|
|
|
|
// Timeline renders the document title from the feed item
|
|
expect(document.body.textContent).toContain('Brief 1899');
|
|
// No empty-state in this case
|
|
const empty = document.querySelector('[data-testid="chronik-empty-state"]');
|
|
expect(empty).toBeNull();
|
|
});
|
|
|
|
it('renders without crashing when filter is set to a non-default value', async () => {
|
|
render(AktivitaetenPage, {
|
|
props: { data: baseData({ filter: 'transkription' as const }) }
|
|
});
|
|
|
|
const main = document.querySelector('main');
|
|
expect(main).not.toBeNull();
|
|
});
|
|
});
|