refactor(chronik): replace callback props with form actions in ChronikFuerDichBox
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m20s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m22s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m20s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m22s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
Dismiss (X) button and mark-all-read button now submit forms to /aktivitaeten?/dismiss-notification and /aktivitaeten?/mark-all-read respectively. Props renamed onMarkRead/onMarkAllRead → optimisticMarkRead/optimisticMarkAllRead. aktivitaeten/+page.svelte drops the now-deleted onMarkRead/onMarkAllRead wrapper functions and passes notificationStore.optimisticMarkRead/optimisticMarkAllRead directly to the box. Tests: $app/forms enhance mock added to both spec files so dismiss and mark-all assertions work synchronously against form-submit events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { enhance } from '$app/forms';
|
||||||
import * as m from '$lib/paraglide/messages.js';
|
import * as m from '$lib/paraglide/messages.js';
|
||||||
import { relativeTime } from '$lib/shared/utils/time';
|
import { relativeTime } from '$lib/shared/utils/time';
|
||||||
import type { NotificationItem } from '$lib/notification/notifications.svelte';
|
import type { NotificationItem } from '$lib/notification/notifications.svelte';
|
||||||
@@ -6,11 +7,11 @@ import { buildCommentHref } from '$lib/shared/discussion/commentDeepLink';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
unread: NotificationItem[];
|
unread: NotificationItem[];
|
||||||
onMarkRead: (n: NotificationItem) => void;
|
optimisticMarkRead: (id: string) => void;
|
||||||
onMarkAllRead: () => void;
|
optimisticMarkAllRead: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { unread, onMarkRead, onMarkAllRead }: Props = $props();
|
const { unread, optimisticMarkRead, optimisticMarkAllRead }: Props = $props();
|
||||||
|
|
||||||
function verb(type: NotificationItem['type'], actor: string): string {
|
function verb(type: NotificationItem['type'], actor: string): string {
|
||||||
return type === 'REPLY'
|
return type === 'REPLY'
|
||||||
@@ -66,14 +67,24 @@ function href(n: NotificationItem): string {
|
|||||||
{m.chronik_for_you_count({ count: unread.length })}
|
{m.chronik_for_you_count({ count: unread.length })}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<form
|
||||||
type="button"
|
action="/aktivitaeten?/mark-all-read"
|
||||||
data-testid="chronik-mark-all-read"
|
method="POST"
|
||||||
onclick={onMarkAllRead}
|
use:enhance={() => {
|
||||||
class="font-sans text-xs font-medium text-ink-3 transition-colors hover:text-ink"
|
optimisticMarkAllRead();
|
||||||
|
return async ({ update }) => {
|
||||||
|
await update({ reset: false, invalidateAll: false });
|
||||||
|
};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{m.chronik_mark_all_read()}
|
<button
|
||||||
</button>
|
type="submit"
|
||||||
|
data-testid="chronik-mark-all-read"
|
||||||
|
class="font-sans text-xs font-medium text-ink-3 transition-colors hover:text-ink"
|
||||||
|
>
|
||||||
|
{m.chronik_mark_all_read()}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul role="list" class="flex flex-col gap-2">
|
<ul role="list" class="flex flex-col gap-2">
|
||||||
@@ -89,7 +100,7 @@ function href(n: NotificationItem): string {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent-bg font-sans text-xs font-bold text-accent"
|
class="mt-0.5 inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-accent-bg font-sans text-xs font-bold text-accent"
|
||||||
>
|
>
|
||||||
{n.type === 'MENTION' ? '@' : '\u21A9'}
|
{n.type === 'MENTION' ? '@' : '↩'}
|
||||||
</span>
|
</span>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="font-sans text-sm leading-snug text-ink">
|
<p class="font-sans text-sm leading-snug text-ink">
|
||||||
@@ -100,25 +111,36 @@ function href(n: NotificationItem): string {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<form
|
||||||
type="button"
|
action="/aktivitaeten?/dismiss-notification"
|
||||||
data-testid="chronik-fuerdich-dismiss"
|
method="POST"
|
||||||
aria-label={m.chronik_mark_read_aria()}
|
use:enhance={() => {
|
||||||
onclick={() => onMarkRead(n)}
|
optimisticMarkRead(n.id);
|
||||||
class="mt-0.5 shrink-0 rounded-sm p-1 text-ink-3 transition-colors hover:bg-muted hover:text-ink focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
|
return async ({ update }) => {
|
||||||
|
await update({ reset: false, invalidateAll: false });
|
||||||
|
};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<svg
|
<input type="hidden" name="notificationId" value={n.id} />
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<button
|
||||||
class="h-4 w-4"
|
type="submit"
|
||||||
fill="none"
|
data-testid="chronik-fuerdich-dismiss"
|
||||||
viewBox="0 0 24 24"
|
aria-label={m.chronik_mark_read_aria()}
|
||||||
stroke="currentColor"
|
class="mt-0.5 shrink-0 rounded-sm p-1 text-ink-3 transition-colors hover:bg-muted hover:text-ink focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:outline-none"
|
||||||
stroke-width="2"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
<svg
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</button>
|
class="h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -5,6 +5,17 @@ import { page, userEvent } from 'vitest/browser';
|
|||||||
import ChronikFuerDichBox from './ChronikFuerDichBox.svelte';
|
import ChronikFuerDichBox from './ChronikFuerDichBox.svelte';
|
||||||
import type { NotificationItem } from '$lib/notification/notifications.svelte';
|
import type { NotificationItem } from '$lib/notification/notifications.svelte';
|
||||||
|
|
||||||
|
vi.mock('$app/forms', () => ({
|
||||||
|
enhance(node: HTMLFormElement, submit?: (opts: { formData: FormData }) => unknown) {
|
||||||
|
const handler = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submit?.({ formData: new FormData(node) } as never);
|
||||||
|
};
|
||||||
|
node.addEventListener('submit', handler);
|
||||||
|
return { destroy: () => node.removeEventListener('submit', handler) };
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
afterEach(cleanup);
|
afterEach(cleanup);
|
||||||
|
|
||||||
function notif(partial: Partial<NotificationItem>): NotificationItem {
|
function notif(partial: Partial<NotificationItem>): NotificationItem {
|
||||||
@@ -26,8 +37,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('renders inbox-zero state when there are no unread items', async () => {
|
it('renders inbox-zero state when there are no unread items', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [],
|
unread: [],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
const zero = document.querySelector('[data-testid="chronik-inbox-zero"]');
|
const zero = document.querySelector('[data-testid="chronik-inbox-zero"]');
|
||||||
expect(zero).not.toBeNull();
|
expect(zero).not.toBeNull();
|
||||||
@@ -37,8 +48,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('links to the archived mentions in the inbox-zero state', async () => {
|
it('links to the archived mentions in the inbox-zero state', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [],
|
unread: [],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
const link = document.querySelector('a[href="/aktivitaeten?filter=fuer-dich"]');
|
const link = document.querySelector('a[href="/aktivitaeten?filter=fuer-dich"]');
|
||||||
expect(link).not.toBeNull();
|
expect(link).not.toBeNull();
|
||||||
@@ -47,8 +58,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('renders the count badge with correct total when unread exists', async () => {
|
it('renders the count badge with correct total when unread exists', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [notif({ id: 'a' }), notif({ id: 'b' })],
|
unread: [notif({ id: 'a' }), notif({ id: 'b' })],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
await expect.element(page.getByText('2 neu')).toBeInTheDocument();
|
await expect.element(page.getByText('2 neu')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
@@ -56,8 +67,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('count badge has aria-live=polite when unread exists', async () => {
|
it('count badge has aria-live=polite when unread exists', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [notif({ id: 'a' })],
|
unread: [notif({ id: 'a' })],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
// Wait for render
|
// Wait for render
|
||||||
await expect.element(page.getByText('1 neu')).toBeInTheDocument();
|
await expect.element(page.getByText('1 neu')).toBeInTheDocument();
|
||||||
@@ -69,8 +80,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('does not render the "Alle gelesen" button when there are no unread items', async () => {
|
it('does not render the "Alle gelesen" button when there are no unread items', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [],
|
unread: [],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
await expect.element(page.getByText('Keine neuen Erwähnungen')).toBeInTheDocument();
|
await expect.element(page.getByText('Keine neuen Erwähnungen')).toBeInTheDocument();
|
||||||
const all = document.querySelector('[data-testid="chronik-mark-all-read"]');
|
const all = document.querySelector('[data-testid="chronik-mark-all-read"]');
|
||||||
@@ -80,38 +91,38 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('renders the "Alle gelesen" button when unread exists', async () => {
|
it('renders the "Alle gelesen" button when unread exists', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [notif({ id: 'a' })],
|
unread: [notif({ id: 'a' })],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
await expect.element(page.getByText('Alle gelesen')).toBeInTheDocument();
|
await expect.element(page.getByText('Alle gelesen')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onMarkAllRead when the "Alle gelesen" button is clicked', async () => {
|
it('calls optimisticMarkAllRead when the "Alle gelesen" button is submitted', async () => {
|
||||||
const onMarkAllRead = vi.fn();
|
const optimisticMarkAllRead = vi.fn();
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [notif({ id: 'a' })],
|
unread: [notif({ id: 'a' })],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead
|
optimisticMarkAllRead
|
||||||
});
|
});
|
||||||
await userEvent.click(page.getByText('Alle gelesen'));
|
await userEvent.click(page.getByText('Alle gelesen'));
|
||||||
expect(onMarkAllRead).toHaveBeenCalledTimes(1);
|
expect(optimisticMarkAllRead).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onMarkRead (and not navigation) when a per-item Dismiss button is clicked', async () => {
|
it('calls optimisticMarkRead with the notification id when its dismiss button is submitted', async () => {
|
||||||
const onMarkRead = vi.fn();
|
const optimisticMarkRead = vi.fn();
|
||||||
const n = notif({ id: 'xyz' });
|
const n = notif({ id: 'xyz' });
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [n],
|
unread: [n],
|
||||||
onMarkRead,
|
optimisticMarkRead,
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
const dismiss = document.querySelector(
|
const dismiss = document.querySelector(
|
||||||
'[data-testid="chronik-fuerdich-dismiss"]'
|
'[data-testid="chronik-fuerdich-dismiss"]'
|
||||||
) as HTMLButtonElement | null;
|
) as HTMLButtonElement | null;
|
||||||
expect(dismiss).not.toBeNull();
|
expect(dismiss).not.toBeNull();
|
||||||
dismiss?.click();
|
dismiss?.click();
|
||||||
expect(onMarkRead).toHaveBeenCalledTimes(1);
|
expect(optimisticMarkRead).toHaveBeenCalledTimes(1);
|
||||||
expect(onMarkRead.mock.calls[0][0]).toEqual(n);
|
expect(optimisticMarkRead.mock.calls[0][0]).toBe('xyz');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('mention row href includes both commentId and annotationId when annotationId is present', async () => {
|
it('mention row href includes both commentId and annotationId when annotationId is present', async () => {
|
||||||
@@ -124,8 +135,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
annotationId: 'annot-9'
|
annotationId: 'annot-9'
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
const link = document.querySelector(
|
const link = document.querySelector(
|
||||||
'a[href="/documents/doc-42?commentId=comment-7&annotationId=annot-9"]'
|
'a[href="/documents/doc-42?commentId=comment-7&annotationId=annot-9"]'
|
||||||
@@ -136,8 +147,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
it('Dismiss button is a sibling of the document link, never nested inside <a>', async () => {
|
it('Dismiss button is a sibling of the document link, never nested inside <a>', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
unread: [notif({ id: 'x' })],
|
unread: [notif({ id: 'x' })],
|
||||||
onMarkRead: vi.fn(),
|
optimisticMarkRead: vi.fn(),
|
||||||
onMarkAllRead: vi.fn()
|
optimisticMarkAllRead: vi.fn()
|
||||||
});
|
});
|
||||||
const dismiss = document.querySelector('[data-testid="chronik-fuerdich-dismiss"]');
|
const dismiss = document.querySelector('[data-testid="chronik-fuerdich-dismiss"]');
|
||||||
expect(dismiss).not.toBeNull();
|
expect(dismiss).not.toBeNull();
|
||||||
|
|||||||
@@ -4,6 +4,17 @@ import { page } from 'vitest/browser';
|
|||||||
import ChronikFuerDichBox from './ChronikFuerDichBox.svelte';
|
import ChronikFuerDichBox from './ChronikFuerDichBox.svelte';
|
||||||
import type { NotificationItem } from '$lib/notification/notifications';
|
import type { NotificationItem } from '$lib/notification/notifications';
|
||||||
|
|
||||||
|
vi.mock('$app/forms', () => ({
|
||||||
|
enhance(node: HTMLFormElement, submit?: (opts: { formData: FormData }) => unknown) {
|
||||||
|
const handler = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submit?.({ formData: new FormData(node) } as never);
|
||||||
|
};
|
||||||
|
node.addEventListener('submit', handler);
|
||||||
|
return { destroy: () => node.removeEventListener('submit', handler) };
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
afterEach(cleanup);
|
afterEach(cleanup);
|
||||||
|
|
||||||
const mention = (overrides: Partial<NotificationItem> = {}): NotificationItem => ({
|
const mention = (overrides: Partial<NotificationItem> = {}): NotificationItem => ({
|
||||||
@@ -22,7 +33,7 @@ const mention = (overrides: Partial<NotificationItem> = {}): NotificationItem =>
|
|||||||
describe('ChronikFuerDichBox', () => {
|
describe('ChronikFuerDichBox', () => {
|
||||||
it('renders the inbox-zero state when there are no unread', async () => {
|
it('renders the inbox-zero state when there are no unread', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: { unread: [], onMarkRead: () => {}, onMarkAllRead: () => {} }
|
props: { unread: [], optimisticMarkRead: () => {}, optimisticMarkAllRead: () => {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect.element(page.getByText(/keine neuen erwähnungen/i)).toBeVisible();
|
await expect.element(page.getByText(/keine neuen erwähnungen/i)).toBeVisible();
|
||||||
@@ -34,8 +45,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention(), mention({ id: 'n-2' }), mention({ id: 'n-3' })],
|
unread: [mention(), mention({ id: 'n-2' }), mention({ id: 'n-3' })],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead: () => {}
|
optimisticMarkAllRead: () => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -47,8 +58,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention({ id: 'n-m', type: 'MENTION' }), mention({ id: 'n-r', type: 'REPLY' })],
|
unread: [mention({ id: 'n-m', type: 'MENTION' }), mention({ id: 'n-r', type: 'REPLY' })],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead: () => {}
|
optimisticMarkAllRead: () => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,8 +73,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention({ actorName: 'Bertha' })],
|
unread: [mention({ actorName: 'Bertha' })],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead: () => {}
|
optimisticMarkAllRead: () => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,8 +87,8 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention({ type: 'REPLY', actorName: 'Carl' })],
|
unread: [mention({ type: 'REPLY', actorName: 'Carl' })],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead: () => {}
|
optimisticMarkAllRead: () => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,11 +97,11 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
.toBeVisible();
|
.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onMarkRead with the notification when its dismiss button is clicked', async () => {
|
it('calls optimisticMarkRead with the notification id when its dismiss button is clicked', async () => {
|
||||||
const onMarkRead = vi.fn();
|
const optimisticMarkRead = vi.fn();
|
||||||
const item = mention({ id: 'n-7' });
|
const item = mention({ id: 'n-7' });
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: { unread: [item], onMarkRead, onMarkAllRead: () => {} }
|
props: { unread: [item], optimisticMarkRead, optimisticMarkAllRead: () => {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
const dismiss = document.querySelector(
|
const dismiss = document.querySelector(
|
||||||
@@ -98,31 +109,31 @@ describe('ChronikFuerDichBox', () => {
|
|||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
dismiss.click();
|
dismiss.click();
|
||||||
|
|
||||||
expect(onMarkRead).toHaveBeenCalledWith(item);
|
expect(optimisticMarkRead).toHaveBeenCalledWith('n-7');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls onMarkAllRead when the mark-all-read button is clicked', async () => {
|
it('calls optimisticMarkAllRead when the mark-all-read button is clicked', async () => {
|
||||||
const onMarkAllRead = vi.fn();
|
const optimisticMarkAllRead = vi.fn();
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention()],
|
unread: [mention()],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead
|
optimisticMarkAllRead
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const btn = document.querySelector('[data-testid="chronik-mark-all-read"]') as HTMLElement;
|
const btn = document.querySelector('[data-testid="chronik-mark-all-read"]') as HTMLElement;
|
||||||
btn.click();
|
btn.click();
|
||||||
|
|
||||||
expect(onMarkAllRead).toHaveBeenCalledOnce();
|
expect(optimisticMarkAllRead).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds a deep-link href to the comment for each notification', async () => {
|
it('builds a deep-link href to the comment for each notification', async () => {
|
||||||
render(ChronikFuerDichBox, {
|
render(ChronikFuerDichBox, {
|
||||||
props: {
|
props: {
|
||||||
unread: [mention({ documentId: 'doc-x', referenceId: 'ref-y', annotationId: null })],
|
unread: [mention({ documentId: 'doc-x', referenceId: 'ref-y', annotationId: null })],
|
||||||
onMarkRead: () => {},
|
optimisticMarkRead: () => {},
|
||||||
onMarkAllRead: () => {}
|
optimisticMarkAllRead: () => {}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -76,14 +76,6 @@ async function onFilterChange(v: FilterValue) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onMarkRead(n: NotificationItem) {
|
|
||||||
await notificationStore.markRead(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onMarkAllRead() {
|
|
||||||
await notificationStore.markAllRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayFeed = $derived(applyClientFilter(data.activityFeed, data.filter));
|
const displayFeed = $derived(applyClientFilter(data.activityFeed, data.filter));
|
||||||
|
|
||||||
const isEmpty = $derived(displayFeed.length === 0);
|
const isEmpty = $derived(displayFeed.length === 0);
|
||||||
@@ -108,7 +100,11 @@ function retry() {
|
|||||||
{#if data.loadError === 'activity'}
|
{#if data.loadError === 'activity'}
|
||||||
<ChronikErrorCard onRetry={retry} />
|
<ChronikErrorCard onRetry={retry} />
|
||||||
{:else}
|
{:else}
|
||||||
<ChronikFuerDichBox unread={unread} onMarkRead={onMarkRead} onMarkAllRead={onMarkAllRead} />
|
<ChronikFuerDichBox
|
||||||
|
unread={unread}
|
||||||
|
optimisticMarkRead={notificationStore.optimisticMarkRead}
|
||||||
|
optimisticMarkAllRead={notificationStore.optimisticMarkAllRead}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<ChronikFilterPills value={data.filter} onChange={onFilterChange} />
|
<ChronikFilterPills value={data.filter} onChange={onFilterChange} />
|
||||||
|
|||||||
Reference in New Issue
Block a user