test(chronik): extract applyClientFilter helper with full test coverage
Addresses review concern: the fuer-dich predicate (youMentioned || youParticipated) had zero test coverage after feedFilters.test.ts was deleted. The new clientFilter module is a pure function that is directly testable, and the test explicitly documents why MENTION_CREATED items without the youMentioned flag are now excluded (they would have shown mentions directed at OTHER users under the old feedFilters.ts logic). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import ChronikEmptyState from '$lib/components/chronik/ChronikEmptyState.svelte'
|
||||
import ChronikErrorCard from '$lib/components/chronik/ChronikErrorCard.svelte';
|
||||
import type { components } from '$lib/generated/api';
|
||||
import type { FilterValue } from './+page.server';
|
||||
import { applyClientFilter } from './clientFilter';
|
||||
|
||||
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||
|
||||
@@ -83,13 +84,7 @@ async function onMarkAllRead() {
|
||||
await notificationStore.markAllRead();
|
||||
}
|
||||
|
||||
// fuer-dich cannot be expressed as a server-side kinds filter — youMentioned/youParticipated
|
||||
// are user-scoped flags that require client-side narrowing
|
||||
const displayFeed = $derived(
|
||||
data.filter === 'fuer-dich'
|
||||
? data.activityFeed.filter((item) => item.youMentioned || item.youParticipated)
|
||||
: data.activityFeed
|
||||
);
|
||||
const displayFeed = $derived(applyClientFilter(data.activityFeed, data.filter));
|
||||
|
||||
const isEmpty = $derived(displayFeed.length === 0);
|
||||
const emptyVariant = $derived<'first-run' | 'filter-empty' | 'inbox-zero'>(
|
||||
|
||||
94
frontend/src/routes/chronik/clientFilter.test.ts
Normal file
94
frontend/src/routes/chronik/clientFilter.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { applyClientFilter } from './clientFilter';
|
||||
import type { components } from '$lib/generated/api';
|
||||
|
||||
type Item = components['schemas']['ActivityFeedItemDTO'];
|
||||
|
||||
function makeItem(overrides: Partial<Item> = {}): Item {
|
||||
return {
|
||||
kind: 'FILE_UPLOADED',
|
||||
documentId: 'd1',
|
||||
documentTitle: 'Brief A',
|
||||
happenedAt: '2026-04-20T10:00:00Z',
|
||||
youMentioned: false,
|
||||
youParticipated: false,
|
||||
count: 1,
|
||||
actor: null,
|
||||
happenedAtUntil: null,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
describe('applyClientFilter', () => {
|
||||
describe('non-fuer-dich filters pass through unchanged', () => {
|
||||
it('alle returns all items', () => {
|
||||
const items = [makeItem(), makeItem({ kind: 'COMMENT_ADDED' })];
|
||||
expect(applyClientFilter(items, 'alle')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('hochgeladen passes through (server already filtered by kinds)', () => {
|
||||
const items = [makeItem({ kind: 'FILE_UPLOADED' }), makeItem({ kind: 'COMMENT_ADDED' })];
|
||||
expect(applyClientFilter(items, 'hochgeladen')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('transkription passes through (server already filtered by kinds)', () => {
|
||||
const items = [makeItem({ kind: 'TEXT_SAVED' })];
|
||||
expect(applyClientFilter(items, 'transkription')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('kommentare passes through (server already filtered by kinds)', () => {
|
||||
const items = [makeItem({ kind: 'COMMENT_ADDED' })];
|
||||
expect(applyClientFilter(items, 'kommentare')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fuer-dich applies youMentioned || youParticipated predicate', () => {
|
||||
it('includes items where youMentioned is true', () => {
|
||||
const items = [makeItem({ youMentioned: true })];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('includes items where youParticipated is true', () => {
|
||||
const items = [makeItem({ youParticipated: true })];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('includes items where both flags are true', () => {
|
||||
const items = [makeItem({ youMentioned: true, youParticipated: true })];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('excludes items where neither flag is set', () => {
|
||||
const items = [makeItem({ kind: 'COMMENT_ADDED' })];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('MENTION_CREATED without youMentioned flag is excluded', () => {
|
||||
// youMentioned is set by the backend only when this event is directed at the current user.
|
||||
// Unlike the old feedFilters.ts, we no longer include MENTION_CREATED unconditionally —
|
||||
// that incorrectly showed mentions directed at OTHER users.
|
||||
const items = [
|
||||
makeItem({ kind: 'MENTION_CREATED', youMentioned: false, youParticipated: false })
|
||||
];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('MENTION_CREATED with youMentioned true is included', () => {
|
||||
const items = [makeItem({ kind: 'MENTION_CREATED', youMentioned: true })];
|
||||
expect(applyClientFilter(items, 'fuer-dich')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('filters mixed items to only personally relevant ones', () => {
|
||||
const items = [
|
||||
makeItem({ kind: 'FILE_UPLOADED' }),
|
||||
makeItem({ kind: 'COMMENT_ADDED', youParticipated: true }),
|
||||
makeItem({ kind: 'MENTION_CREATED', youMentioned: true }),
|
||||
makeItem({ kind: 'TEXT_SAVED' })
|
||||
];
|
||||
const result = applyClientFilter(items, 'fuer-dich');
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].kind).toBe('COMMENT_ADDED');
|
||||
expect(result[1].kind).toBe('MENTION_CREATED');
|
||||
});
|
||||
});
|
||||
});
|
||||
18
frontend/src/routes/chronik/clientFilter.ts
Normal file
18
frontend/src/routes/chronik/clientFilter.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { components } from '$lib/generated/api';
|
||||
import type { FilterValue } from './+page.server';
|
||||
|
||||
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||
|
||||
// All server-side filters (hochgeladen, transkription, kommentare) are already applied via
|
||||
// kinds param — the server returns only matching items, so client-side is a no-op.
|
||||
// fuer-dich is the only filter that requires client-side narrowing: youMentioned and
|
||||
// youParticipated are user-scoped flags that cannot be expressed as a kinds filter.
|
||||
export function applyClientFilter(
|
||||
items: ActivityFeedItemDTO[],
|
||||
filter: FilterValue
|
||||
): ActivityFeedItemDTO[] {
|
||||
if (filter === 'fuer-dich') {
|
||||
return items.filter((item) => item.youMentioned || item.youParticipated);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user